本节课讲述如何实现触屏缩放动画,这种动画很有用处,像在图片集中将一个缩略图放大成一个全尺寸填满屏幕的图片。
创建View
创建一个layout布局文件,其中包含你要缩放的内容的小尺寸版本和大尺寸版本。下面的历次创建了一个ImageButton以用于可点击的缩略图和一个ImageView以用于显示放大了的图片:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <ImageButton android:id="@+id/thumb_button_1" android:layout_width="100dp" android:layout_height="75dp" android:layout_marginRight="1dp" android:src="@drawable/thumb1" android:scaleType="centerCrop" android:contentDescription="@string/description_image_1" /> </LinearLayout> <!-- 该ImageView在初始化后会被隐藏,其会保存前面图片的缩小版和扩大版。该ImageView会占满整个屏幕。为了实现缩放动画,该view的边界会从缩略图按钮的边界变换到最终的布局边界。 --> <ImageView android:id="@+id/expanded_image" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" android:contentDescription="@string/description_zoom_touch_close" /> </FrameLayout>
建立缩放动画
一旦应用了layout布局,就建立触发缩放动画的事件处理器。下面的例子把View.OnClickListener监听器添加到ImageButton来在用户点击图片按钮时执行缩放动画。
public class ZoomActivity extends FragmentActivity { // 保存一个对当前animator的引用,确保在中途它能被删掉 private Animator mCurrentAnimator; // 系统”短“动画持续时间,以毫秒为单位。该持续时间是短暂动画或频繁发生动画的理想持续时间。 private int mShortAnimationDuration; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_zoom); // 绑定缩略图view的点击事件 final View thumb1View = findViewById(R.id.thumb_button_1); thumb1View.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { zoomImageFromThumb(thumb1View, R.drawable.image1); } }); // 重新找回并缓存系统的默认”短“动画持续时间 mShortAnimationDuration = getResources().getInteger( android.R.integer.config_shortAnimTime); } ... }
缩放View
现在你需要在适当的时点将正常尺寸的view变为缩放的view。通常,你需要从正常尺寸的view边界变换到大尺寸view的边界。下面的方法讲解了如何实现一个从缩略图放大到大尺寸view的几个步骤:
- 把高解析度大图赋值到隐藏状态的”放大“ImageView。下面的例子中直接在UI线程加载了大图,实际上你应该在另一个独立的线程去加载图片以防阻碍了UI主线程然后在UI线程设置位图,理想状态下位图不应该大于图片尺寸。
- 计算ImageView的开始边界和结束边界。
- Animate each of the four positioning and sizing properties
,X
, (Y
SCALE_X
, and
) simultaneously, from the starting bounds to the ending bounds. These four animations are added to anSCALE_Y
AnimatorSet
so that they can be started at the same time. - 当用户点击已经放大了的图片时,以一个类似于放大动画但逆向的动画来返回缩小view。可以通过向ImageView上添加View.OnClickListener监听器来实现。当点击时,ImageView缩小回缩略图尺寸然后设置其可见性visibility为GONE以隐藏自己。
private void zoomImageFromThumb(final View thumbView, int imageResId) { // 若已经有animation在进行中,立即删除该animation,然后推进新的animation if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); } // 加载高分辨率的放大图片 final ImageView expandedImageView = (ImageView) findViewById( R.id.expanded_image); expandedImageView.setImageResource(imageResId); // 计算放大图片动画的开始边界和终止边界。该步骤设计较多的数学计算 final Rect startBounds = new Rect(); final Rect finalBounds = new Rect(); final Point globalOffset = new Point(); // 开始边界即为缩略图的全局可视正方形,终止边界即为container view的全局可视正方形。The start bounds are the global visible rectangle of the thumbnail, // 同时设置container viewr的偏移量为边界的起点,因为这是positioning animation 参数(X,Y)的起点。 thumbView.getGlobalVisibleRect(startBounds); findViewById(R.id.container) .getGlobalVisibleRect(finalBounds, globalOffset); startBounds.offset(-globalOffset.x, -globalOffset.y); finalBounds.offset(-globalOffset.x, -globalOffset.y); // 用”center crop”技术调整开始边界与终止边界的宽高比一样。这样可以防止动画执行期间的不佳的拉伸。同时计算开始的缩放比例因子(终止的比例因子总是1.0) float startScale; if ((float) finalBounds.width() / finalBounds.height() > (float) startBounds.width() / startBounds.height()) { // 水平方向扩展开始边界 startScale = (float) startBounds.height() / finalBounds.height(); float startWidth = startScale * finalBounds.width(); float deltaWidth = (startWidth - startBounds.width()) / 2; startBounds.left -= deltaWidth; startBounds.right += deltaWidth; } else { // 垂直方向扩展开始边界 startScale = (float) startBounds.width() / finalBounds.width(); float startHeight = startScale * finalBounds.height(); float deltaHeight = (startHeight - startBounds.height()) / 2; startBounds.top -= deltaHeight; startBounds.bottom += deltaHeight; } // 隐藏缩略图,显示放大view。当动画开始时,将把放大view安放在缩略图的位置。 thumbView.setAlpha(0f); expandedImageView.setVisibility(View.VISIBLE); // 设置轴心点,为了SCALE_X和SCALE_Y转换到放大view的左上角(默认轴心点是view的中心) expandedImageView.setPivotX(0f); expandedImageView.setPivotY(0f); // 构建并运行平行的四个转换动画,即为依次对 (X, Y, SCALE_X, and SCALE_Y)四个属性执行转换.set中存放四个animator。 AnimatorSet set = new AnimatorSet(); set .play(ObjectAnimator.ofFloat(expandedImageView, View.X, //View的X属性从startBounds.left变到finalBounds的left startBounds.left, finalBounds.left)) .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top, finalBounds.top)) .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, //View的SCALE_X和SCALE_Y(X轴和Y轴的放大倍数,0.0代表放大为0,1.0代表正常) startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale, 1f)); set.setDuration(mShortAnimationDuration); //设置动画持续时间 set.setInterpolator(new DecelerateInterpolator()); //设置动画播放为减速器播放 set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mCurrentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { mCurrentAnimator = null; } }); set.start();//开始执行预定的动画 mCurrentAnimator = set; //做好缓存 // 逆向的操作,从大图到小图,先设定AnimatorSet,然后按设定执行AnimatorSet,然后隐藏大图,显示小图,不详细讲了。 // final float startScaleFinal = startScale; expandedImageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); } // Animate the four positioning/sizing properties in parallel, // back to their original values. AnimatorSet set = new AnimatorSet(); set.play(ObjectAnimator .ofFloat(expandedImageView, View.X, startBounds.left)) .with(ObjectAnimator .ofFloat(expandedImageView, View.Y,startBounds.top)) .with(ObjectAnimator .ofFloat(expandedImageView, View.SCALE_X, startScaleFinal)) .with(ObjectAnimator .ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal)); set.setDuration(mShortAnimationDuration); set.setInterpolator(new DecelerateInterpolator()); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { thumbView.setAlpha(1f); expandedImageView.setVisibility(View.GONE); mCurrentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { thumbView.setAlpha(1f); expandedImageView.setVisibility(View.GONE); mCurrentAnimator = null; } }); set.start(); mCurrentAnimator = set; } }); }