属性动画

ObjectAnimator

        float y = textView.getTranslationY();
        ObjectAnimator animator = ObjectAnimator.ofFloat(textView,"translationY",y,y+100);
        animator.setDuration(2000);
        animator.start();

效果:将tv像下平移100,单位我也不知道。

代码分析: 首先获取tv的y方向的值,使用ObjectAnimator的ofFloat方法创建ObjectAnimator对象,传入1234四个参数,

  • 1:动画实现的目标对象(demo这里是我们的tv),
  • 2:目标对象的属性,ObjectAnimator的原理是改变属性去实现动画效果的。
  • 34:这两个参数是绑定在一起的,是动画的参数,他们两个相当于第三个参数(用来描述动画的效果),而这第三个参数可以有多个值,demo中只有两个,如果是y,y+100,y,则效果是向下平移100,然后在平移回复到原来的y位置。
  • 下面两行就不用讲了。设置时长和开始动画。


    属性动画_第1张图片
    参数2可选的属性

动画集合

        // 创建三个属性动画,y方向平移,y方向缩放,x方向缩放
        ObjectAnimator translationY = ObjectAnimator.ofFloat(textView, "translationY", y, y+500f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(textView, "scaleY", 1f, 5f, 1f);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(textView, "scaleX", 1f, 5f, 1f);
        // 创建一个属性动画集合,
        AnimatorSet animSet = new AnimatorSet();
        animSet.play(scaleY).with(scaleX).after(translationY);
        animSet.setDuration(2000);
        animSet.start();

效果:tv向下平移500,xy同时放大5,在缩小到原来大小
首先新建三个动画,再建了一个动画集合,重点分析play这句,由tony play basketball with jack after school这句我们知道scaleY和scaleX是在translationY完成之后再一起同时进行的。
动画集合中的其他方法:

  • playTogether() :将传入的动画一起同时播放
  • with():同时
  • before():在之前
  • after():在之后

写一个demo帮助大家深入理解一下

demo描述:动画效果是六个iv选项发散出去围成一圈,有个回弹的效果。

                // 重复该段
                ObjectAnimator animator_llRepeatThisChapter = ObjectAnimator.ofFloat(ll_repeat_this_chapter,
                        "translationX",
                        ll_repeat_this_chapter.getTranslationX()+400,
                        ll_repeat_this_chapter.getTranslationX()-50,
                        ll_repeat_this_chapter.getTranslationX());

                // 重新讲解
                ObjectAnimator animator_llReExplain_1 = ObjectAnimator.ofFloat(ll_re_explain,
                        "translationX",
                        ll_re_explain.getTranslationX()+400,
                        ll_re_explain.getTranslationX()-40,
                        ll_re_explain.getTranslationX());
                ObjectAnimator animator_llReExplain_2 = ObjectAnimator.ofFloat(ll_re_explain,
                        "translationY",
                        ll_re_explain.getTranslationY()+300,
                        ll_re_explain.getTranslationY()-40,
                        ll_re_explain.getTranslationY());

                // 重新开始
                ObjectAnimator animator_ll_re_start_1 = ObjectAnimator.ofFloat(ll_re_start,
                        "translationX",
                        ll_re_start.getTranslationX()+100,
                        ll_re_start.getTranslationX()-30,
                        ll_re_start.getTranslationX());
                ObjectAnimator animator_ll_re_start_2 = ObjectAnimator.ofFloat(ll_re_start,
                        "translationY",
                        ll_re_start.getTranslationY()+400,
                        ll_re_start.getTranslationY()-30,
                        ll_re_start.getTranslationY());

                // 问答
                ObjectAnimator animator_ll_question_answer_1 = ObjectAnimator.ofFloat(ll_question_answer,
                        "translationX",
                        ll_question_answer.getTranslationX()-100,
                        ll_question_answer.getTranslationX()+30,
                        ll_question_answer.getTranslationX());
                ObjectAnimator animator_ll_question_answer_2 = ObjectAnimator.ofFloat(ll_question_answer,
                        "translationY",
                        ll_question_answer.getTranslationY()+400,
                        ll_question_answer.getTranslationY()-30,
                        ll_question_answer.getTranslationY());

                // 跳到下一段
                ObjectAnimator animator_ll_jump_next_chapter_1 = ObjectAnimator.ofFloat(ll_jump_next_chapter,
                        "translationX",
                        ll_jump_next_chapter.getTranslationX()-350,
                        ll_jump_next_chapter.getTranslationX()+30,
                        ll_jump_next_chapter.getTranslationX());
                ObjectAnimator animator_ll_jump_next_chapter_2 = ObjectAnimator.ofFloat(ll_jump_next_chapter,
                        "translationY",
                        ll_jump_next_chapter.getTranslationY()+300,
                        ll_jump_next_chapter.getTranslationY()-30,
                        ll_jump_next_chapter.getTranslationY());

                // 跳到下一个讲解点
                ObjectAnimator animator_ll_jump_next_place_1 = ObjectAnimator.ofFloat(ll_jump_next_place,
                        "translationX",
                        ll_jump_next_place.getTranslationX()-400,
                        ll_jump_next_place.getTranslationX()+40,
                        ll_jump_next_place.getTranslationX());

                // 背景动画
                ObjectAnimator animator_rl_menu_1 = ObjectAnimator.ofFloat(rl_menu,
                        "ScaleX",0,1);
                ObjectAnimator animator_rl_menu_2 = ObjectAnimator.ofFloat(rl_menu,
                        "ScaleY",0,1);
                rl_menu.setPivotX(rl_menu.getWidth()/2);
                rl_menu.setPivotY(rl_menu.getHeight());
                animator_rl_menu_1.setDuration(300);
                animator_rl_menu_1.start();
                animator_rl_menu_2.setDuration(300);
                animator_rl_menu_2.start();

                // 动画集合
                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.playTogether(animator_llRepeatThisChapter,
                        animator_llReExplain_1,animator_llReExplain_2,
                        animator_ll_re_start_1,animator_ll_re_start_2,
                        animator_ll_question_answer_1,animator_ll_question_answer_2,
                        animator_ll_jump_next_chapter_1,animator_ll_jump_next_chapter_2,
                        animator_ll_jump_next_place_1
                );
                animatorSet.setDuration(600);
                animatorSet.start();

重点说一个东西:

                rl_menu.setPivotX(rl_menu.getWidth()/2);
                rl_menu.setPivotY(rl_menu.getHeight());

这是用来设置缩放的中心位置的,别的都应该可以看懂。
这里需要注意一下view的getWidth方法

  private void openQA() {
        llQA.setVisibility(View.VISIBLE);
        // 打开QA动画
        ObjectAnimator animator_QA_up = ObjectAnimator.ofFloat(llQA,"ScaleY",0,1);
        llQA.setPivotY(llQA.getHeight());
        animator_QA_up.setDuration(400).start();
    }

我在xml中将llQA设置为GONE,openQA方法的作用是从屏幕下方下打开这个ll,可是第一次运行程序发现ll是从默认位置0打开的,debug发现 llQA.setPivotY(llQA.getHeight());这行是运行了的,为什么第一次无效呢?原因是在设置ll为Visible后,ll并没有在屏幕上展示出来,而是经过animator慢慢的出来,view都没有绘制过,你的measure怎么会measure到呢,所以llQA.setPivotY(llQA.getHeight());里第一次传入的是0,以后的第二次第三次就正常了,那么我们怎么解决呢。

private void openQA() {
        llQA.setVisibility(View.VISIBLE);
        // 打开QA动画
        ObjectAnimator animator_QA_up = ObjectAnimator.ofFloat(llQA,"ScaleY",0,1);
        llQA.post(() -> {
            llQA.setPivotY(llQA.getHeight());
        });
        animator_QA_up.setDuration(400).start();
    }

可以通过View.post(runnable)方法获取View的宽高,通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了,就是将view放到最后去测量,等view绘制完毕再去测量。


下面介绍两个非常重要的概念,插值器(Interpolator)和估值器(TypeEvaluator)。

  • 插值器:决定值的变化规律。(加速减速匀速等)
  • 估值器:决定值的具体变化。
    插值器决定值的变化规律,即决定的是变化趋势;而接下来的具体变化数值则交给估值器

插值器:

安卓为我们提供了九种默认的插值器

        // 动画加速进行
        AccelerateInterpolator accelerateInterpolator;
        // 快速完成动画,超出再回到结束样式
        OvershootInterpolator overshootInterpolator;
        // 先加速再减速
        AccelerateDecelerateInterpolator accelerateDecelerateInterpolator;
        // 先退后再加速前进
        AnticipateInterpolator anticipateInterpolator;
        // 先退后再加速前进,超出终点后再回终点
        AnticipateOvershootInterpolator anticipateOvershootInterpolator;
        // 最后阶段弹球效果
        BounceInterpolator bounceInterpolator;
        // 周期运动
        CycleInterpolator cycleInterpolator;
        // 减速
        DecelerateInterpolator decelerateInterpolator;
        // 匀速
        LinearInterpolator linearInterpolator;

样例

原作者出处:https://www.jianshu.com/p/2f19fe1e3ca1

  • 添加插值器:
        animation.setInterpolator(new BounceInterpolator());

系统默认的插值器是AccelerateDecelerateInterpolator,即先加速后减速

估值器:

协助插值器 实现非线性运动的动画效果
系统提供的估值器:

        // 以整型的形式从初始值 - 结束值 进行过渡
        IntEvaluator intEvaluator;
        // 以浮点型的形式从初始值 - 结束值 进行过渡
        FloatEvaluator floatEvaluator;
        // 以Argb类型的形式从初始值 - 结束值 进行过渡
        ArgbEvaluator argbEvaluator;

估值器一般无需选择,系统会帮我们配置好,ofInt() & ofFloat()都具备系统内置的估值器,即FloatEvaluator & IntEvaluator。

ValueAnimator

不多bb直接上用法

 ValueAnimator animator = ValueAnimator.ofInt(iv_play.getLayoutParams().width,600);
                animator.setDuration(1000);
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
                        int currentValue = (int) valueAnimator.getAnimatedValue();
                        iv_play.getLayoutParams().width = currentValue;
                        iv_play.requestLayout();
                    }
                });
                animator.start();

ofInt()传入动画的起始值,未指定插值器和估值器,都使用默认的AccelerateDecelerateInterpolator和IntEvaluator。addUpdateListener这个方法用于改变对象的属性值从而达到动—画的效果,比如demo中我要做一个iv变宽的效果,我就在addUpdateListener中不停的改变iv的width。

重点:ValueAnimator.ofObject()

这是一个可以操作对象实现动画效果的类
由于对象复杂多样,所以系统自带的估值器无法使用,需要我们自定义估值器。
ofObject操作对象动画的使用步骤:

  • 新建一个动画作用目标的类,
public class Point {

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

    // 构造方法用于设置坐标
    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    // get方法用于获取坐标
    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }
}
  • 根据需求自定义估值器:
    这里的demo的需求是将point移动
public class PointEvaluator implements TypeEvaluator {

    // 复写evaluate()
    // 在evaluate()里写入对象动画过渡的逻辑
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        // 将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;

        // 根据fraction来计算当前动画的x和y的值
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
        
        // 将计算后的坐标封装到一个新的Point对象中并返回
        Point point = new Point(x, y);
        return point;
    }

}

fraction是在0-1之间过度,用于描述动画的完成度,startValue是开始的Object(这里是点),endValue是结束的Object,最后返回当前fraction下动画的点。举个例子,
开始点是(1,1),结束点是(6,7),当fraction是0.25时,我们的点移动到了((6-1)0.25,(7-1)0.25)的坐标位置。

  • 将属性动画作用到自定义View当中
/**
 * Created by Carson_Ho on 17/4/18.
 */
public class MyView extends View {
    // 设置需要用到的变量
    public static final float RADIUS = 70f;// 圆的半径 = 70
    private Point currentPoint;// 当前点坐标
    private Paint mPaint;// 绘图画笔
    

    // 构造方法(初始化画笔)
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }

    // 复写onDraw()从而实现绘制逻辑
    // 绘制逻辑:先在初始点画圆,通过监听当前坐标值(currentPoint)的变化,每次变化都调用onDraw()重新绘制圆,从而实现圆的平移动画效果
    @Override
    protected void onDraw(Canvas canvas) {
        // 如果当前点坐标为空(即第一次)
        if (currentPoint == null) {
            currentPoint = new Point(RADIUS, RADIUS);
            // 创建一个点对象(坐标是(70,70))

            // 在该点画一个圆:圆心 = (70,70),半径 = 70
            float x = currentPoint.getX();
            float y = currentPoint.getY();
            canvas.drawCircle(x, y, RADIUS, mPaint);


             // (重点关注)将属性动画作用到View中
            // 步骤1:创建初始动画时的对象点  & 结束动画时的对象点
            Point startPoint = new Point(RADIUS, RADIUS);// 初始点为圆心(70,70)
            Point endPoint = new Point(700, 1000);// 结束点为(700,1000)

            // 步骤2:创建动画对象 & 设置初始值 和 结束值
            ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
            // 参数说明
            // 参数1:TypeEvaluator 类型参数 - 使用自定义的PointEvaluator(实现了TypeEvaluator接口)
            // 参数2:初始动画的对象点
            // 参数3:结束动画的对象点

            // 步骤3:设置动画参数
            anim.setDuration(5000);
            // 设置动画时长

            // 步骤3:通过 值 的更新监听器,将改变的对象手动赋值给当前对象
            // 此处是将 改变后的坐标值对象 赋给 当前的坐标值对象
            // 设置 值的更新监听器
            // 即每当坐标值(Point对象)更新一次,该方法就会被调用一次
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    currentPoint = (Point) animation.getAnimatedValue();
                    // 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint)
                    // 从而更新当前坐标值(currentPoint)

                    // 步骤4:每次赋值后就重新绘制,从而实现动画效果
                    invalidate();
                    // 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
                    // 所以坐标值每改变一次,就会调用onDraw()一次
                }
            });

            anim.start();
            // 启动动画


        } else {
            // 如果坐标值不为0,则画圆
            // 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果

            // 在该点画一个圆:圆心 = (30,30),半径 = 30
            float x = currentPoint.getX();
            float y = currentPoint.getY();
            canvas.drawCircle(x, y, RADIUS, mPaint);
        }
    }


}
效果图

你可能感兴趣的:(属性动画)