AnimationsDemo中的ZoomActivity代码分析

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

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

下面是效果图:

Zoom

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

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

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

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

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

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

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

 

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

浅绿色部分就是整个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">



        <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;

            }

        });

    }

}

你可能感兴趣的:(animation)