前几天看到了有人在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();//点击后缩小回小图时调用
} }

终于搞定!现在我们就讲解完了所有的代码。应该十分详细,如果大家有更好的点子也可以提供给我~

源码下载:http://download.csdn.net/detail/shark0017/8155169

Android 实现类似于QQ空间相册的点击图片放大,再点后缩小回原来位置的更多相关文章

  1. 实现类似于QQ空间相册的点击图片放大,再点后缩小回原来位置

    前几天看到了有人在android5.0上实现了如下图一样的效果,我自己就去搜了下.参考了国外一篇文章和国内的一篇文章,最终实现了想要的效果.具体参考的网址我已经贴到文章末尾,大家可以去英文的那个网站看 ...

  2. [WPF源代码]QQ空间相册下载工具

    放一个WPF源代码,源代码地址 http://download.csdn.net/detail/witch_soya/6195987 代码没多少技术含量,就是用WPF做的一个QQ空间相册下载工具,效果 ...

  3. 类似于qq空间类型的评论和回复

    最近学习thinkphp,做了一个博客系统,其中感觉实现一个类似于qq空间的评论和回复功能比较复杂,所以把这次的经历记录下来,与大家共勉,具体的方法就不说了,在这里分享一下思路. 目标就是这种,关键是 ...

  4. 如何破解QQ空间相册密码访问权限2019方法

    今天小编给大家介绍一下最新的QQ空间相册破解方法,是2019年最新方法,本方法来自互联网,下面开始方法教程 教程之前我们需要下载软件,地址我发在下方 软件切图 1.首先我们打开软件,然后在“操作界面” ...

  5. JS时间轴效果(类似于qq空间时间轴效果)

    在上一家公司写了一个时间轴效果,今天整理了下,感觉有必要写一篇博客出来 给大家分享分享 当然代码还有很多不足的地方,希望大家多指点指点下,此效果类似于QQ空间或者人人网空间时间轴效果,当时也是为了需求 ...

  6. qq空间相册下载

    qq空间相册下载 描述 目前功能只可以下载 单个相册 程序基本是3个独立分开的部分. 解析(某一用户)所有相册 解析(单个)相册所有图片地址并写文件 根据文件下载图片 目的 只要有权限可以访问到的相册 ...

  7. QQ空间相册展示特效

    <!doctype html> <html lang="en"> <head> <title>QQ空间相册展示特效<title ...

  8. python+selenium+requests爬取qq空间相册时遇到的问题及解决思路

    最近研究了下用python爬取qq空间相册的问题,遇到的问题及解决思路如下: 1.qq空间相册的访问需要qq登录并且需是好友,requests模块模拟qq登录略显麻烦,所以采用selenium的dri ...

  9. Python_小林的爬取QQ空间相册图片链接程序

    前言 昨天看见某人的空间有上传了XXXX个头像,然后我就想着下载回来[所以本质上这是一个头像下载程序],但是一个个另存为太浪费时间了,上网搜索有没有现成的工具,居然要注册码,还卖45一套.你们的良心也 ...

随机推荐

  1. 在WinDBG中查看调用栈的命令

    命令 ========== k k命令显示的是一定数量的栈帧, 其中帧的数量是由.kframes命令来控制的, 默认值是256. kp 5 显示调用栈中前5个函数以及他们的参数. kb 5 显示调用栈 ...

  2. bzoj 4711 小奇挖矿 ——“承诺”类树形dp

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4711 对“承诺”有了更深的了解. 向外和向内要区分,所以 f [ i ][ j ] 表示根向 ...

  3. shell函数(调用、返回值,返回值获取)

    Shell函数返回值,常用的两种方式:return,echo 1) return 语句shell函数的返回值,可以和其他语言的返回值一样,通过return语句返回.示例1: [devadmin@swa ...

  4. CodeForces 1110H. Modest Substrings

    题目简述:给定$1 \leq l \leq r \leq 10^{800}$,求一个长度为$n \leq 2000$的数字串$s$,其含有最多的[好]子串.一个串$s$是[好]的,如果将其看做数字时无 ...

  5. 【Linux学习】Linux文件系统5—查看文件内容命令

    Linux文件系统5-查看文件内容命令 cat: 由第一行开始显示文件内容 more: 一页一页地显示文件内容,空格键可以继续翻页显示下一页内容 less:与more类似,但是可以往前翻页 head: ...

  6. 每次打开office 2013都提示配置进度,必须得等他下完然后重启,重启完了在打开,还是提示配置进度,怎么解决

    方法一: 我用下面的方法完美解决.在控制面板中1.删除旧版本2010 或者20032.删除激活程序3.重装2013激活程序done 方法二: 我把WPS卸载了就好了. 方法三: 我是按这个方法解决的, ...

  7. 718. Maximum Length of Repeated Subarray

    Given two integer arrays A and B, return the maximum length of an subarray that appears in both arra ...

  8. hrbust1444 逃脱 【BFS】

    Description  这是mengxiang000和Tabris来到幼儿园的第四天,幼儿园老师在值班的时候突然发现幼儿园某处发生火灾,而且火势蔓延极快,老师在第一时间就发出了警报,位于幼儿园某处的 ...

  9. 阿里云物联网 .NET Core 客户端 | CZGL.AliIoTClient:5. 设置设备属性

    文档目录: 说明 1. 连接阿里云物联网 2. IoT 客户端 3. 订阅Topic与响应Topic 4. 设备上报属性 4.1 上报位置信息 5. 设置设备属性 6. 设备事件上报 7. 服务调用 ...

  10. 【BZOJ1174】: [Balkan2007]Toponyms

    →原题← ↑这样子我就不复制题面啦~ 就是一题很裸的字典树而已,不过空间卡的很死,直接开个数组 tr[N][52] 什么之类的一定会RE的(惨痛的教训) 当字典树空间不够而时间限制又比较宽松时,我们可 ...