Android高级UI开发(二十四)属性动画之大师在流浪、小丑在天堂

举报
yd_57386892 发表于 2020/12/28 23:26:58 2020/12/28
【摘要】 昨天刷抖音的时候,被一个在路灯下诵读《战国策》、《尚书》、《左转》的流浪汉感动。 今天为了致敬大师,以大师的图片作为素材来演示一下Android中的属性动画。 Android的动画分为补间动画、帧动画、属性动画。(本文中的实例工程下载地址:https://download.csdn.net/download/gaoxiaoweiandy/11033672) 补间动画...

昨天刷抖音的时候,被一个在路灯下诵读《战国策》、《尚书》、《左转》的流浪汉感动。

今天为了致敬大师,以大师的图片作为素材来演示一下Android中的属性动画。

Android的动画分为补间动画、帧动画、属性动画。(本文中的实例工程下载地址:https://download.csdn.net/download/gaoxiaoweiandy/11033672)

补间动画:透明度、缩放、旋转、平移,通过设置动画的起点、终点、执行时间及插值器来计算某一时间点中相应的值,从而补充这个时间点上的动画,最终实现从起始点到终点的平滑过渡。它直接作用的对象是VIEW视图,并不是VIEW的某一个属性,如translationX属性、translationY属性。

帧动画:播放一组图片,我们小时候都玩过,就是不断翻书,每一页上的小人就会连贯的动起来。

属性动画:通过改变VIEW的属性,如X,Y,alpha,rotate属性来实现平移、透明度、旋转等。而且还可以自定义属性。补间动画与属性动画最大的区别是在平移上,一个按钮执行了补件动画的X平移后,你点击它是不会有反应的,只有点击按钮最开始的那个位置才会响应,这说明补间动画不是真正的平移,而是在目标位置重新绘制了一个该按钮的副本,并非真身。

今天我们来着重介绍一下属性动画。我们定义一个按钮,通过单击按钮来执行一些示例动画。

1. 布局:


  
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context=".MainActivity">
  8. <Button
  9. android:id="@+id/btStartAnimate"
  10. android:background="@mipmap/king"
  11. android:layout_centerInParent="true"
  12. android:onClick="startAnimate"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:text="start!"
  16. />
  17. </RelativeLayout>

在此我们为Button定义了一个单击响应函数startAnimate,在这个函数里我们将演示一些属性动画的用法。

2. MainActivity.java代码

2.1 演示补间动画的“假移动”


  
  1. package com.example.propertyanimate;
  2. import android.animation.Animator;
  3. import android.animation.AnimatorSet;
  4. import android.animation.ObjectAnimator;
  5. import android.animation.PropertyValuesHolder;
  6. import android.animation.TypeEvaluator;
  7. import android.animation.ValueAnimator;
  8. import android.graphics.PointF;
  9. import android.media.tv.TvContract;
  10. import android.support.v7.app.AppCompatActivity;
  11. import android.os.Bundle;
  12. import android.util.Log;
  13. import android.view.View;
  14. import android.view.animation.AccelerateDecelerateInterpolator;
  15. import android.view.animation.AccelerateInterpolator;
  16. import android.view.animation.Animation;
  17. import android.view.animation.AnimationSet;
  18. import android.view.animation.AnimationUtils;
  19. import android.view.animation.AnticipateInterpolator;
  20. import android.view.animation.BounceInterpolator;
  21. import android.view.animation.CycleInterpolator;
  22. import android.view.animation.OvershootInterpolator;
  23. import android.widget.Button;
  24. import android.widget.Toast;
  25. import java.util.ArrayList;
  26. public class MainActivity extends AppCompatActivity {
  27. @Override
  28. protected void onCreate(Bundle savedInstanceState) {
  29. super.onCreate(savedInstanceState);
  30. setContentView(R.layout.activity_main);
  31. }
  32. /**
  33. * 动画测试
  34. * @param v: 就是布局里的BUTTON
  35. */
  36. int position = 0;
  37. public void startAnimate(final View v)
  38. {
  39. //补间动画START:
  40. // 当按钮移动到目标位置时,点击按钮是不起作用的,即不会弹出“start Animate”,
  41. //只有在原来的位置处单击才会响应,这说明它并没有真正的移动,只是在目标位置处又重新绘制了一个按钮。
  42. Toast.makeText(this,"start Animate",Toast.LENGTH_LONG).show();
  43. Animation anim = AnimationUtils.loadAnimation(this,R.anim.translate);
  44. v.startAnimation(anim);
  45. }

在此,我们在startAnimate里先演示了下补件动画,用来说明补件动画的平移是“假平移”。当第一次点击按钮时,按钮移动到了右下角,这时你在右下角点击该按钮时,该按钮没有任何响应(不弹出任何Toast提示),只有点击原位置处的时候才会有提示,这说明它还在原地。上面代码中的R.anim.translate动画XML文件如下:


  
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <translate
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:fromXDelta="0"
  5. android:fromYDelta="0"
  6. android:toXDelta="50%p"
  7. android:toYDelta="50%p"
  8. android:fillAfter="true"
  9. android:duration="500">
  10. </translate>

2.2 用属性动画来实现平移


  
  1. ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(v,"translationX",0,500);
  2. objectAnimator.setDuration(2000);
  3. objectAnimator.start();

我们可以把这几行代码放在startAnimate函数里,单击按钮来演示按钮水平移动。我们着重看一下ObjectAnimator.ofFloat函数的这几个参数:

v: 表示动画作用在按钮这个view上

translationX:  按钮的属性,表示水平位置。

0,500 这是一组浮点数,可以有多个参数。0表示按钮的起始位置、500表示相对于起始点的距离。

最终执行效果是:按钮从原来的位置水平向右移动500px。当我们第二次点击按钮时会重复执行此动画,但是还是从最初的位置水平右移500px,而不是从第一次执行的末尾位置开始向右移动500px。你可以这样给出这组数值0,100,500,按钮会先移动到100px处,然后再移动到500px处。注意:这里的距离都是相对于按钮最初起始点的相对位移。

除了X轴平移外,我们还可以实现以下与补件动画同样的动画效果:

1. Y轴平移


  
  1. ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(v,"translationY",0,500);
  2. objectAnimatorY.setDuration(3000);
  3. objectAnimatorY.start();

2. Z轴平移


  
  1. ObjectAnimator objectAnimatorZ = ObjectAnimator.ofFloat(v,"translationZ",0,20);
  2. objectAnimatorZ.setDuration(3000);
  3. objectAnimatorZ.start();

3. 旋转


  
  1. ObjectAnimator objectAnimatorRotation = ObjectAnimator.ofFloat(v,"rotationY",0,360);
  2. objectAnimatorRotation.setDuration(5000);
  3. objectAnimatorRotation.start();

绕Y轴旋转,当然你可以改成rotationX,rotationZ.

 

4. 缩放


  
  1. ObjectAnimator objectAnimatorScaleX = ObjectAnimator.ofFloat(v,"scaleX",0,1);
  2. objectAnimatorScaleX.setDuration(5000);
  3. objectAnimatorScaleX.start();
  4. ObjectAnimator objectAnimatorScaleY = ObjectAnimator.ofFloat(v,"scaleY",0,1);
  5. objectAnimatorScaleY.setDuration(5000);
  6. objectAnimatorScaleY.start()

宽、高 缩放:从小变大。

 

2.3 同时执行多个动画

2.3.1 方法1:使用AnimatorSet


  
  1. //X
  2. ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(v,"translationX",0,500);
  3. objectAnimatorX.setDuration(500);
  4. //Y
  5. ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(v,"translationY",0,500);
  6. objectAnimatorY.setDuration(500);
  7. //将上面2个动画添加到集合里
  8. ArrayList<Animator> animatorArrayList = new ArrayList<>();
  9. animatorArrayList.add(objectAnimatorX);
  10. animatorArrayList.add(objectAnimatorY);
  11. AnimatorSet animationSet = new AnimatorSet();
  12. //同时执行集合里的多个动画
  13. animationSet.playTogether(animatorArrayList);
  14. animationSet.start();

2.3.2 方法2:监听“属性值”时并行执行动画


  
  1. ObjectAnimator animator = ObjectAnimator.ofFloat(v,"hello",0,100);
  2. animator.setDuration(2000);
  3. animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  4. @Override
  5. public void onAnimationUpdate(ValueAnimator valueAnimator) {
  6. float value = (float)valueAnimator.getAnimatedValue();
  7. //valueAnimator.getAnimatedFraction();实质就是value/100
  8. v.setScaleX((float)(0 + value/100));
  9. v.setScaleY((float)(0 + value/100));
  10. }
  11. });
  12. animator.addListener(new Animator.AnimatorListener() {
  13. @Override
  14. public void onAnimationStart(Animator animator) {
  15. Log.i("AnimatorListener","onAnimationStart");
  16. }
  17. @Override
  18. public void onAnimationEnd(Animator animator) {
  19. Log.i("AnimatorListener","onAnimationEnd");//只能监听到END
  20. }
  21. @Override
  22. public void onAnimationCancel(Animator animator) {
  23. Log.i("AnimatorListener","onAnimationCancel");
  24. }
  25. @Override
  26. public void onAnimationRepeat(Animator animator) {
  27. Log.i("AnimatorListener","onAnimationRepeat");
  28. }
  29. });
  30. animator.start();

这里ObjectAnimator.ofFloat(v,"hello",0,100)与之前的不一样,其中"hello"是一个VIEW不存在的属性,这个动画只关心值得变化:从。0到100。 为何ObjectAnimator可以这样用,是因为ObjectAnimator继承于ValueAnimator,而ValueAnimator就可以这样用,只关心值得变化。我们使用为ObjectAnimator对象设置如下监听器就可以监听动画执行情况,可以获取到某一时刻动画执行的进度值:


  
  1. animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  2. @Override
  3. public void onAnimationUpdate(ValueAnimator valueAnimator) {
  4. float value = (float)valueAnimator.getAnimatedValue();
  5. //valueAnimator.getAnimatedFraction();实质就是value/100
  6. v.setScaleX((float)(0 + value/100));
  7. v.setScaleY((float)(0 + value/100));
  8. }
  9. });

在这里我们调用valueAnimator.getAnimatedValue()来获取动画执行过程中某一时间点的值。执行总时长为2000毫秒,由animator.setDuration(2000)决定。比如当动画执行到1秒时,这里的value一般情况应该是50. 我们在这个监听函数里主要并行执行宽、高两个缩放动画:v.setScaleX, v.setScaleY. 缩放的范围是0-1,某一时间点缩放的比例刚好是 (0-100)值动画执行的进度比例,即value/100,也就是valueAnimator.getAnimatedFraction(); 这里的运行效果是:一张图片从无到原始大小的放大效果。

2.3.3 方法3:使用ValueAnimator

原理同方法2一样,因为方法2中的ObjectAnimator继承于ValueAnimator。


  
  1. //方法3:直接使用ValueAnimator,原理与方法2一样
  2. ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f,100f);
  3. valueAnimator.setDuration(2000);
  4. valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  5. @Override
  6. public void onAnimationUpdate(ValueAnimator valueAnimator) {
  7. float value = (float)valueAnimator.getAnimatedValue();
  8. //valueAnimator.getAnimatedFraction();实质就是value/100
  9. v.setScaleX((float)(0 + value/100));
  10. v.setScaleY((float)(0 + value/100));
  11. }
  12. });
  13. valueAnimator.start();

这次使用的是ObjectAnimator的父类ValueAnimator,与ObjectAnimator的区别是 ofFloat函数不用传递“属性”了,在上一方法2中ObjectAnimator.ofFloat不得不传递一个参数:属性名。同样,在ValueAnimator中添加监听动画值得变化,即0-100在2000毫秒内的变化,原理和过程同方法2,在此不再赘述。

2.3.4 方法4:PropertyValuesHolder

创建多个PropertyValuesHolder,每一个PropertyValuesHolder关联一个动画。最后一起执行多个PropertyValuesHolder。


  
  1. PropertyValuesHolder propertyValuesHolderScaleX = PropertyValuesHolder.ofFloat("scaleX",1f,0.7f,1f);
  2. PropertyValuesHolder propertyValuesHolderScaleY = PropertyValuesHolder.ofFloat("scaleY",1f,0.7f,1f);
  3. PropertyValuesHolder propertyValuesHolderAlpha = PropertyValuesHolder.ofFloat("alpha",1f,0.7f,1f);
  4. ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(v,
  5. propertyValuesHolderScaleX,
  6. propertyValuesHolderScaleY,
  7. propertyValuesHolderAlpha);
  8. objectAnimator.setDuration(2000);
  9. objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  10. @Override
  11. public void onAnimationUpdate(ValueAnimator valueAnimator) {
  12. float fraction = valueAnimator.getAnimatedFraction();
  13. float value = (float) valueAnimator.getAnimatedValue();
  14. Log.i("PropertyValuesHolder","fraction="+fraction+",value="+value);
  15. }
  16. });
  17. objectAnimator.start();

代码分析:

Step1.  我们在这里创建了3个PropertyValuesHolder,propertyValuesHolderScaleX,propertyValuesHolderScaleY,propertyValuesHolderAlhap,分别是代表缩放宽、高、透明度。

Step2.  使用ObjectAnimator.ofPropertyValuesHolder函数,以Step1中的3个PropertyValuesHolder为参数创建一个ObjectAnimator对象,这时ObjectAnimator相当于一个AnimateSet。

Step3. objectAnimator.start()启动动画,相当于同时执行了3个动画。

我们发现每一个PropertyValuesHolder动画都有3个值,10.7, 1这几个值叫关键帧。例如缩放:

PropertyValuesHolder.ofFloat("scaleX",1f,0.7f,1f);   表示图片先从原始大小缩放到原大小的70%,然后再恢复成原大小。我们通过objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()){....}函数中打印的日志可以看出:当动画执行到fraction  = 0.5时,即执行了一半时,图片会缩小到最中间的关键帧 0.7,即原大小的70%。也就是说从1到0.7与从0.7到1的缩放动画所经历的时间是相等的,因为它们是关键帧。

2.4 属性动画估值器

为什么要用估值器,那我们先来看一下估值器有什么作用。我们发现上面的例子中,大多数属性都是VIEW自带的属性,而且动画执行过程中某一时刻对应的动画值都是由系统计算出来的,代码如下:


  
  1. objectAnimator.setDuration(2000);
  2. objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  3. @Override
  4. public void onAnimationUpdate(ValueAnimator valueAnimator) {
  5. float fraction = valueAnimator.getAnimatedFraction();
  6. float value = (float) valueAnimator.getAnimatedValue();
  7. Log.i("PropertyValuesHolder","fraction="+fraction+",value="+value);
  8. }
  9. });
  10. objectAnimator.start();

         安卓系统是通过valueAnimator.getAnimatedValue();这个API函数来获取2000毫秒期间某一时间点动画执行到的属性值。计算方法是:根据动画执行的进度比例(时间比例)来计算出当时的属性值。然而这种计算方法我们不能控制和修改。为了我们可以自定义某一时刻属性值的计算方法(算法),我们就得用估值器,利用来自定义值的计算方法。我们先看代码,用代码来讲解估值器如何使用,以下代码实现了一个抛物线动画,就是VIEW沿着抛物线的轨迹移动.


  
  1. //估值器: 某一时间点的 属性值是 自定义计算出来的。
  2. ValueAnimator valueAnimator = new ValueAnimator();
  3. valueAnimator.setObjectValues(new PointF(0,0));
  4. valueAnimator.setDuration(4000);
  5. //设置一个估值器来计算控件的X坐标与Y坐标
  6. valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {
  7. @Override
  8. public PointF evaluate(float fraction, PointF start, PointF end) {
  9. PointF pointF = new PointF();
  10. pointF.x = 100f * (fraction * 4);// s = vt; y = 1/2g t*t
  11. pointF.y = 0.5f*100*(fraction*4)*(fraction*4);
  12. Log.i("onAnimationUpdate","x0="+pointF.x+",y0="+pointF.y+",fraction="+fraction);
  13. return pointF;
  14. }
  15. });
  16. valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  17. @Override
  18. public void onAnimationUpdate(ValueAnimator animation) {
  19. PointF pointF = (PointF) animation.getAnimatedValue();
  20. v.setX(pointF.x);
  21. v.setY(pointF.y);
  22. Log.i("onAnimationUpdate","x="+pointF.x+",y="+pointF.y);
  23. }
  24. });
  25. valueAnimator.start();

 第一步:valueAnimator.setObjectValues(new PointF(0,0));  设置一个动画的初始值为(0,0),表示VIEW的起始坐标(X=0,Y=0),在这里我们可以把值的类型设置为Point对象,这完全颠覆了我们之前只能设置float、int等基本类型的习惯。

 第二步:valueAnimator添加一个估值器:

valueAnimator.setEvaluator(new TypeEvaluator<PonitF>()

                         evaluate(float fraction,PonitF start, PointF end){....}

                );

我们发现泛型参数是我们自己的 "属性对象类型"PointF. 然后重新evaluate函数,在这个函数内我们自己计算出我们的value;


  
  1. PointF pointF = new PointF();
  2. pointF.x = 100f * (fraction * 4);// s = vt; y = 1/2g t*t
  3. pointF.y = 0.5f*100*(fraction*4)*(fraction*4);
  4. Log.i("onAnimationUpdate","x0="+pointF.x+",y0="+pointF.y+",fraction="+fraction);
  5. return pointF;

在这里我们new了一个PonitF,并计算它的x,y坐标,计算方法就是 x = vt(速度*时间), y = 1/2*g*t*t(g是个常量加速度)。最后将这个PointF当做一个Value返回。到时候我们的值监听器就会在某一时间进度上返回这个PointF,作为animation.getAnimatedValue()的返回值。值监听器的代码如下:


  
  1. valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  2. @Override
  3. public void onAnimationUpdate(ValueAnimator animation) {
  4. PointF pointF = (PointF) animation.getAnimatedValue();
  5. v.setX(pointF.x);
  6. v.setY(pointF.y);
  7. Log.i("onAnimationUpdate","x="+pointF.x+",y="+pointF.y);
  8. }
  9. });

在动画值更新监听器里通过调用animation.getAnimatedValue()返回某一时刻的值:PointF。然后我们把View的坐标(x,y)设置成PointF中的(x,y). 最终VIEW的运行轨迹就是一条抛物线。

 

2.5  加速器


  
  1. //设置加速器
  2. ObjectAnimator oa = ObjectAnimator.ofFloat(v, "translationY", 0f,300);
  3. oa.setDuration(500);
  4. //设置加速器---
  5. // oa.setInterpolator(new AccelerateInterpolator(5));//加速
  6. // oa.setInterpolator(new AccelerateDecelerateInterpolator());//先加速后减速
  7. // oa.setInterpolator(new AnticipateInterpolator(8)); //
  8. oa.setInterpolator(new OvershootInterpolator());//滑出
  9. // oa.setInterpolator(new CycleInterpolator(4)); //振荡
  10. // oa.setInterpolator(new BounceInterpolator()); //阻尼,回弹
  11. oa.start();

通过setInterpolator函数来为动画设置加速器,比如第一个oa.setInterpolator(new AccelerateInterpolator(5));设置了一个“”速度增加“ 的加速器,动画的运行效果是平移速度越来越快。其他几个“加速器” 大家可以尝试运行一下看看效果。

 

文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/88415885

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。