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 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后 ...
随机推荐
- list集合的排序Comparator和Collections.sort
一个例子 package sortt; import java.util.ArrayList; import java.util.Collections; import java.util.Compa ...
- setOnLongClickListener中return值
今天在做一个按钮的长按事件,长按的时候弹出一个Dialog弹出框,点击则是进入到下一个界面. 在我调试的时候,发现长按确实弹出了一个Dialog,但是同事他还跳转到下一个界面了. 这么说,就是在我长按 ...
- 基于显卡的光栅化渲染器Gaius计划
决定实现一个基于显卡的光栅化渲染器,能将一些基于显卡的新算法融入其中.
- git 远程仓库-github
第1步:创建SSH Key.在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步.如果没有,打开Shell ...
- Orchard中如何配置远端发布
Orchard中默认安装是有Blog功能的.下面介绍如何配置Remote Blog Publishing功能,使用Windows Live Writer客户端发布博客. 一,开启Remote Blog ...
- Sqlserver通过链接服务器访问Oracle的那些事儿
前言: 1.不经历风雨,怎能见彩虹. 2.充分利用BaiDu.google等搜索引擎查找资料并整合分析! 3.世上无难事只怕有心人! 本文由来:笔者在研究SQLSERVER链接服务器到oracle并使 ...
- ORACLE 11g 数据库体系结构图
ORACLE 11g 的数据库体系结构图,非常全面.系统.高屋建瓴的整体介绍了ORACLE 11g 的数据库体系结构.如果能全面了解.清晰梳理.深入掌握这些知识点,相信对你了解学习.深入研究ORACL ...
- ORA-02292: integrity constraint (xxxx) violated - child record found
在更新表的主键字段或DELETE数据时,如果遇到ORA-02292: integrity constraint (xxxx) violated - child record found 这个是因为主外 ...
- SpringMVC 自定义一个拦截器
自定义一个拦截器方法,实现HandlerInterceptor方法 public class FirstInterceptor implements HandlerInterceptor{ /** * ...
- Hadoop日常维护系列——Hadoop添加删除节点
添加节点 1.修改host 和普通的datanode一样.添加namenode的ip 2.修改namenode的配置文件conf/slaves 添加新增节点的ip或host 3.在新节点的 ...