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

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

下面是效果图:

毫无疑问这是一个组合动画,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; }

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

我们都知道绿色部分才是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;
}
});
}
}

AnimationsDemo中的ZoomActivity代码分析的更多相关文章

  1. 对Java tutorial-examples中hello2核心代码分析

    1.在hello2中有两个.java源文件分别是GreetingServlet.Java和ResponseServlet.jva文件主要对以下核心代码做主要分析. String username = ...

  2. iOS 第三方自定义Alertview项目MBProcessHud中的重要代码分析

    做ios,弹出一个自定义的alertview挺常见的.ios7以前,我们可以对系统的UIAlertView进行一点操作,实现一点简单的定制,但是ios7不再允许我们这样做了.因此,我们需要自己创建一个 ...

  3. Openstack中RabbitMQ RPC代码分析

    在Openstack中,RPC调用是通过RabbitMQ进行的. 任何一个RPC调用,都有Client/Server两部分,分别在rpcapi.py和manager.py中实现. 这里以nova-sc ...

  4. openCV中cvSnakeImage()函数代码分析

    /*M/////////////////////////////////////////////////////////////////////////////////////// // // IMP ...

  5. 微擎框架中receive.php代码分析

  6. Metalama简介3.自定义.NET项目中的代码分析

    本系列其它文章 使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题 Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架 Metalama简介2.利用Aspect在 ...

  7. 常用 Java 静态代码分析工具的分析与比较

    常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...

  8. [转载] 常用 Java 静态代码分析工具的分析与比较

    转载自http://www.oschina.net/question/129540_23043 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代 ...

  9. 【转载】常用 Java 静态代码分析工具的分析与比较

    摘自:http://www.oschina.net/question/129540_23043常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后 ...

随机推荐

  1. 谈谈iOS app的线上性能监测

    在移动端开发者中最重要的KPI应该是崩溃率.当崩溃率稳定下来后,工作的重心就应该转移到性能优化上.那么问题来了,如果你的项目也没有接入任何性能监测SDK,没有量化的指标来衡量,那你说你优化了性能领导信 ...

  2. 友盟(Swift)-集成、统计用户数量、具体页面访问数量、具体按钮点击数量

    什么是友盟.有什么用? 这些傻瓜问题这里就不解释了,可以自己百度去. 友盟提供的文档和demo都是oc的,这里用swift写了一个小demo,在此分享一下. 步骤1:友盟后台注册应用(iOS),拿到a ...

  3. Objective-C语法之KVO使用

    本文转自:http://blog.csdn.net/yuquan0821/article/details/6646400/ 一,概述 KVO,即:Key-Value Observing,它提供一种机制 ...

  4. IOS开发之Bug--View是懒加载导致出误以为是UI加载的bug

    虽然分类为bug,但也算的上是一个问题,一个很简单的问题.先来看看问题的重现,就写了简单的Demo验证效果: 问题:点击ViewController跳转到TwoViewController,发现会延迟 ...

  5. C# .net dotnet属性定义属性,以提供显示明称,默认值

    //使用显示名称初始化 System.ComponentModel.DisplayNameAttribute 类的新实例. displayName 显示名称 [DisplayName("we ...

  6. vagrant vbox上配置好开发环境缓存问题

    vagrant配置完成 设置好共享目录 搭建好nginx环境 访问 127.0.0.1:8080 一切正常  然后进入本的的开发目录修改测试文件保存后刷新页面 问题来了..........没变化  然 ...

  7. 非root用户的SSH免密登录

    在网上找到的教程一般是这样说的 cd ~/.ssh/ # 若没有该目录,请先执行一次ssh localhost ssh-keygen -t rsa # 会有提示,都按回车就可以 cat id_rsa. ...

  8. 【转】Java并发编程:volatile关键字解析

    转自:http://www.importnew.com/18126.html#comment-487304 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备 ...

  9. php应用jquery做ajax操作

    以下是全部代码: <html> <head> <title>jQuery Ajax 实例演示</title> </head> <scr ...

  10. 透明ActionBar

    代码方式:    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceSta ...