AnimationsDemo中的ZoomActivity代码分析
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代码分析的更多相关文章
- 对Java tutorial-examples中hello2核心代码分析
1.在hello2中有两个.java源文件分别是GreetingServlet.Java和ResponseServlet.jva文件主要对以下核心代码做主要分析. String username = ...
- iOS 第三方自定义Alertview项目MBProcessHud中的重要代码分析
做ios,弹出一个自定义的alertview挺常见的.ios7以前,我们可以对系统的UIAlertView进行一点操作,实现一点简单的定制,但是ios7不再允许我们这样做了.因此,我们需要自己创建一个 ...
- Openstack中RabbitMQ RPC代码分析
在Openstack中,RPC调用是通过RabbitMQ进行的. 任何一个RPC调用,都有Client/Server两部分,分别在rpcapi.py和manager.py中实现. 这里以nova-sc ...
- openCV中cvSnakeImage()函数代码分析
/*M/////////////////////////////////////////////////////////////////////////////////////// // // IMP ...
- 微擎框架中receive.php代码分析
- Metalama简介3.自定义.NET项目中的代码分析
本系列其它文章 使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题 Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架 Metalama简介2.利用Aspect在 ...
- 常用 Java 静态代码分析工具的分析与比较
常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...
- [转载] 常用 Java 静态代码分析工具的分析与比较
转载自http://www.oschina.net/question/129540_23043 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代 ...
- 【转载】常用 Java 静态代码分析工具的分析与比较
摘自:http://www.oschina.net/question/129540_23043常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后 ...
随机推荐
- 天津政府应急系统之GIS一张图(arcgis api for flex)讲解(四)地图导航控件模块
config.xml文件的配置如下: <widget left="10" top="50" config="widgets/Navigation ...
- MyEclipse 2016 CI 1支持远程WebSphere连接器
MyEclipse 2016 CI 1有很多Web开发者会喜欢的新功能,包括Live Preview,带有Map支持和hot-swap功能的JavaScript调试.另外还新增支持远程WebSpher ...
- Objective-C语法之KVO使用
本文转自:http://blog.csdn.net/yuquan0821/article/details/6646400/ 一,概述 KVO,即:Key-Value Observing,它提供一种机制 ...
- iOS开发之功能模块--高仿Boss直聘的常用语的开发
首先上Boss直聘的功能界面截图,至于交互请读者现在Boss直聘去交互体验: 本人的公司项目要高仿Boss直聘的IM常用语的交互功能,居然花费了我前后17个小时完成,这回自己测试了很多遍,代码 ...
- -bash: ulimit: pipe size: cannot modify limit: Invalid argument
从root账号切换到oracle账号时,出现了"-bash: ulimit: pipe size: cannot modify limit: Invalid argument"提示 ...
- EasyUI 中点击取消按钮关闭Dialog(对话框窗口)
<div id="dl1" class="easyui-dialog" title="窗口"style="width:400 ...
- Java读写文本文件操作
package com.test; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; ...
- Eclipse相关设置与优化
原文:http://chaoxz2005.blog.163.com/blog/static/15036542013411105519685/ 一般在不对eclipse进行相关设置的时候,使用eclip ...
- Visual Studio SetSite failed for package [JavaScriptWebExtensionsPackage] 错误解决方案一则
安装 AspNet5.ENU.RC1.exe Microsoft ASP.NET and Web Tools 2015 (RC) – Visual Studio 2015 打开VS后发生了错误 < ...
- 使用IntelliJ IDEA搭建多maven模块JAVA项目
一.新建项目和模块 步骤: 1. 新建一个项目,因为maven管理jar包非常方便,故此处建立一个maven项目:New Project->Maven->(Create from arch ...