Android之动画精讲一:从setTranslationX谈属性动画和view动画的区别

转载:http://blog.csdn.net/yanzi1225627/article/details/47850471

最近又用到了动画,决定把几次项目里用到的动画走过的弯路总结一下,顺便梳理下android的动画体系。众所周知,android动画分三类:一是View 动画,又叫Tween动画,二是frame 动画(帧动画),又叫drawable 动画,三是属性动画,即property animation. 

    转载地址:http://blog.csdn.net/yanzi1225627/article/details/47850471   

    View动画,根据作用又分为缩放动画ScaleAnimation/移位动画TranslateAnimation / 透明度动画AlphaAnimation / 旋转动画RotateAnimation,这四个动画都继承android.view.animation下的Animation类。继承Animation的除了这四个类外,还有AnimationSet,关系图如下所示: 


 帧动画 对应AnimationDrawable类,继承自DrawableContainer,通过加载多个Drawable来一帧一帧播放达到动画效果。尽管很多人觉得这个不值一提,但是某些动画效果,如显示个小羊吃草还必须得用这个动画。 
       接下来进入正题谈属性动画,该动画从android3.0引入,API11引入,是为了弥补view动画的不足。正式项目里用的话为了兼容android2.3可以用NineOldAndroids,直接将生成的jar包放进去就ok了。 
       属性动画都在android.animation包下,基类是Animator类,子类为ValueAnimator和AnimatorSet(作用同view动画的AnimationSet相同),ValueAnimator的子类有ObjectAnimator和TimeAnimator,一般我们用属性动画ObjectAnimator就ok了。不妨简单对比下和view动画架构上的异同: 
       View动画,包名android.view.animation,基类为Animation,核心子类为TranslateAnimation,ScaleAnimation,AlphaAnimation,RotateAnimation及AnimationSet。 
       Property动画,包名android.animation,基类为Animator,核心子类为AnimatorSet,ValueAnimator,ObjectAnimator,TimeAnimator。 
       在详细对比属性动画和view动画前,先介绍个函数setTranslationX和setTranslationY,api版本为11,是设置view相对原始位置的偏移量,正式项目用的话考虑到兼容api11之前的用nineoldandroids里的ViewHelper即可。

public void setTranslationX (float translationX)Added in API level 11
Sets the horizontal location of this view relative to its left position. This effectively positions the object post-layout, in addition to wherever the object's layout placed it.Related XML Attributes
android:translationX
Parameters
translationX    The horizontal position of this view relative to its left position, in pixels.
上面是api介绍,即相对left position的偏移,所谓left position也即getLeft(),同时可以在xml里直接用android:translationX进行设置。关于view的位置,我们最常用的莫过于android:layoutMargin这一套,用来设置相对父布局的偏移,在java代码里可以通过新建或更新view的LayoutParams进行修改,如下所示:

LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)text.getLayoutParams();params.leftMargin = 0;params.rightMargin = 0;params.setMargins(0, 0, 0, 0);text.setLayoutParams(params);
 之所以说有时需要新建Params而有时候需要更新,是因为有时候从view取来的params是空的,这个日后开篇文章专门谈这个问题。总之,通过view的LayoutParams设置margin最终影响了view的位置,这个同时会改变view的getLeft/getRight等变量。但通过setTranslationX改变view的位置,是不改变view的LayoutParams的,也即不改变getLeft等view的信息。 但他确实改变了view的位置,这一点可以通过获取其在window或screen的坐标,或通过getLocationInWindow及如下所示的api等到view的精确位置:

text.getLocationInWindow(pos);text.getLocationOnScreen(pos);text.getLocalVisibleRect()text.getGlobalVisibleRect()

总结: 
1,setTranslationX改变了view的位置,但没有改变view的LayoutParams里的margin属性; 
2,它改变的是android:translationX 属性,也即这个参数级别是和margin平行的。

       下面来看这个例子,通过点击按键让一个view从最左边移动到屏幕的最右边,分别用view的TranslateAnimation和属性动画来实现。 
布局代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:background="@android:color/holo_green_light"android:orientation="vertical"><TextViewandroid:id="@+id/text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/hello_world" /></LinearLayout><Buttonandroid:id="@+id/btn_start_anim"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="属性动画" /><Buttonandroid:id="@+id/btn_start_anim2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_toLeftOf="@id/btn_start_anim"android:layout_centerVertical="true"android:layout_marginRight="40dp"android:text="复位" /><Buttonandroid:id="@+id/btn_reset_pos"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_toRightOf="@id/btn_start_anim"android:layout_centerVertical="true"android:layout_marginLeft="40dp"android:text="复位" /></RelativeLayout>
Java代码: 
MainActivity.java

package com.example.yanzi.myapplication;import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.view.ViewHelper;
import com.yanzi.util.UiUtil;public class MainActivity extends ActionBarActivity implements View.OnClickListener{private static final String TAG = "YanZi";Button btn_start_anim;Button btn_reset_pos;Button btn_start_anim2;TextView text;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initData();initUI();}private void initData(){UiUtil.initialize(getApplicationContext());}private void initUI(){btn_start_anim = (Button)findViewById(R.id.btn_start_anim);btn_start_anim.setOnClickListener(this);btn_start_anim2 = (Button)findViewById(R.id.btn_start_anim2);btn_start_anim2.setOnClickListener(this);btn_reset_pos = (Button)findViewById(R.id.btn_reset_pos);btn_reset_pos.setOnClickListener(this);text = (TextView)findViewById(R.id.text);text.setOnClickListener(this);LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)text.getLayoutParams();params.leftMargin = 0;params.rightMargin = 0;params.setMargins(0, 0, 0, 0);text.setLayoutParams(params);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.btn_start_anim:playAnim1();break;case R.id.btn_start_anim2:playAnim2();break;case R.id.btn_reset_pos:resetPos();break;case R.id.text:printParams();break;default:break;}}public void printParams(){LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)text.getLayoutParams();if(params != null){String s =  "leftMargin = " + params.leftMargin + " rightMargin = " + params.rightMargin+ " getLeft = " + text.getLeft() + " getRight = " + text.getRight() + " getWidth = " + text.getWidth();Log.i(TAG, s);int[] pos = new int[2];text.getLocationInWindow(pos);Log.i(TAG, "location, x = " + pos[0] + " y = " + pos[1]);Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show();}}private void playAnim1(){int w = text.getWidth();int screenW = UiUtil.getScreenWidth();int transX = screenW - w;ObjectAnimator transAnim = ObjectAnimator.ofFloat(text, "translationX", 0, transX);transAnim.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animator) {}@Overridepublic void onAnimationEnd(Animator animator) {}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic void onAnimationRepeat(Animator animator) {}});transAnim.setDuration(300);transAnim.start();;}private void playAnim2(){int w = text.getWidth();int screenW = UiUtil.getScreenWidth();int transX = screenW - w;TranslateAnimation transAnim = new TranslateAnimation(0, transX, 0, 0);transAnim.setDuration(300);text.setAnimation(transAnim);transAnim.start();}private void resetPos(){ViewHelper.setTranslationX(text, 0);}
}
用到了一个辅助类获得屏幕的宽高和dip转px:

package com.yanzi.util;import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ListAdapter;
import android.widget.ListView;public class UiUtil {private static final String TAG =  "YanZi_UiUtil";private static int screenWidth = 0;private static int screenHeight = 0;private static float screenDensity = 0;private static int densityDpi = 0;private static int statusBarHeight = 0;public static void initialize(Context context){if (context == null)return;DisplayMetrics metrics = new DisplayMetrics();WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);wm.getDefaultDisplay().getMetrics(metrics);screenWidth = metrics.widthPixels;     // 屏幕宽度screenHeight = metrics.heightPixels;   // 屏幕高度screenDensity = metrics.density;      // 0.75 / 1.0 / 1.5 / 2.0 / 3.0densityDpi = metrics.densityDpi;  //120 160 240 320 480Log.i(TAG, "screenDensity = " + screenDensity + " densityDpi = " + densityDpi);}public static int dip2px(float dipValue){return (int)(dipValue * screenDensity + 0.5f);}public static int px2dip(float pxValue){return (int)(pxValue / screenDensity + 0.5f);}public static int getScreenWidth() {return screenWidth;}public static int getScreenHeight() {return screenHeight;}}




大概说下里面核心的几个函数: 
1,使用view动画TranslateAnimation:

private void playAnim2(){int w = text.getWidth();int screenW = UiUtil.getScreenWidth();int transX = screenW - w;TranslateAnimation transAnim = new TranslateAnimation(0, transX, 0, 0);transAnim.setDuration(300);text.startAnimation(transAnim);}
2,使用属性动画移位:

private void playAnim1(){int w = text.getWidth();int screenW = UiUtil.getScreenWidth();int transX = screenW - w;ObjectAnimator transAnim = ObjectAnimator.ofFloat(text, "translationX", 0, transX);transAnim.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animator) {}@Overridepublic void onAnimationEnd(Animator animator) {}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic void onAnimationRepeat(Animator animator) {}});transAnim.setDuration(300);transAnim.start();;}
3,点击text打印它的坐标:

 public void printParams(){LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)text.getLayoutParams();if(params != null){String s =  "leftMargin = " + params.leftMargin + " rightMargin = " + params.rightMargin+ " getLeft = " + text.getLeft() + " getRight = " + text.getRight() + " getWidth = " + text.getWidth();Log.i(TAG, s);int[] pos = new int[2];text.getLocationInWindow(pos);Log.i(TAG, "location, x = " + pos[0] + " y = " + pos[1]);Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show();}}

4,使用属性动画后如果想复位:

private void resetPos(){ViewHelper.setTranslationX(text, 0);}

直接将translationX设为0即可,而不是上次偏移量的相反数。正因为如此,重复点击属性动画,看到view每次都从最左边到最右边,并最终停在最右边。因为属性动画的执行过程就是setTranslationX(0), 1, 2, 3, 4,……..N的过程,所以才会有看到的效果。

       另外,可以看到使用view的TranslateAnimation动画播放完毕后,view瞬间又回到了原点;而使用属性动画移位后view位置确实发生了改变。但LayoutParams里的margin和getLeft信息并未改变。有没有办法让view的TranslateAnimation播放完毕后,停在那个地方呢?

       肯定是有,加上这句话:transAnim.setFillAfter(true);之后运行发现view确实停在了屏幕的右侧,但是点击右侧的textview并没有触发打印参数的函数,而点击textview的初始位置才触发。所以它并没有改变view的位置,仅仅是绘制在了屏幕的右侧。因此,如果使用view动画但又想真正改变view位置需要如下代码:

private void playAnim2(){int w = text.getWidth();int screenW = UiUtil.getScreenWidth();int transX = screenW - w;TranslateAnimation transAnim = new TranslateAnimation(0, transX, 0, 0);transAnim.setDuration(300);
//        transAnim.setFillAfter(true);transAnim.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {updateParams();}@Overridepublic void onAnimationRepeat(Animation animation) {}});text.startAnimation(transAnim);}private void updateParams(){int w = text.getWidth();int screenW = UiUtil.getScreenWidth();LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) text.getLayoutParams();params.leftMargin = screenW - w;text.setLayoutParams(params);}
 即使用LayoutParams在动画结束后设置下就ok了,这样也能达到属性动画改变view的位置的效果。view 动画+updateParams 约等于property动画效果。 
       但是切忌,使用view动画+updateParams策略时,务必注意不要使用transAnim.setFillAfter(true);这句话,先看看setFillAfter的api:

If fillAfter is true, the transformation that this animation performed will persist when it is finished. Defaults to false if not set. Note that this applies to individual animations and when using an AnimationSet to chain animations.Related XML Attributes
android:fillAfter
Parameters
fillAfter   true if the animation should apply its transformation after it ends

如果为true,动画结束后关于view的变换会一直存在。在view动画+updateParams+transAnim.setFillAfter(true)这种策略下,view最终的绘制位置等于将view先updateParams后在新的位置基础上,再进行动画移位,一般情况下这并不是我们想要的! 
       基本上可以这么说,如果需要view位置真正改变setFillAfter一定不要设! 
       时间原因,很多东西只有下次再写了,关于属性动画和view动画详细对比可以参考官方文档里How Property Animation Differs from View Animation这一段,见后文。

       总之,要知其然并知其所以然,不要一味否定view动画而肯定属性动画。很多多个界面间的复杂效果非view动画不可,用属性动画只能掉坑里,我是两种坑都掉过。如果想改变动画后view的属性,如位置,可以用属性动画也可以用view动画+updateParams,当然前者更省事。在有些情况下,仅仅是想得到动画的呈现,动画结束后的位置就是view的初始位置,如view从一个地方飞过来,动画结束时view的位置就是view的位置时,此时view动画最合适!

he view animation system provides the capability to only animate View objects, so if you wanted to animate non-View objects, you have to implement your own code to do so. The view animation system is also constrained in the fact that it only exposes a few aspects of a View object to animate, such as the scaling and rotation of a View but not the background color, for instance.Another disadvantage of the view animation system is that it only modified where the View was drawn, and not the actual View itself. For instance, if you animated a button to move across the screen, the button draws correctly, but the actual location where you can click the button does not change, so you have to implement your own logic to handle this.With the property animation system, these constraints are completely removed, and you can animate any property of any object (Views and non-Views) and the object itself is actually modified. The property animation system is also more robust in the way it carries out animation. At a high level, you assign animators to the properties that you want to animate, such as color, position, or size and can define aspects of the animation such as interpolation and synchronization of multiple animators.The view animation system, however, takes less time to setup and requires less code to write. If view animation accomplishes everything that you need to do, or if your existing code already works the way you want, there is no need to use the property animation system. It also might make sense to use both animation systems for different situations if the use case arises.

文中测试代码下载: http://download.csdn.net/detail/yanzi1225627/9034125  
--—–--本文系原创,转载注明作者yanzi1225627












本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294799.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

现在的娃娃有多智能?

1 我的12月&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 抱歉啊女儿...&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 一看就很厉害的名字▼4 突然的潮流&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼5 上下班的表情管理&#xff08;素…

Nats的消息通信模型

版权声明&#xff1a;本文为博主chszs的原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/chszs/article/details/50996679 Nats的消息通信模型 作者&#xff1a;chszs&#xff0c;未经博主允许不得转载。经许可的转载需注明作者和博客主页&#xff1a;http:…

.NET6之MiniAPI(一):开始Mini API

Mini API之前的官方.net web框架&#xff0c;都是偏重的&#xff0c;不像其他语言&#xff0c;如go&#xff0c;python&#xff0c;或基于c#的nancy&#xff0c;都是简洁方式来开启web之旅的。所以有小伙伴就吐槽.net基于web的不友好性&#xff0c;这不&#xff0c;.net6中官方…

超线程_超线程加核显 i310100+梅捷H410超值爆款组合

酷睿i3-10100是一颗4核心8线程&#xff0c;三级缓存6MB&#xff0c;主频3.6-4.3GHz&#xff0c;集成核显UHD 630 350-1100MHz&#xff0c;热设计功耗65W。对比上代酷睿i3-9100&#xff0c;它增加了超线程技术&#xff0c;加速频率高了100MHz&#xff0c;其他不变。为什么在短短…

Hibernate之悲观锁与乐观锁

http://blog.csdn.net/a19881029/article/details/20665663 如果需要保证数据访问的排它性&#xff0c;则需对目标数据加“锁”&#xff0c;使其无法被其它程序修改 一&#xff0c;悲观锁 对数据被外界&#xff08;包括本系统当前的其它事务和来自外部系统的事务处理&#xff0…

他本硕博连跨3大专业,毕业后没多久被破格聘为985高校教授!

全世界只有3.14 % 的人关注了爆炸吧知识本文来源&#xff1a;募格学术整合自东南大学新闻网、东南大学校团委、募格课堂图片&#xff1a;网络来源&#xff1a;东南大学新闻网、东南大学校团委他从本科的计算机专业&#xff0c;到研究生的应用数学专业&#xff0c;再到博士开始研…

Winform VS2015打包

首先 &#xff0c;我们要去官网http://learn.flexerasoftware.com/content/IS-EVAL-InstallShield-Limited-Edition-Visual-Studio注册一个账号已获得installshiled的注册码。 是免费的~ 注册完后就可以下载我们的第三方打包工具&#xff1a; 注意&#xff1a;这里下载并安装完…

触发键盘_雷蛇这款光轴机械键盘开箱评测,光速触发,颜值爆表

首先感谢头条众测给予雷蛇猎魂光蛛精英版机械键盘的测评机会&#xff0c;雷蛇作为在游戏设备领域深耕的领先者&#xff0c;其生产的游戏设备深得游戏玩家喜爱&#xff0c;下面我们来一睹这款雷蛇机械键盘的风采。首先从包装盒正面可以感受到这款雷蛇光学机械轴键盘霸气侧漏&…

spring之使用Spring Security实现权限管理

转载&#xff1a;http://hanqunfeng.iteye.com/blog/1155226 目录 SpringSecurity3.X--一个简单实现 SpringSecurity3.X--前台与后台登录认证 SpringSecurity3.X--remember-me SpringSecurity3.X--验证码 作者对springsecurity研究不深&#xff0c;算是个初学者吧&#xff0c;最…

iNeuOS工业互联网操作系统,提升分布式云端控制安全策略和增加实时日志功能...

目 录1. 概述... 22. 平台演示... 23. 云端控制策略和应用过程... 23.1 云端控制策略... 23.2 控制应用过程... 34. 实时日志... 71. 概述这次升级主要提升云端控制的安全策略&#xff0c;不管公有云部署或是私有云部署&#…

直男的回答能多出乎意料?

1 我家的鸭子没这么扁&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 这个回答&#xff0c;妙啊...&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 强迫症犯人要求判10年▼4 像羊又像猫&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼5 美女…

Angularjs调用公共方法与共享数据

这个问题场景是在使用ionic开发页面的过程中发现&#xff0c;多个页面对应的多个controller如何去调用公共方法&#xff0c;比如给ionic引入了toast插件&#xff0c;如何将这个插件的调用变成公共方法或者设置成工具类&#xff0c;因为在每个controller中直接注入这个toast插件…

mysql去掉两个最高分_数据分析系列 16/32 | MySQL中子查询与联合查询

前面说了很多MySQL中的查询&#xff0c;比如条件查询、分组聚合查询、连接查询&#xff0c;今天来说一下另外两个非常的重要的查询&#xff0c;MySQL中的子查询和联合查询。PART01子查询子查询也称嵌套查询&#xff0c;是将一个查询语句嵌套在另一个查询语句的WHERE子句或者HAV…

那些不回你微信的人都在看什么?

如何成为一个“聊得开”的人&#xff1f;如何丰富空闲时光&#xff1f;如何在短时间内获取最最专业的文化、艺术资讯&#xff1f;小编给大家推荐几个公众号它们有趣有颜有料长按二维码&#xff0c;选择“识别图中二维码”关注印客美学id&#xff1a;inkbetter△长按二维码“识别…

按照学号查找学生_[源码和文档分享]基于JAVA和MYSQL数据库的学生成绩管理系统...

一、需求分析本系统是学生成绩管理系统&#xff0c;所以应该做到可以录入学生成绩&#xff0c;修改学生成绩&#xff0c;删除学生成绩&#xff0c;查询学生成绩&#xff0c;以及最后的所有学生按照GPA排名。本系统的数据来源期末考试成绩&#xff0c;用来实现录入&#xff0c;查…

颜宁问4对科研夫妻:男科学家怎样平衡事业家庭?

全世界只有3.14 % 的人关注了爆炸吧知识本文来源&#xff1a;科学网网上很多人一说到我&#xff0c;就说颜宁之所以能成功&#xff0c;因为她是单身&#xff0c;没有家庭拖累。但事实上&#xff0c;有家庭、有事业才是绝大多数人的现状。我们这次请来四对科研伉俪&#xff0c;不…

WebService学习笔记---CXF入门

2019独角兽企业重金招聘Python工程师标准>>> 一、准备 软件环境&#xff1a; JDK1.8, Eclipse JEE 4.4, Maven-3.2.5, Spring-4, CXF-3.1.5 二、创建项目 新建一个Maven项目&#xff0c;在pom.xml里添加spring依赖<dependencyManagement><dependencies>…

.NET 6新特性试用 | ArgumentNullException卫语句

前言在前面的文章中&#xff08;《可空引用类型》&#xff09;&#xff0c;我们介绍过编译器会帮我们检查空引用&#xff0c;但是仅仅是警告。最好的方式还是在运行时用卫语句进行检查&#xff1a;private void Test(WeatherForecast weatherForecast) {if (weatherForecast n…

JSP之EL表达式详细介绍

一、JSP EL语言定义 E L&#xff08;Expression Language&#xff09; 目的&#xff1a;为了使 JSP写起来更加简单。 表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言&#xff0c;它提供了在 JSP 中简化表达式的方法。它是一种简单的语言&#xff0c;基于可用的命名空…

那一年,爱因斯坦输得很惨很惨,被十几个诺奖得主怼了一遍后,退出了群聊……...

全世界只有3.14 % 的人关注了爆炸吧知识科学家撕逼原来这么刺激“遇事不决&#xff0c;量子力学”&#xff0c;作为长期在民间被调侃的学科&#xff0c;量子力学的名声不小&#xff0c;但它究竟有多重要&#xff0c;又是怎么来的&#xff0c;却少有人了解。而说到量子力学&…