你的位置:首页 > 操作系统

[操作系统]AnimationsDemo中的ZoomActivity代码分析


AnimationsDemo是android官网的一个动画使用示例。

ZoomActivity是demo中的图像缩放动画,因为这种效果比较常见,所以研究了一下代码。

下面是效果图:

Zoom

毫无疑问这是一个组合动画,translation和scale动画.实现这种动画的关键是如何确定动画的坐标和缩放比例

除了一些简单的数学计算外,该demo还利用了ImageView的fitCenter特性.稍后我们就可以看到.

在开始分析代码之前,先说一下程序的原理:

    1,点击缩略图的时候同时将缩略图隐藏。

    2,载入相应的大图,将大图缩小成缩略图的大小,并设置为Visible

    3,大图缩小后移动到原缩略图的位置,并把它覆盖

    4,被缩小的大图在该位置重新放大

 

为了更清楚的表达这个过程,我将程序改动一下再运行:

Zoom2

浅绿色部分就是整个ImageView的大小。明白这一点很重要。

 

原理明白了就可以开始分析代码,先来的是程序的布局:

<FrameLayout ="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">    <TextView      style="?android:textAppearanceSmall"      android:layout_width="wrap_content"      android:layout_height="wrap_content"      android:text="@string/message_zoom_touch_expand"/>    <LinearLayout      android:layout_width="wrap_content"      android:layout_height="wrap_content"      android:layout_marginTop="16dp"      android:orientation="horizontal">      <ImageView        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"/>      <ImageView        android:id="@+id/thumb_button_2"        android:layout_width="100dp"        android:layout_height="75dp"        android:src="@drawable/thumb2"        android:scaleType="centerCrop"        android:contentDescription="@string/description_image_2"/>    </LinearLayout>  </LinearLayout>  <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>

布局本身并没有什么值得讨论的地方,唯一需要注意的是布局中的三个ImageView对象。

两个用于放置缩略图,亦即是上图中的两个小图,最下面的ImageView就是我们主要操作的对象。

这样做的好处是可以节省程序的使用内存,防止OOM的发生。

 

大概了解一下布局后我们就可以来分析程序的逻辑,程序中所有的动画逻辑都在下面的函数中完成

private void zoomImageFromThumb(final View thumbView, int imageResId)

 

下面是函数的其中一段代码:

//用于计算translation动画开始的坐标    final Rect startBounds = new Rect();    final Rect finalBounds = new Rect();    final Point globalOffset = new Point();    //获取thumbView在屏幕中的偏移量    thumbView.getGlobalVisibleRect(startBounds);    //获取container在屏幕中的偏移量并将偏移量记录到globalOffset中    findViewById(R.id.container).getGlobalVisibleRect(finalBounds, globalOffset);    //将屏幕坐标减去ActionBar+StatusBar的高度    startBounds.offset(-globalOffset.x, -globalOffset.y);    finalBounds.offset(-globalOffset.x, -globalOffset.y);

 

对getGlobalVisibleRect函数不明白的可以参考下面的文章

GetGlobalVisibleRect和getLocalVisibleRect

上面这段代码的主要作用就是获取缩略图的坐标,因为动画就是从这个坐标开始进行。

startBounds和finalBounds调用offset方法的作用是将坐标转换为以Activity左上角为原点的坐标

 

坐标计算好后就开始计算缩放比率,下面是函数的另一段代码:

float startScale;    if ((float) finalBounds.width() / finalBounds.height()        > (float) startBounds.width() / startBounds.height())    {      // Extend start bounds horizontally      startScale = (float) startBounds.height() / finalBounds.height();      System.out.println("startScale1:"+startScale);      float startWidth = startScale * finalBounds.width();      float deltaWidth = (startWidth - startBounds.width()) / 2;      System.out.println("startWidth:"+startWidth);      System.out.println("deltaWidth:"+deltaWidth);      startBounds.left -= deltaWidth;      startBounds.right += deltaWidth;    } else    {      //计算缩放量比例      startScale = (float) startBounds.width() / finalBounds.width();      //计算expanded_image缩小后的大小      float startHeight = startScale * finalBounds.height();      //计算expanded_image上下空间的偏移距离      float deltaHeight = (startHeight - startBounds.height()) / 2;      //开始移动动画前的位置      startBounds.top -= deltaHeight;      startBounds.bottom += deltaHeight;    }

根据下面的图在来分析代码

Zoom3

我们都知道绿色部分才是ImageView的覆盖位置,所以位移开始的地方是绿色部分的左上角

startBounds和finalBounds的坐标实际上并不包含上下两个绿色矩形,因此我们要纠正之前获取的坐标

下面几行代码的作用就是用于纠正坐标和计算缩放比例

startScale = (float) startBounds.width() / finalBounds.width();      //计算expanded_image缩小后的大小      float startHeight = startScale * finalBounds.height();      //计算expanded_image上下空间的偏移距离      float deltaHeight = (startHeight - startBounds.height()) / 2;      //开始移动动画前的位置      startBounds.top -= deltaHeight;      startBounds.bottom += deltaHeight;

startHeight是缩放后整个绿色部分的高度,deltaHeight就是上下两个矩形的各自高度,它们的值相等。

startBounds.top减去deltaHeight的高度就可以将startBounds的坐标向上移动。因为原点在左上角,要向上移动就要用减号。

startBounds.bottom的原理相同。

 

准备工作都做好后,动画开始播放:

    expandedImageView.setVisibility(View.VISIBLE);    expandedImageView.setPivotX(0f);    expandedImageView.setPivotY(0f);    AnimatorSet set = new AnimatorSet();    set        .play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left,            finalBounds.left))        .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top,            finalBounds.top))        .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScale, 1f))        .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale, 1f));    set.setDuration(mShortAnimationDuration);    set.setInterpolator(new DecelerateInterpolator());    set.start();

正如我们前面说的需要将大图设置为显示:expandedImageView.setVisibility(View.VISIBLE);

下面的代码将中心点移动到expandedImageView的左上角

expandedImageView.setPivotX(0f); expandedImageView.setPivotY(0f);

剩下的代码基本上就是如何使用Property Animation,不熟悉的可参考使用属性动画 — Property Animation

 

Demo的完整代码:

package com.example.android.animationsdemo;import android.animation.Animator;import android.animation.AnimatorListenerAdapter;import android.animation.AnimatorSet;import android.animation.ObjectAnimator;import android.content.Intent;import android.graphics.Point;import android.graphics.Rect;import android.os.Bundle;import android.support.v4.app.FragmentActivity;import android.support.v4.app.NavUtils;import android.view.MenuItem;import android.view.View;import android.view.animation.DecelerateInterpolator;import android.widget.ImageView;public class ZoomActivity extends FragmentActivity{  private Animator mCurrentAnimator;  private int mShortAnimationDuration;  @Override  protected void onCreate(Bundle savedInstanceState)  {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_zoom);    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);      }    });    final View thumb2View = findViewById(R.id.thumb_button_2);    thumb2View.setOnClickListener(new View.OnClickListener()    {      @Override      public void onClick(View view)      {        zoomImageFromThumb(thumb2View, R.drawable.image2);      }    });    // Retrieve and cache the system's default "short" animation time.    mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);  }  @Override  public boolean onOptionsItemSelected(MenuItem item)  {    switch (item.getItemId())    {      case android.R.id.home:        // Navigate "up" the demo structure to the launchpad activity.        // See http://developer.android.com/design/patterns/navigation.html for more.        NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));        return true;    }    return super.onOptionsItemSelected(item);  }  private void zoomImageFromThumb(final View thumbView, int imageResId)  {    // If there's an animation in progress, cancel it immediately and proceed with this one.    if (mCurrentAnimator != null)    {      mCurrentAnimator.cancel();    }    // Load the high-resolution "zoomed-in" image.    final ImageView expandedImageView = (ImageView) findViewById(R.id.expanded_image);    expandedImageView.setImageResource(imageResId);    //用于计算translation动画开始的坐标    final Rect startBounds = new Rect();    final Rect finalBounds = new Rect();    final Point globalOffset = new Point();    //获取thumbView在屏幕中的偏移量    thumbView.getGlobalVisibleRect(startBounds);    //获取container在屏幕中的偏移量并将偏移量记录到globalOffset中    findViewById(R.id.container).getGlobalVisibleRect(finalBounds, globalOffset);    //将屏幕坐标减去ActionBar+StatusBar的高度    startBounds.offset(-globalOffset.x, -globalOffset.y);    finalBounds.offset(-globalOffset.x, -globalOffset.y);    float startScale;    if ((float) finalBounds.width() / finalBounds.height()        > (float) startBounds.width() / startBounds.height())    {      // Extend start bounds horizontally      startScale = (float) startBounds.height() / finalBounds.height();      System.out.println("startScale1:"+startScale);      float startWidth = startScale * finalBounds.width();      float deltaWidth = (startWidth - startBounds.width()) / 2;      System.out.println("startWidth:"+startWidth);      System.out.println("deltaWidth:"+deltaWidth);      startBounds.left -= deltaWidth;      startBounds.right += deltaWidth;    } else    {      //计算缩放量比例      startScale = (float) startBounds.width() / finalBounds.width();      //计算expanded_image缩小后的大小      float startHeight = startScale * finalBounds.height();      //计算expanded_image上下空间的偏移距离      float deltaHeight = (startHeight - startBounds.height()) / 2;      //开始移动动画前的位置      startBounds.top -= deltaHeight;      startBounds.bottom += deltaHeight;    }    // Hide the thumbnail and show the zoomed-in view. When the animation begins,    // it will position the zoomed-in view in the place of the thumbnail.    thumbView.setAlpha(0f);    expandedImageView.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).    expandedImageView.setPivotX(0f);    expandedImageView.setPivotY(0f);    // 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(expandedImageView, View.X, startBounds.left,            finalBounds.left))        .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top,            finalBounds.top))        .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, 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;    // Upon clicking the zoomed-in image, it should zoom back down to the original bounds    // and show the thumbnail instead of the expanded image.    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;      }    });  }}