安卓属性动画之 - TypeEvaluator自定义计算器和TimeInterpolator实现自定义插值器

引言:
属性动画是一种很强大的东西,不同于补间动画,它在完成之后,会保留最终的状态。比如说,实现一个平移操作,最后View就会真的失去初始的位置和占用的空间,而获得一个新的位置和空间。

说明:
这里我们说明一下为什么要自定义上面两个东西。
1.对于Evaluator,可用的类型有好几种,比如Integer,Float等,他们是基于一个类型进行变换。如果我们想要实现从一个对象变到另一个对象,当然必须是是同一个类的对象。这种状态的过渡就需要我们自己重写evaluate方法来实现。
2.那又为什么要自定义插值器呢,因为有时候我们可能需要根据需求,在规定时间内对每个时间段动画变化快慢和方式进行调整,因此需要重写getInterpolation方法。可以把参数input理解为定义域为【0,1】的自变量x,我们要返回一个同样值域为【0,1】的因变量y,且y是x的函数。哈哈哈,太形象了吧。

实现TypeEvaluator:
说一下fraction,这是一个分数,在0到1之间,可以理解为进度。

    /**
     * 自定义计算器,计算[0,1]之间对象的每个对应状态
     * 我们这里改变点的坐标
     */
    public class PointEvaluator implements TypeEvaluator<Point> {
        @Override
        public Point evaluate(float fraction, Point startValue, Point endValue) {
            Point point=new Point();
            point.x= (int) (startValue.x+fraction*(endValue.x-startValue.x));
            point.y=(int)(startValue.y+fraction*(endValue.y-startValue.y));
            return point;
        }
    }

实现TimeInterpolator:
这个实现,是用来控制上面那个进度变化的速度,变化快慢。

    /**
     * 自定义插值器,即控制动画变换快慢,这里我们实现以正弦函数在【0,1】变换
     * 即斜率逐渐减小,动画逐渐变的迟缓
     */
    public class MyInterpolator implements TimeInterpolator{

        @Override
        public float getInterpolation(float input) {
            //正弦函数sin(Π*x/2),在定义域区间【0,1】取值为【0,1】,若定义的函数值域超出【0,1】
            //会出现回弹现象,即不能准确定位到我们定义属性动画时的最终状态
            return (float) Math.sin(input*Math.PI/2);
        }
    }

完整Demo:
给出完整的代码,实现一个“点击按钮就在屏幕上以正弦函数变化快慢绘制一个小圆的移动过程”的功能。

MainActivity:

package com.example.advancedanimatorusage;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.example.advancedanimatorusage.views.MyView;

public class MainActivity extends AppCompatActivity {

    private Button mStartAnimation;
    private MyView myView;

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

    private void setClickEvent() {
        mStartAnimation.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //开始动画时,隐藏按钮,显示MyView
                mStartAnimation.setVisibility(View.GONE);
                myView.setVisibility(View.VISIBLE);
            }
        });
    }

    private void initView() {
        mStartAnimation=findViewById(R.id.bt_start_animation);
        myView=findViewById(R.id.my_view_main);
    }

    public void setmStartAnimationVisibility() {
        //动画结束时,显示按钮,隐藏MyView
        mStartAnimation.setVisibility(View.VISIBLE);
        myView.setVisibility(View.GONE);
    }
}

布局代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/bt_start_animation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Animation"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <com.example.advancedanimatorusage.views.MyView
        android:id="@+id/my_view_main"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

MyView(实现绘制的自定义View):

package com.example.advancedanimatorusage.views;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

import com.example.advancedanimatorusage.MainActivity;

public class MyView extends View {

    private Paint mPaint;
    private static final int RADIUS=80;
    private Point currentPoint;
    private Point startPoint;  //初始状态
    private Point endPoint;   //最后状态
    private MainActivity mContext;

    public MyView(Context context) {
        super(context);
        init(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(currentPoint==null){
            currentPoint=new Point(RADIUS,RADIUS);
            canvas.drawCircle(currentPoint.x,currentPoint.y,RADIUS,mPaint);
            startAnimator();
        }else{
            canvas.drawCircle(currentPoint.x,currentPoint.y,RADIUS,mPaint);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        startPoint=new Point(RADIUS,RADIUS);
        endPoint=new Point(getMeasuredWidth()-RADIUS,getMeasuredHeight()-RADIUS);
    }

    private void init(Context context){
        //初始化画笔对象
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);
        mContext= (MainActivity) context;
    }

    public void startAnimator(){
        //定义一个属性动画
        ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Point point= (Point) animation.getAnimatedValue();
                currentPoint.set(point.x,point.y);
                //调用该方法,会使View重新绘制,会清空画布,并且会重新调用onDraw方法
                invalidate();
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mContext.setmStartAnimationVisibility();
                currentPoint=null;
            }
        });
        valueAnimator.setInterpolator(new MyInterpolator());
        valueAnimator.setDuration(4000);
        valueAnimator.start();
    }


    /**
     * 自定义计算器,计算[0,1]之间对象的每个对应状态
     * 我们这里改变点的坐标
     */
    public class PointEvaluator implements TypeEvaluator<Point> {
        @Override
        public Point evaluate(float fraction, Point startValue, Point endValue) {
            Point point=new Point();
            point.x= (int) (startValue.x+fraction*(endValue.x-startValue.x));
            point.y=(int)(startValue.y+fraction*(endValue.y-startValue.y));
            return point;
        }
    }

    /**
     * 自定义插值器,即控制动画变换快慢,这里我们实现以正弦函数在【0,1】变换
     * 即斜率逐渐减小,动画逐渐变的迟缓
     */
    public class MyInterpolator implements TimeInterpolator{

        @Override
        public float getInterpolation(float input) {
            //正弦函数sin(Π*x/2),在定义域区间【0,1】取值为【0,1】,若定义的函数值域超出【0,1】
            //会出现回弹现象,即不能准确定位到我们定义属性动画时的最终状态
            return (float) Math.sin(input*Math.PI/2);
        }
    }
}

效果图由于是动态的,还不会制作gif~~~自己复制粘贴我的代码去试试吧,注意改一改引入的文件就好了

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

猜你喜欢

转载自blog.csdn.net/tran_sient/article/details/104849663