zoukankan      html  css  js  c++  java
  • AnimationsDemo中的ZoomActivity代码分析

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

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

    下面是效果图:

    Zoom

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

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

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

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

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

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

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

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

    Zoom2

    浅绿色部分就是整个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;
                }
            });
        }
    }
  • 相关阅读:
    The formatter threw an exception while trying to deserialize the message in WCF
    通过Web Deploy方式部署WCF
    The Managed Metadata Service or Connection is currently not available
    How to create Managed Metadata Column
    冒泡算法
    asp.net core 实战项目(一)——ef core的使用
    Vue学习笔记入门篇——安装及常用指令介绍
    Vue学习笔记入门篇——数据及DOM
    Vue学习笔记目录
    Chart.js在Laravel项目中的应用
  • 原文地址:https://www.cnblogs.com/ai-developers/p/4414955.html
Copyright © 2011-2022 走看看