属性动画

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位置。
  • 下面两行就不用讲了。设置时长和开始动画。


    参数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;

样例

原作者出处://www.greatytc.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);
        }
    }


}
效果图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容