Android属性动画

一、属性动画出现的原因 

属性动画(Property Animation)是在Android3.0(API  11)后才提供的一种全新的动画模式。

原因:由于补间和逐帧动画存在的一些缺点:

①作用的对象局限:即这两种动画只能够作用在视图View上,即只可以对一个ButtonTextView、甚至是LinearLayout、或者其它继承自View的组件进行动画操作,但无法对非View的对象进行动画操作。但是有时候,需要实现视图颜色动态变化,那么就需要操作视图颜色属性,从而实现动画效果。

②没有改变View的属性,只是改变视觉效果:举例

  • 如,将屏幕左上角的按钮 通过补间动画 移动到屏幕的右下角
  • 点击当前按钮位置(屏幕右下角)是没有效果的,因为实际上按钮还是停留在屏幕左上角,补间动画只是将这个按钮绘制到屏幕右下角,改变了视觉效果而已。

③动画效果单一,在功能和可扩展性上有局限性

为了解决补间动画的缺陷,Android在3.0以后给我们提供了一种全新的动画模式:属性动画。

二、属性动画简介

作用对象:任意的Java对象  不在局限于视图View对象

实现的动画效果:可自定义各种动画效果

特点:

  • 作用对象进行了扩展:不只是View对象,甚至没对象也可以
  • 动画效果:不只是4种基本变换,还有其他动画效果
  • 作用领域:API11后引入的

工作原理:在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。

工作原理逻辑图参照:https://www.jianshu.com/p/2412d00a0ce4

https://upload-images.jianshu.io/upload_images/944365-16a162a731f548d8.png

属性动画的两个核心类:ValueAnimator和ObjectAnimator

三、ValueAnimator的使用

通过不断控制 值 的变化,再不断 手动 赋给对象的属性,从而实现动画效果

  1. ValueAnimator.ofInt(int values):将初始值 以整型数值的形式 过渡到结束值 (即整型估值器-Int Evaluator)

具体使用:Java代码设置、xml设置

推荐使用Java代码设置,因为很多时候属性的起始值是无法提前知晓的,这需要在Java代码里动态获取。

例子,将一个Button按钮的宽从布局文件中默认设置的100扩大到1000

package com.example.wcystart.otherproject;

public class AnimationActivity extends AppCompatActivity {
    private ImageView mIvImageView;
    private Button mBtnStartAnimation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation);
        mIvImageView = findViewById(R.id.iv_image);
        mBtnStartAnimation = findViewById(R.id.start_animation);

   ValueAnimator valueAnimator=ValueAnimator.ofInt(mBtnStartAnimation.getLayoutParams().width,300);
        valueAnimator.setDuration(2000);
        valueAnimator.setRepeatCount(5);
        valueAnimator.setStartDelay(2000);

        //**将属性数值   手动赋值给 对象的属性:此处是将值  赋值给  图片的宽度
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int  currentValue = (int) animation.getAnimatedValue();
                System.out.println(currentValue);
                mBtnStartAnimation.getLayoutParams().width=currentValue;//每次变化后的值,赋给图片的宽度
                //刷新视图,即重新绘制
                mBtnStartAnimation.requestLayout();
            }
        });
        valueAnimator.start(); //启动动画

    }
     
}

打印出来的从初始值过度到结束值:

 

2.ValueAnimator.oFloat(float values)将初始值以浮点型数值的形式过度到结束值

这个演示在xml文件中配置:在res/animator

<?xml version="1.0" encoding="utf-8"?>
<animator  xmlns:android="http://schemas.android.com/apk/res/android"
   android:valueFrom="0"
    android:valueTo="100"
    android:duration="2000"
   android:repeatCount="2"
    android:valueType="intType">
</animator>

在Activity中引用

public class AnimationActivity extends AppCompatActivity {
    private ImageView mIvImageView;
    private Button mBtnStartAnimation;
    private Button mBtnStopAnimation;
    private AnimationDrawable mAnimationDrable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation);
        mIvImageView = findViewById(R.id.iv_image);
        mBtnStartAnimation = findViewById(R.id.start_animation);
        mBtnStopAnimation = findViewById(R.id.stop_animation);

        //引用属性动画
        Animator animator= AnimatorInflater.loadAnimator(this,R.animator.set_animation);
        animator.setTarget(mBtnStartAnimation.getLayoutParams().width);
        animator.start();

    }

3. ValueAnimator.ofObject()将初始值以对象的形式过渡到结束值,即通过操作对象实现动画效果。

实现动画效果:一个圆从一个点移动到另外一个点 

1.因为ValueAnimator.ofObject()是面向对象操作的,所以需要自定义对象类


public class Point {
    //设置两个变量用于记录坐标的位置
    private float x;
    private float y;

    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }
}

2.自定义估值器(TypeEvaluator)类

/**
 * Created by ${wcystart}
 * date:on 2019/1/17
 * description: 当初始值以对象的形式过渡到结束值时,估值器需自定义
 * 自定义的目的是自定义如何从初始坐标过渡到结束点坐标
 */

public class ObjectEvaluator implements TypeEvaluator {
    // 复写evaluate()
    // 在evaluate()里写入对象动画过渡的逻辑
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        // fraction:表示动画完成度(根据它来计算当前动画的值)
        // 1.将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;
        //2.根据fraction来计算当前动画的x,和 Y的值
        float x=startPoint.getX()+fraction*(endPoint.getX()-startPoint.getX());
        float y=startPoint.getY()+fraction*(endPoint.getY()-startPoint.getY());
        //3.将计算后的坐标封装到一个新的Point对象中返回
        Point point=new Point(x,y);
        return point;
        // 返回对象动画过渡的逻辑计算后的值
    }
}

3.将属性动画应用到自定义View类中

/**
 * Created by ${wcystart}
 * date:on 2019/1/17
 * description: 将属性动画应用到自定义的View中
 */

public class PotinView extends View {
    private static final float REDIUS = 80f; //圆的半径
    private Point currentPoint;//当前点的坐标
    private Paint mPaint; //画笔


    public PotinView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);
        if (currentPoint == null) {
            currentPoint = new Point(REDIUS, REDIUS);
            //在该点(80,80)上画一个圆,圆心(80,80),坐标80
            float x = currentPoint.getX();
            float y = currentPoint.getY();
            canvas.drawCircle(x, y, REDIUS, mPaint);

            //将属性动画应用到View上
            //1.创建初始值 和结束值
            Point startPoint = new Point(REDIUS, REDIUS);
            Point endPoint = new Point(800, 1300);

            //2.创建动画对象 设置初始值和结束值
            ValueAnimator animator = ValueAnimator.ofObject(new ObjectEvaluator(), startPoint, endPoint);
            animator.setDuration(5000);

            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    // 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint)
                    // 从而更新当前坐标值(currentPoint)
                    currentPoint = (Point) animation.getAnimatedValue();
                    // 步骤4:每次赋值后就重新绘制,从而实现动画效果
                    // 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
                    // 所以坐标值每改变一次,就会调用onDraw()一次
                    invalidate();
                }
            });

            animator.start();
        } else {
            // 如果坐标值不为0,则画圆
            // 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果
            float x = currentPoint.getX();
            float y = currentPoint.getY();
            canvas.drawCircle(x, y, REDIUS, mPaint);
        }

    }
}

4.在布局文件中引用自定义View

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    android:padding="10dp"
    tools:context="com.example.wcystart.otherproject.PropertyActivity">
    <com.example.wcystart.otherproject.PotinView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.example.wcystart.otherproject.PotinView>

</android.support.constraint.ConstraintLayout>

5.在Activity中设置显示视图

public class PropertyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_property);
    }
}

运行效果图:由于这是个动态的效果图,我录制了一个小视频,但是不知道怎么上传视频。求助求助~~~

其实ValueAnimator.ofObject()的本质还是操作 ** 值 **,只是是采用将 多个值 封装到一个对象里的方式 同时对多个值一起操作而已

就像上面的例子,本质还是操作坐标中的x,y两个值,只是将其封装到Point对象里,方便同时操作x,y两个值而已

四、ObjectAnimator类

直接对对象的属性值进行改变操作,如直接改变View的color,从而实现颜色的动画效果。

继承自ValueAnimator类,它的动画实现机制是基于ValueAnimator类的

工作原理:通过不断控制 值 的变化,再不断 自动 赋给对象的属性,从而实现动画效果.

ObjectAnimatorValueAnimator类的区别:

  • ValueAnimator 类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作;
  • ObjectAnimator 类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作;

 具体使用:可以使用ObjectAnimator实现补间动画的4种类型

 ObjectAnimator animator=ObjectAnimator.ofFloat(mBtnStartAnimation,"alpha",1f,0f,1f);
        animator.setDuration(5000);
        animator.start();

        ObjectAnimator animator=ObjectAnimator.ofFloat(mBtnStartAnimation,"rotation",0f,360f);
        animator.setDuration(5000);
        animator.start();

        float curTranslationX = mBtnStartAnimation.getTranslationX();
        ObjectAnimator animator=ObjectAnimator.ofFloat(mBtnStartAnimation,"translationX",curTranslationX,360f, curTranslationX);
        animator.setDuration(5000);
        animator.start();


        ObjectAnimator animator=ObjectAnimator.ofFloat(mBtnStartAnimation,"scaleX",1f,3f, 1f);
        animator.setDuration(5000);
        animator.start();

不仅仅是这几种属性值,可以写任意的属性值,因为ObjectAnimator 类 对 对象属性值 进行改变 再不断的自动赋给对象的属性 从而实现动画效果的。

至于是怎么自动赋给对象的属性的?本质

  • 是调用该对象属性的set() & get()方法进行赋值
  • 所以,ObjectAnimator.ofFloat(Object object, String property, float ....values)的第二个参数传入值的作用是:让ObjectAnimator类根据传入的属性名 去寻找 该对象对应属性名的 set() & get()方法,从而进行对象属性值的赋值

自动赋值的逻辑:

  1. 初始化时,如果属性的初始值没有提供,则调用属性的 get()进行取值;
  2. 当 值 变化时,用对象该属性的 set()方法,从而从而将新的属性值设置给对象属性。
  • ObjectAnimator 类针对的是任意对象 & 任意属性值,并不是单单针对于View对象
  • 如果需要采用ObjectAnimator 类实现动画效果,那么需要操作的对象就必须有该属性的set() & get()

***自定义对象属性实现动画效果

  • 为对象设置需要操作属性的set() & get()方法
  • 通过实现TypeEvaluator类从而定义属性变化的逻辑
  • ObjectAnimator更加智能、自动化程度更高

举例:一个圆的颜色渐变

public class PotinView2 extends View {
    private static final float RADIUS=100f;//圆的半径
    private Paint mPaint;

    private String color;

        public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
        mPaint.setColor(Color.parseColor(color));
        invalidate();
    }

    public PotinView2(Context context) {
        super(context);
    }

    public PotinView2(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }

    //先在初始点画圆,通过监听当前坐标值(currentPoint)的变化,每次变化都调用onDraw()重新绘制圆,从而实现圆的平移动画效果
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(500,500,RADIUS,mPaint);
    }
}

 

public class PropertyActivity extends AppCompatActivity {
private PotinView2 potinView2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_property);
        potinView2=findViewById(R.id.pointView2);

        ObjectAnimator animator=ObjectAnimator.ofObject(potinView2,"color",new ColorEvaluator(),"#0000FF", "#FF0000");
        animator.setDuration(5000);
        animator.start();
    }
}

具体可参考:https://www.jianshu.com/p/2412d00a0ce4

上述对象属性的set()不是设置属性 或 根本没有set() / get ()的情况应该如何处理?

手动设置属性的set() get()方法

通过包装原始对象,间接给对象加上该属性

public class AnimationActivity extends AppCompatActivity {
    private ImageView mIvImageView;
    private Button mBtnStartAnimation;
    private Button mBtnStopAnimation;
    private AnimationDrawable mAnimationDrable;
    private ViewWrapper viewWrapper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation);
        mIvImageView = findViewById(R.id.iv_image);
        mBtnStartAnimation = findViewById(R.id.start_animation);
        mBtnStopAnimation = findViewById(R.id.stop_animation);

    }

    public void startAnimation(View view) 

    }


    public void stopAnimation(View view) {

        viewWrapper=new ViewWrapper(mBtnStartAnimation);
        ObjectAnimator.ofInt(viewWrapper, "width", 500).setDuration(3000).start();
        // 设置动画的对象是包装类的对象
    }


    private static class ViewWrapper {
        private View mTarget;

        public ViewWrapper(View target) {
            mTarget = target;
        }

        public int getWidth() {
            return mTarget.getLayoutParams().width;
        }

        public void setWidth(int width) {
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }
}

好了,至此属性动画的两个核心类就学完了。

属性动画的本质原理:通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果

整体文章参考:https://www.jianshu.com/p/2412d00a0ce4

如果面试中被问到,为什么属性动画被移动之后仍可点击?

view动画和属性动画的实现

//平移动画
TranslateAnimation translateAnimation = new TranslateAnimation(0, 150, 0, 300);
translateAnimation.setDuration(500);
translateAnimation.setFillAfter(true);
textView.startAnimation(translateAnimation);

//属性动画
textView.animate().translationX(150).translationY(300).setDuration(500).start();
textView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(AnimationActivity.this,"clicked",Toast.LENGTH_SHORT).show();
    }
});

view动画,当textView位移之后,监听点击事件的区域还在原来的地方。而属性动画,监听点击事件的区域随着View的移动监听事件也随着移动了。

这是为什么呢?

补间动画不能改变接受触摸事件的区域,而属性动画就可以?是因为属性动画改变了坐标吗?

因为他们返回的值都分别加上了Translaion的值,而属性动画更新帧时,也更新了TransLation的值,所以当动画播放完毕,getX 和getY,总能取到正确的值。

public float getX() {
    return mLeft + getTranslationX();
}
public float getY() {
    return mTop + getTranslationY();
}

参考链接:https://mp.weixin.qq.com/s/cmsuZyJaqBHAA-oC0SyS-g

 

 

 

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值