前幾天看到了有人在android5.0上實現了如下圖一樣的效果,我自己就去搜了下。參考了國外一篇文章和國內的一篇文章,最終實現了想要的效果。具體參考的網址我已經貼到文章末尾,大家可以去英文的那個網站看看,講解的很傻瓜化。

好,下面我們來看看如何實現如上面右圖一樣的效果。
1.原理分析
(1)我們是否需要在點擊后開啟一個新的Activity呢?我參考了很多有類似功能的相冊應用,發現大家都是在一個Activity中就完成了這個功能。所以我們僅僅需要一個布局文件。
(2)在同一個布局文件中我們可以放兩個層次,一個是顯示小圖的GridView控件,一個是用於展示大圖的viewPager控件。


我們只需要剛開始隱藏viewpager,點擊后用viewpager覆蓋后面的gridview即可。這樣就造成了用戶認為的新界面。

(3)我們讓圖片動畫放大到屏幕上,那么就需要一個動畫將其連貫起來,我們就需要得到小圖片的位置才能構造出動畫效果。所以要通過點擊的小圖片計算出它的位置
(4)如果為了兼容,那么就需要用一個動畫兼容包來兼容2.x版本
(5)上面覆蓋的viewpager需要根據點擊的圖片來加載不同的圖片,並且可以在點擊后釋放自己的資源,並回到透明狀態。
(6)如果viewpager滑動了好幾張圖片,當前顯示的大圖在后面的gridview中找不到位置,那么久應該直接漸變消失。而不做動畫效果處理。
2.編碼實現
2.1 建立布局文件
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<GridView
android:id="@+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:columnWidth="90dp"
android:gravity="center"
android:horizontalSpacing="10dp"
android:numColumns="auto_fit"
android:stretchMode="columnWidth"
android:verticalSpacing="10dp" />
<android.support.v4.view.ViewPager
android:id="@+id/detail_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#eeeeee"
android:visibility="invisible"
android:contentDescription="用來展示圖片的viewpager"/>
</RelativeLayout>
</FrameLayout>
一個很簡單的布局文件,兩個視圖重疊在一起。剛開始時請將viewpager設置為invisible。保證不會干擾點擊事件的傳遞。
2.2 寫一個Activity來加載這個布局
package com.kale.gridviewanimtest;
import android.app.Activity;
import android.os.Bundle;
import android.widget.GridView;
/**
* @from:
* http://blog.csdn.net/huluhong/article/details/40379767
* https://github.com/ywenblocker/Android-Photo-Zoom
*/
/**
* @author:Jack Tony
* @tips :
* @date :2014-11-11
*/
public class MainActivity extends Activity {
GridView gridview;
// References to our images in res > drawable
public static int[] thumbPicIds = { R.drawable.sample_0, R.drawable.sample_1,
R.drawable.sample_2, R.drawable.sample_3, R.drawable.sample_4,
R.drawable.sample_5, R.drawable.sample_6, R.drawable.sample_7,
R.drawable.sample_8, R.drawable.sample_9, R.drawable.sample_10,
R.drawable.sample_11, R.drawable.sample_12, R.drawable.sample_13,
R.drawable.sample_14, R.drawable.sample_0, R.drawable.sample_1,
R.drawable.sample_2, R.drawable.sample_3, R.drawable.sample_4,
R.drawable.sample_5, R.drawable.sample_6, R.drawable.sample_7,
R.drawable.sample_8, R.drawable.sample_9, R.drawable.sample_10,
R.drawable.sample_11, R.drawable.sample_12, R.drawable.sample_13,
R.drawable.sample_14 };
//大圖片的的id,這里為了簡單弄成和小圖一樣的
public static int[] largePicIds = thumbPicIds;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gridview = (GridView) findViewById(R.id.gridview);
gridview.setAdapter(new GridViewAdapter(this, thumbPicIds));
}
}
這個Activity十分簡單,就是給gridview添加適配器。添加的時候傳入了圖片數組,下面講到的GridView的Adapter才是重點。
2.3 GridView的適配器——GridViewAdapter
這個適配器其實也很普通,只不過給ImageView添加了點擊事件,在這個點擊事件中我們就開始做我們想要做的事情了。
package com.kale.gridviewanimtest;
import com.kale.gridviewanimtest.ZoomTutorial.OnZoomListener;
import android.app.Activity;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
public class GridViewAdapter extends BaseAdapter {
private Context mContext;
// 要展示的小圖片的id數組
private int[] mThumbIds;
public GridViewAdapter(Context c, int[] thumbIds) {
mContext = c;
mThumbIds = thumbIds;
}
/**
* 要顯示的圖片的數目,thumbIds是小圖片的resource Id
*/
public int getCount() {
return mThumbIds.length;
}
public Object getItem(int position) {
return mThumbIds[position];
}
public long getItemId(int position) {
return position;
}
/**
* 創建每個item的視圖
*/
public View getView(final int position, View convertView, ViewGroup parent) {
final ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
// 這里的scaleType是CENTER_CROP
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mThumbIds[position]);
imageView.setTag(mThumbIds[position]);
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//當前drawable的res的id
int id = (Integer) v.getTag();
setViewPagerAndZoom(imageView, position);
}
});
return imageView;
}
public void setViewPagerAndZoom(View v ,int position) {
//得到要放大展示的視圖界面
ViewPager expandedView = (ViewPager)((Activity)mContext).findViewById(R.id.detail_view);
//最外層的容器,用來計算
View containerView = (FrameLayout)((Activity)mContext).findViewById(R.id.container);
//實現放大縮小類,傳入當前的容器和要放大展示的對象
ZoomTutorial mZoomTutorial = new ZoomTutorial(containerView, expandedView);
ViewPagerAdapter adapter = new ViewPagerAdapter(mContext,
MainActivity.largePicIds,mZoomTutorial);
expandedView.setAdapter(adapter);
expandedView.setCurrentItem(position);
// 通過傳入Id來從小圖片擴展到大圖,開始執行動畫
mZoomTutorial.zoomImageFromThumb(v);
mZoomTutorial.setOnZoomListener(new OnZoomListener() {
@Override
public void onThumbed() {
// TODO 自動生成的方法存根
System.out.println("現在是-------------------> 小圖狀態");
}
@Override
public void onExpanded() {
// TODO 自動生成的方法存根
System.out.println("現在是-------------------> 大圖狀態");
}
});
}
}
點擊事件中觸發的方法——setViewPagerAndZoom(…)
public void setViewPagerAndZoom(View v ,int position) {
//得到要放大展示的視圖界面
ViewPager expandedView = (ViewPager)((Activity)mContext).findViewById(R.id.detail_view);
//最外層的容器,用來計算
View containerView = (FrameLayout)((Activity)mContext).findViewById(R.id.container);
//實現放大縮小類,傳入當前的容器和要放大展示的對象
ZoomTutorial mZoomTutorial = new ZoomTutorial(containerView, expandedView);
ViewPagerAdapter adapter = new ViewPagerAdapter(mContext,
MainActivity.largePicIds,mZoomTutorial);
expandedView.setAdapter(adapter);
expandedView.setCurrentItem(position);
// 通過傳入Id來從小圖片擴展到大圖,開始執行動畫
mZoomTutorial.zoomImageFromThumb(v);
mZoomTutorial.setOnZoomListener(new OnZoomListener() {
@Override
public void onThumbed() {
// TODO 自動生成的方法存根
System.out.println("現在是-------------------> 小圖狀態");
}
@Override
public void onExpanded() {
// TODO 自動生成的方法存根
System.out.println("現在是-------------------> 大圖狀態");
}
});
}
* 在這個方法中我們發現了這么一個類,這個類就是產生動畫的主要類。這個類的構造方法是要傳入一個容器的視圖還有一個是要展示放大圖片的視圖。這個容器就是布局文件中最外層的container,展示的視圖就是viewpager。
//得到要放大展示的視圖界面
ViewPager expandedView = (ViewPager)((Activity)mContext).findViewById(R.id.detail_view);
//最外層的容器,用來計算
View containerView = (FrameLayout)((Activity)mContext).findViewById(R.id.container);
//實現放大縮小類,傳入當前的容器和要放大展示的對象
ZoomTutorial mZoomTutorial = new ZoomTutorial(containerView, expandedView);
* 傳入容器的作用是來計算當前小圖片在容器中的位置,為動畫做准備。傳入viewpager的目的是小圖片點擊后需要通過動畫來慢慢放大出一個Viewpager。
* 這個方法就是執行點擊后將小圖的操作,這里傳入的是小圖片的視圖,用來確定小圖片現在在屏幕上的詳細位置。
// 通過傳入Id來從小圖片擴展到大圖,開始執行動畫
mZoomTutorial.zoomImageFromThumb(v);
* 這個監聽器就是來判斷當前是展示的是小圖界面還是大圖界面。
mZoomTutorial.setOnZoomListener(new OnZoomListener() {
@Override
public void onThumbed() {
System.out.println("現在是-------------------> 小圖狀態");
}
@Override
public void onExpanded() {
System.out.println("現在是-------------------> 大圖狀態");
}
});
* 這個是根據當前大圖的position來進行將viewpager動態縮放到小圖的操作,在viewpager的適配器中使用。
// 執行結束動畫的操作
mZoomTutorial.closeZoomAnim(position);
2.4 viewpager的適配器
這個適配器很簡單,就是滑動時展示下圖片。點擊圖片后執行縮放動畫。注意一下,這里傳入的圖片應該是大圖!
package com.kale.gridviewanimtest;
import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager.LayoutParams;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ImageView;
/**
* @author:Jack Tony
* @tips :viewpager的適配器
* @date :2014-11-12
*/
public class ViewPagerAdapter extends PagerAdapter {
private int[] sDrawables;
private Context mContext;
private ZoomTutorial mZoomTutorial;
public ViewPagerAdapter( Context context ,int[] imgIds,ZoomTutorial zoomTutorial) {
this.sDrawables = imgIds;
this.mContext = context;
this.mZoomTutorial = zoomTutorial;
}
@Override
public int getCount() {
return sDrawables.length;
}
@Override
public View instantiateItem(ViewGroup container, final int position) {
final ImageView imageView = new ImageView(mContext);
imageView.setImageResource(sDrawables[position]);
container.addView(imageView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mZoomTutorial.closeZoomAnim(position);
}
});
return imageView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
2.5 ZoomTutorial類
這個類是核心類,是我自己從參考文獻的代碼中進行修改和完善出來的。大大降低了程序中的依賴性,方便調用。下面我們來詳細說說這個類里面的各種方法。
(1)構造函數
final private int mAnimationDuration = 300;// 動畫持續的時間,300比較合適
private Animator mCurrentAnimator;//當前的動畫對象
private View mContainView;//當前屏幕中視圖最外層的容器
private ViewGroup mThumbViewParent;//小圖片的視圖
private View mExpandedView;//大圖片所在的視圖
private Rect startBounds;//開始動畫的區域范圍
private float startScale;//開始的比率
private float startScaleFinal;//結束時的比率
public ZoomTutorial(View containerView,View expandedView) {
mContainView = containerView;
mExpandedView = expandedView;
}
構造函數中傳入了容器對象和要展示大圖的對象,都是來用於計算的。變量中定義了動畫等對象。
(2)zoomImageFromThumb
這個方法十分重要,就是用來執行動畫的。
1 /**
2 * 十分重要的一個方法,用於展示大的圖片
3 *
4 * @param thumbView
5 * @param imageResId
6 */
7 public void zoomImageFromThumb(final View thumbView) {
8 mThumbViewParent = (ViewGroup) thumbView.getParent();
9 // If there's an animation in progress, cancel it immediately and
10 // proceed with this one.
11 if (mCurrentAnimator != null) {
12 mCurrentAnimator.cancel();
13 }
14
15 // Calculate the starting and ending bounds for the zoomed-in image.
16 // This step involves lots of math. Yay, math.
17 // 計算開始和結束的邊界+偏移量
18 startBounds = new Rect();
19 final Rect finalBounds = new Rect();// 結束的邊界
20 final Point globalOffset = new Point();// 現在view對其父控件的偏移量
21
22 // The start bounds are the global visible rectangle of the thumbnail,
23 // 開始的邊界是小圖整體可見部分的范圍
24 // and the final bounds are the global visible rectangle of the container view.
25 // 結束的邊界是容器的邊界
26 // Also set the container view's offset as the origin for the bounds,
27 // since that's the origin for the positioning animation properties (X, Y).
28 thumbView.getGlobalVisibleRect(startBounds);
29 // 這里的id,container是整個布局最外層的容器
30 mContainView.getGlobalVisibleRect(finalBounds, globalOffset);
31
32 // 開始設置偏移量,這樣就可以知道現在圖片距離邊界的位置
33 startBounds.offset(-globalOffset.x, -globalOffset.y);
34 finalBounds.offset(-globalOffset.x, -globalOffset.y);
35
36 //設置縮放的比例和位置
37 set_Center_crop(finalBounds);
38
39 mExpandedView.setVisibility(View.VISIBLE);
40
41 // Set the pivot point for SCALE_X and SCALE_Y transformations to the
42 // top-left corner of
43 // the zoomed-in view (the default is the center of the view).
44 AnimatorSet animSet = new AnimatorSet();
45 animSet.setDuration(1);
46 animSet.play(ObjectAnimator.ofFloat(mExpandedView, "pivotX", 0f))
47 .with(ObjectAnimator.ofFloat(mExpandedView, "pivotY", 0f))
48 .with(ObjectAnimator.ofFloat(mExpandedView, "alpha", 1.0f));
49 animSet.start();
50
51 startZoomAnim(mExpandedView, startBounds, finalBounds, startScale);
52 // Upon clicking the zoomed-in image, it should zoom back down to the
53 // original bounds and show the thumbnail instead of the expanded image.
54 startScaleFinal = startScale;
55 }
8行:得到小圖的父控件,其實就是我們的GridView。這個之后會用到。
18行:開始初始化用來存放小圖區域的對象。Rect其實就是一個矩形范圍,Rect類主要用於表示坐標系中的一塊矩形區域,並可以對其做一些簡單操作。關於Rect的奇葩之處可以參考這篇文章:http://www.cnblogs.com/hrlnw/archive/2013/07/14/3189755.html
28行:傳入一個startBounds,得到小圖的范圍。然后startBounds就是小圖的范圍了。getGlobalVisibleRect , 獲取全局坐標系的一個視圖區域, 返回一個填充的Rect對象,該Rect是基於總整個屏幕的。
30行:通過getGlobalVisibleRect()方法得到當前屏幕容器的范圍和偏移坐標。
37行:開始執行拉伸比率的計算,這里的finalBounds就是屏幕容器的區域,我們點擊小圖放大到的就是屏幕容器的區域。
/**
* 通過結束的邊界計算開始拉伸的比例
*
* Adjust the start bounds to be the same aspect ratio as the final bounds
* using the "center crop" technique. 通過 center
* crop算法來調整開始邊界,讓它和的結束邊界保持同一個縱橫比例,也就是長寬比 This prevents undesirable
* stretching during the animation.//在動畫執行時保證不讓圖片拉伸 Also calculate the start
* scaling factor (the end scaling factor is always 1.0).
* 我們也需要計算開始的比率因子,結束比例一直是1.0.因為是將圖片從小放到自己的大小。
*/
private void set_Center_crop(Rect finalBounds) {
if ((float) finalBounds.width() / finalBounds.height() > (float)
startBounds.width() / startBounds.height()) {
// Extend start bounds horizontally
startScale = (float) startBounds.height() / finalBounds.height();
float startWidth = startScale * finalBounds.width();
float deltaWidth = (startWidth - startBounds.width()) / 2;
startBounds.left -= deltaWidth;
startBounds.right += deltaWidth;
} else {
// Extend start bounds vertically
startScale = (float) startBounds.width() / finalBounds.width();
float startHeight = startScale * finalBounds.height();
float deltaHeight = (startHeight - startBounds.height()) / 2;
startBounds.top -= deltaHeight;
startBounds.bottom += deltaHeight;
}
}
好了,在計算時我們就已經設置了startScale和startBounds的值,startScale是拉伸比率。現在是通過這個比例拉伸的,那么我們要縮小的時候,就還是按照這個比率進行縮小。startBounds就是小圖的坐標位置。
39行:設置大圖view可見,為接下來動畫做准備。
46-47行:設置漸變動畫是從自己的左上角(0,0)開始的
44行:設定大圖慢慢漸變出來,這個方法必須存在。這樣用戶體驗會很好。
51行:開始真正執行動畫
54行:結束時動畫縮放的比率也應該等於開始縮放的比率。
(3)startZoomAnim
通過剛剛計算好的小圖的位置和拉伸的比率來執行動畫。為了不失真,這里是保持長寬比進行放大的。
/**
* @param v 執行動畫的view
* @param startBounds 開始的邊界
* @param finalBounds 結束時的邊界
* @param startScale 開始的拉伸比率
*/
public void startZoomAnim(View v, Rect startBounds, Rect finalBounds, float startScale) {
// Construct and run the parallel animation of the four translation and
// scale properties (X, Y, SCALE_X, and SCALE_Y).
AnimatorSet set = new AnimatorSet();
set.play(
ObjectAnimator.ofFloat(v, "x", startBounds.left, finalBounds.left))
.with(ObjectAnimator.ofFloat(v, "y", startBounds.top, finalBounds.top))
.with(ObjectAnimator.ofFloat(v, "scaleX", startScale, 1f))
.with(ObjectAnimator.ofFloat(v, "scaleY", startScale, 1f));
set.setDuration(mAnimationDuration);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCurrentAnimator = null;
if (listener != null) {
listener.onExpanded();
}
}
@Override
public void onAnimationCancel(Animator animation) {
mCurrentAnimator = null;
if (listener != null) {
listener.onExpanded();
}
}
});
set.start();
mCurrentAnimator = set;
}
(4)closeZoomAnim
放到后我們還得提供縮小的方法,這個方法因為viewpager的存在而變得不簡單,如果viewpager滑動顯示的圖片不在當前的gridview中呢?因此我們就需要判斷下當前viewpager展示的圖片在不在gridview中。下面就是判斷方法,如果在返回true,應該執行的是縮小動畫,如果返回false則表示不再屏幕中,那么就應該漸變消失。很多相冊應用,在這里都有或大或小的bug,大家可以自行尋找。
/**
* 在GridView中,使用getChildAt(index)的取值,只能是當前可見區域(列表可滾動)的子項!
* 因為子項會進行復用。這里強制轉換了下,變成了GridView,實際使用中需要進行修改
* 【參考】
* http://xie2010.blog.163.com/blog/static/211317365201402395944633/
* http://blog.csdn.net/you_and_me12/article/details/7271006
*
* @param position
* @return 判斷這個position的view是否現在顯示在屏幕上,如果沒有顯示就返回false
*/
public boolean getScaleFinalBounds(int position) {
//得到顯示區域中第一個子視圖的序號
int firstPosition = ((AdapterView<?>)mThumbViewParent).getFirstVisiblePosition();
View childView = mThumbViewParent.getChildAt(position - firstPosition);
startBounds = new Rect();
final Rect finalBounds = new Rect();
final Point globalOffset = new Point();
try {
//通過這個計算startBounds,得到當前view的位置,從而設定偏移值
childView.getGlobalVisibleRect(startBounds);
} catch (Exception e) {
return false;
}
mContainView.findViewById(R.id.container).getGlobalVisibleRect(finalBounds, globalOffset);
startBounds.offset(-globalOffset.x, -globalOffset.y);
finalBounds.offset(-globalOffset.x, -globalOffset.y);
//設置比率
set_Center_crop(finalBounds);
startScaleFinal = startScale;
return true;
}
上面的方法是通過viewpager的position得到Gridview中相應position的子視圖。這里的前提是viewpager和Gridview必須擁有相應數目的圖片和圖片position都是從0開始的。我們還需要注意需要將position減去當前屏幕中第一個子視圖的position。因為position是絕對數目,從0開始計數。getChildAt(int index)傳入的index是一個相對屏幕的內容的數目,因此我們就需要將絕對數目變為相對數目。這里需要尤為注意。
這里在判斷的時候,還會根據自視圖計算子視圖的應該處於的位置,這樣便於縮放。說了這么多還沒到動畫結束的操作,其實動畫結束的方法很簡單,下面就貼出來。
/**
* 根據position執行動畫,如果這個圖片在當前屏幕顯示范圍內,那就執行縮小。否則直接漸變
* @param position
*/
public void closeZoomAnim(int position) {
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
// Animate the four positioning/sizing properties in parallel,back to their original values.
AnimatorSet set = new AnimatorSet();
/**
* 因為展開圖可能是在viewpager中,所以現在顯示的圖片,或許並不是第一次打開的圖片,這里應該考慮兩點
* 1.改變圖片縮小后回到的位置
* 2.如果圖片縮小后回到的位置不在屏幕中,直接漸變消失
*/
boolean isInBound = getScaleFinalBounds(position);
if (isInBound) {
set.play(ObjectAnimator.ofFloat(mExpandedView, "x", startBounds.left))
.with(ObjectAnimator.ofFloat(mExpandedView, "y", startBounds.top))
.with(ObjectAnimator.ofFloat(mExpandedView, "scaleX", startScaleFinal))
.with(ObjectAnimator.ofFloat(mExpandedView, "scaleY", startScaleFinal));
} else {
// 如果當前顯示的圖片不在gridview當前顯示的圖片中,等於越界了。這時我們就不執行縮放操作,直接漸變消失即可。
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(mExpandedView, "alpha", 0.1f);
set.play(alphaAnimator);
}
set.setDuration(mAnimationDuration);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mExpandedView.clearAnimation();
mExpandedView.setVisibility(View.GONE);
mCurrentAnimator = null;
if (listener != null) {
listener.onThumbed();
}
}
@Override
public void onAnimationCancel(Animator animation) {
mExpandedView.clearAnimation();
mExpandedView.setVisibility(View.GONE);
mCurrentAnimator = null;
if (listener != null) {
listener.onThumbed();
}
}
});
set.start();
mCurrentAnimator = set;
}
(5)監聽器
這里為了判斷當前的狀態寫了一個簡單的監聽器,大家可以根據需要進行擴展和優化。這個監聽器作用於動畫執行結束時,在上面的代碼中它已經有所出現了。
private OnZoomListener listener;
public void setOnZoomListener(OnZoomListener l) {
listener = l;
}
public interface OnZoomListener {
public void onExpanded();//點擊后展示大圖成功后調用
public void onThumbed();//點擊后縮小回小圖時調用
}
這個類的全部代碼:
package com.kale.gridviewanimtest;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.AdapterView;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
public class ZoomTutorial {
final private int mAnimationDuration = 300;// 動畫持續的時間,300比較合適
private Animator mCurrentAnimator;//當前的動畫對象
private View mContainView;//當前屏幕中視圖最外層的容器
private ViewGroup mThumbViewParent;//小圖片的視圖
private View mExpandedView;//大圖片所在的視圖
private Rect startBounds;//開始動畫的區域范圍
private float startScale;//開始的比率
private float startScaleFinal;//結束時的比率
public ZoomTutorial(View containerView,View expandedView) {
mContainView = containerView;
mExpandedView = expandedView;
}
/**
* 十分重要的一個方法,用於展示大的圖片
*
* @param thumbView
* @param imageResId
*/
public void zoomImageFromThumb(final View thumbView) {
mThumbViewParent = (ViewGroup) thumbView.getParent();
// If there's an animation in progress, cancel it immediately and
// proceed with this one.
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
// Calculate the starting and ending bounds for the zoomed-in image.
// This step involves lots of math. Yay, math.
// 計算開始和結束的邊界+偏移量
startBounds = new Rect();
final Rect finalBounds = new Rect();// 結束的邊界
final Point globalOffset = new Point();// 目標偏移量
// The start bounds are the global visible rectangle of the thumbnail,
// 開始的邊界是小圖整體可見部分的范圍
// and the final bounds are the global visible rectangle of the container view.
// 結束的邊界是容器的邊界
// Also set the container view's offset as the origin for the bounds,
// since that's the origin for the positioning animation properties (X, Y).
thumbView.getGlobalVisibleRect(startBounds);
// 這里的id,container是整個布局最外層的容器
mContainView.getGlobalVisibleRect(finalBounds, globalOffset);
// 開始設置偏移量
startBounds.offset(-globalOffset.x, -globalOffset.y);
finalBounds.offset(-globalOffset.x, -globalOffset.y);
//設置縮放的比例和位置
set_Center_crop(finalBounds);
mExpandedView.setVisibility(View.VISIBLE);
// Set the pivot point for SCALE_X and SCALE_Y transformations to the
// top-left corner of
// the zoomed-in view (the default is the center of the view).
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(1);
animSet.play(ObjectAnimator.ofFloat(mExpandedView, "pivotX", 0f))
.with(ObjectAnimator.ofFloat(mExpandedView, "pivotY", 0f))
.with(ObjectAnimator.ofFloat(mExpandedView, "alpha", 1.0f));
animSet.start();
startZoomAnim(mExpandedView, startBounds, finalBounds, startScale);
// Upon clicking the zoomed-in image, it should zoom back down to the
// original bounds and show the thumbnail instead of the expanded image.
startScaleFinal = startScale;
}
/**
* 通過結束的邊界計算開始拉伸的比例
*
* Adjust the start bounds to be the same aspect ratio as the final bounds
* using the "center crop" technique. 通過 center
* crop算法來調整開始邊界,讓它和的結束邊界保持同一個縱橫比例,也就是長寬比 This prevents undesirable
* stretching during the animation.//在動畫執行時保證不讓圖片拉伸 Also calculate the start
* scaling factor (the end scaling factor is always 1.0).
* 我們也需要計算開始的比率因子,結束比例一直是1.0.因為是將圖片從小放到自己的大小。
*/
private void set_Center_crop(Rect finalBounds) {
if ((float) finalBounds.width() / finalBounds.height() > (float)
startBounds.width() / startBounds.height()) {
// Extend start bounds horizontally
startScale = (float) startBounds.height() / finalBounds.height();
float startWidth = startScale * finalBounds.width();
float deltaWidth = (startWidth - startBounds.width()) / 2;
startBounds.left -= deltaWidth;
startBounds.right += deltaWidth;
} else {
// Extend start bounds vertically
startScale = (float) startBounds.width() / finalBounds.width();
float startHeight = startScale * finalBounds.height();
float deltaHeight = (startHeight - startBounds.height()) / 2;
startBounds.top -= deltaHeight;
startBounds.bottom += deltaHeight;
}
}
/**
* @param v 執行動畫的view
* @param startBounds 開始的邊界
* @param finalBounds 結束時的邊界
* @param startScale 開始的拉伸比率
*/
public void startZoomAnim(View v, Rect startBounds, Rect finalBounds, float startScale) {
// Construct and run the parallel animation of the four translation and
// scale properties (X, Y, SCALE_X, and SCALE_Y).
AnimatorSet set = new AnimatorSet();
set.play(
ObjectAnimator.ofFloat(v, "x", startBounds.left, finalBounds.left))
.with(ObjectAnimator.ofFloat(v, "y", startBounds.top, finalBounds.top))
.with(ObjectAnimator.ofFloat(v, "scaleX", startScale, 1f))
.with(ObjectAnimator.ofFloat(v, "scaleY", startScale, 1f));
set.setDuration(mAnimationDuration);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCurrentAnimator = null;
if (listener != null) {
listener.onExpanded();
}
}
@Override
public void onAnimationCancel(Animator animation) {
mCurrentAnimator = null;
if (listener != null) {
listener.onExpanded();
}
}
});
set.start();
mCurrentAnimator = set;
}
/**
* 在GridView中,使用getChildAt(index)的取值,只能是當前可見區域(列表可滾動)的子項!
* 因為子項會進行復用。這里強制轉換了下,變成了GridView,實際使用中需要進行修改
* 【參考】
* http://xie2010.blog.163.com/blog/static/211317365201402395944633/
* http://blog.csdn.net/you_and_me12/article/details/7271006
*
* @param position
* @return 判斷這個position的view是否現在顯示在屏幕上,如果沒有顯示就返回false
*/
public boolean getScaleFinalBounds(int position) {
//得到顯示區域中第一個子視圖的序號
int firstPosition = ((AdapterView<?>)mThumbViewParent).getFirstVisiblePosition();
View childView = mThumbViewParent.getChildAt(position - firstPosition);
startBounds = new Rect();
final Rect finalBounds = new Rect();
final Point globalOffset = new Point();
try {
//通過這個計算startBounds,得到當前view的位置,從而設定偏移值
childView.getGlobalVisibleRect(startBounds);
} catch (Exception e) {
return false;
}
mContainView.findViewById(R.id.container).getGlobalVisibleRect(finalBounds, globalOffset);
startBounds.offset(-globalOffset.x, -globalOffset.y);
finalBounds.offset(-globalOffset.x, -globalOffset.y);
//設置比率
set_Center_crop(finalBounds);
startScaleFinal = startScale;
return true;
}
/**
* 根據position執行動畫,如果這個圖片在當前屏幕顯示范圍內,那就執行縮小。否則直接漸變
* @param position
*/
public void closeZoomAnim(int position) {
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
// Animate the four positioning/sizing properties in parallel,back to their original values.
AnimatorSet set = new AnimatorSet();
/**
* 因為展開圖可能是在viewpager中,所以現在顯示的圖片,或許並不是第一次打開的圖片,這里應該考慮兩點
* 1.改變圖片縮小后回到的位置
* 2.如果圖片縮小后回到的位置不在屏幕中,直接漸變消失
*/
boolean isInBound = getScaleFinalBounds(position);
if (isInBound) {
set.play(ObjectAnimator.ofFloat(mExpandedView, "x", startBounds.left))
.with(ObjectAnimator.ofFloat(mExpandedView, "y", startBounds.top))
.with(ObjectAnimator.ofFloat(mExpandedView, "scaleX", startScaleFinal))
.with(ObjectAnimator.ofFloat(mExpandedView, "scaleY", startScaleFinal));
} else {
// 如果當前顯示的圖片不在gridview當前顯示的圖片中,等於越界了。這時我們就不執行縮放操作,直接漸變消失即可。
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(mExpandedView, "alpha", 0.1f);
set.play(alphaAnimator);
}
set.setDuration(mAnimationDuration);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mExpandedView.clearAnimation();
mExpandedView.setVisibility(View.GONE);
mCurrentAnimator = null;
if (listener != null) {
listener.onThumbed();
}
}
@Override
public void onAnimationCancel(Animator animation) {
mExpandedView.clearAnimation();
mExpandedView.setVisibility(View.GONE);
mCurrentAnimator = null;
if (listener != null) {
listener.onThumbed();
}
}
});
set.start();
mCurrentAnimator = set;
}
private OnZoomListener listener;
public void setOnZoomListener(OnZoomListener l) {
listener = l;
}
public interface OnZoomListener {
public void onExpanded();//點擊后展示大圖成功后調用
public void onThumbed();//點擊后縮小回小圖時調用
}
}
終於搞定!現在我們就講解完了所有的代碼。應該十分詳細,如果大家有更好的點子也可以提供給我~

