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

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

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

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

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

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

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

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

1. 布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btStartAnimate"
        android:background="@mipmap/king"
        android:layout_centerInParent="true"
        android:onClick="startAnimate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start!"
 />

</RelativeLayout>

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

2. MainActivity.java代码

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

package com.example.propertyanimate;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
import android.media.tv.TvContract;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.CycleInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.Button;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    /**
     * 动画测试
     * @param v: 就是布局里的BUTTON
     */
    int position  = 0;
    public void startAnimate(final View v)
    {
            //补间动画START:
            //  当按钮移动到目标位置时,点击按钮是不起作用的,即不会弹出“start Animate”,
            //只有在原来的位置处单击才会响应,这说明它并没有真正的移动,只是在目标位置处又重新绘制了一个按钮。
            Toast.makeText(this,"start Animate",Toast.LENGTH_LONG).show();
            Animation anim = AnimationUtils.loadAnimation(this,R.anim.translate);
            v.startAnimation(anim);
    }

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

扫描二维码关注公众号,回复: 8682231 查看本文章
<?xml version="1.0" encoding="utf-8"?>
<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:toXDelta="50%p"
    android:toYDelta="50%p"
    android:fillAfter="true"
    android:duration="500">
</translate>

2.2 用属性动画来实现平移

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

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

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

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

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

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

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

1. Y轴平移

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

2. Z轴平移

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

3. 旋转

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

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

4. 缩放

ObjectAnimator objectAnimatorScaleX = ObjectAnimator.ofFloat(v,"scaleX",0,1);
objectAnimatorScaleX.setDuration(5000);
objectAnimatorScaleX.start();
         
ObjectAnimator objectAnimatorScaleY = ObjectAnimator.ofFloat(v,"scaleY",0,1);
objectAnimatorScaleY.setDuration(5000);
objectAnimatorScaleY.start()

宽、高 缩放:从小变大。

2.3 同时执行多个动画

2.3.1 方法1:使用AnimatorSet

        //X
        ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(v,"translationX",0,500);
        objectAnimatorX.setDuration(500);
        //Y
        ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(v,"translationY",0,500);
        objectAnimatorY.setDuration(500);

        //将上面2个动画添加到集合里
        ArrayList<Animator> animatorArrayList = new ArrayList<>();
        animatorArrayList.add(objectAnimatorX);
        animatorArrayList.add(objectAnimatorY);
        AnimatorSet animationSet = new AnimatorSet();

        //同时执行集合里的多个动画
        animationSet.playTogether(animatorArrayList);
        animationSet.start();

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

        ObjectAnimator animator = ObjectAnimator.ofFloat(v,"hello",0,100);
        animator.setDuration(2000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float)valueAnimator.getAnimatedValue();
                //valueAnimator.getAnimatedFraction();实质就是value/100
                v.setScaleX((float)(0 + value/100));
                v.setScaleY((float)(0 + value/100));
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

                Log.i("AnimatorListener","onAnimationStart");
            }
            @Override
            public void onAnimationEnd(Animator animator) {
                Log.i("AnimatorListener","onAnimationEnd");//只能监听到END
            }
            @Override
            public void onAnimationCancel(Animator animator) {
                Log.i("AnimatorListener","onAnimationCancel");
            }
            @Override
            public void onAnimationRepeat(Animator animator) {
                Log.i("AnimatorListener","onAnimationRepeat");
            }
        });

        animator.start();

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

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float)valueAnimator.getAnimatedValue();
                //valueAnimator.getAnimatedFraction();实质就是value/100
                v.setScaleX((float)(0 + value/100));
                v.setScaleY((float)(0 + value/100));
            }
        });

在这里我们调用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。

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

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

2.3.4 方法4:PropertyValuesHolder

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

PropertyValuesHolder propertyValuesHolderScaleX = PropertyValuesHolder.ofFloat("scaleX",1f,0.7f,1f);
PropertyValuesHolder propertyValuesHolderScaleY = PropertyValuesHolder.ofFloat("scaleY",1f,0.7f,1f);
PropertyValuesHolder propertyValuesHolderAlpha = PropertyValuesHolder.ofFloat("alpha",1f,0.7f,1f);

ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(v,
propertyValuesHolderScaleX,
propertyValuesHolderScaleY,
propertyValuesHolderAlpha);

objectAnimator.setDuration(2000);
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
       @Override
       public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float fraction = valueAnimator.getAnimatedFraction();
            float value = (float) valueAnimator.getAnimatedValue();
            Log.i("PropertyValuesHolder","fraction="+fraction+",value="+value);
          }
        });
 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自带的属性,而且动画执行过程中某一时刻对应的动画值都是由系统计算出来的,代码如下:

objectAnimator.setDuration(2000);
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
       @Override
       public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float fraction = valueAnimator.getAnimatedFraction();
            float value = (float) valueAnimator.getAnimatedValue();
            Log.i("PropertyValuesHolder","fraction="+fraction+",value="+value);
          }
        });
 objectAnimator.start();

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

        //估值器: 某一时间点的 属性值是 自定义计算出来的。
        ValueAnimator valueAnimator =  new ValueAnimator();
        valueAnimator.setObjectValues(new PointF(0,0));
        valueAnimator.setDuration(4000);

        //设置一个估值器来计算控件的X坐标与Y坐标
        valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {
            @Override
            public PointF evaluate(float fraction, PointF start, PointF end) {

                PointF pointF = new PointF();
                pointF.x = 100f * (fraction * 4);// s = vt;  y = 1/2g t*t
                pointF.y = 0.5f*100*(fraction*4)*(fraction*4);
                Log.i("onAnimationUpdate","x0="+pointF.x+",y0="+pointF.y+",fraction="+fraction);
                return pointF;
            }
        });
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF pointF = (PointF) animation.getAnimatedValue();
                v.setX(pointF.x);
                v.setY(pointF.y);
                Log.i("onAnimationUpdate","x="+pointF.x+",y="+pointF.y);
            }
        });
        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;

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

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

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF pointF = (PointF) animation.getAnimatedValue();
                v.setX(pointF.x);
                v.setY(pointF.y);
                Log.i("onAnimationUpdate","x="+pointF.x+",y="+pointF.y);
            }
        });

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

2.5  加速器

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

          oa.start();

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

发布了44 篇原创文章 · 获赞 27 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/gaoxiaoweiandy/article/details/88415885
今日推荐