Android 动画基础知识学习(下)

学习资料:Android开发艺术探索Animation的api

1.属性动画

属性动画可以对任意对象的属性进行动画不仅仅是View,动画默认时间间隔是300ms,默认帧率是100ms/帧

作用:在一个时间间隔内完成对一个对象从属性值到另一个属性值的改变。

三个常用类:ValueAnimator,ObjectAnimator,AnimatorSet


属性动画

Java代码

private void initView() {
        Button bt = (Button) findViewById(R.id.bt_object_animation_activity);
        ImageView iv = (ImageView) findViewById(R.id.iv_object_animation_activity);

        //改变背景属性
        ValueAnimator colorAnim = ObjectAnimator.ofInt(iv, "backgroundColor", Color.parseColor("#FF4081"), Color.CYAN);
        colorAnim.setRepeatCount(2);
        colorAnim.setRepeatMode(ObjectAnimator.REVERSE);
        colorAnim.setDuration(1000);
        colorAnim.setEvaluator(new ArgbEvaluator());//估值器

        //动画集合
        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(iv, "rotationX", 0, 360),//绕x轴旋转360度
                ObjectAnimator.ofFloat(iv, "rotation", 0, -90),//逆时针旋转90度
                ObjectAnimator.ofFloat(iv, "translationX", 0, 90),//右移
                ObjectAnimator.ofFloat(iv, "scaleY", 1, 0.5f),//y轴缩放到一半
                ObjectAnimator.ofFloat(iv, "alpha", 1, 0.25f, 1)//透明度变换
        );
        //延迟一秒开始
        set.setStartDelay(1000);

        bt.setOnClickListener((v) -> {
            //改变属性 位置  向下移动iv高的二分之一
            ObjectAnimator.ofFloat(iv, "translationY", iv.getHeight() / 2).start();
            //背景属性改变开始
            colorAnim.start();
            //集合动画
            set.setDuration(3000).start();
        });
}

轴点默认为View的中心点。

常用的propertyName

  • rotationX 围绕x轴旋转
  • rotationY 围绕y轴旋转
  • rotation 围绕轴点旋转
  • translationX 在x轴方向上平移
  • translationY 在y轴方向上平移
  • scaleX 在x轴方向缩放
  • scaleY 在y轴方向缩放
  • alpha 透明度
  • width 宽度
  • height 高度

1.2 常用方法介绍

Animation的api

1.2.1 ObjectAnimator

  • ObjectAnimator.ofFloat(Object target, String propertyName, float... values)

Constructs and returns an ObjectAnimator that animates between float values.

返回一个根据方法内的具体的values创建的ObjectAnimator对象

  1. Object target ,目标控件View的对象
  2. String propertyName,属性的名字
  3. float... values ,根据具体需求需要的属性值

  • ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)

Constructs and returns an ObjectAnimator that animates between the sets of values specified in PropertyValueHolder objects

返回一个由PropertyValueHolder对象创建的ObjectAnimator对象

 public void byPropertyValuesHolder(ImageView iv) {
       PropertyValuesHolder pvh_1 = PropertyValuesHolder.ofFloat("rotationX", 0, 360);
       PropertyValuesHolder pvh_2 = PropertyValuesHolder.ofFloat("rotation", 0, -90);
       PropertyValuesHolder pvh_3 = PropertyValuesHolder.ofFloat("alpha", 1, 0.25f, 1);
       ObjectAnimator.ofPropertyValuesHolder(iv, pvh_1, pvh_2, pvh_3).setDuration(1000).start();
}

  • ofInt(Object target, String propertyName, int... values)

Constructs and returns an ObjectAnimator that animates between int values.

返回一个由int值属性创建的ObjectAnimator对象


  • setTarget(Object target)
    设置动画目标View

1.2.2 ValueAnimator

  • ValueAnimatorsetEvaluator(new ArgbEvaluator())
    设置估值器

  • addUpdateListener(ValueAnimator.AnimatorUpdateListener listener)

Adds a listener to the set of listeners that are sent update events through the life of an animation.

添加一个监听,可以用来在动画过程中改变属性


  • setRepeatCount(int num)
    设置动画的循环次数,默认为0,-1为无限循环

  • setStartDelay(long startDelay)
    设置动画开始的延迟时间


  • cancel()
    取消一个正在进行的动画。取消前,动画进行到哪个状态,取消后,就保持在那个状态。

  • end()
    结束动画。调用结束方法后,View会跳转到结束状态。如果动画设置了循环次数setRepeatCount()和重复模式setRepeatMode(ObjectAnimator.REVERSE),结束状态就要根据具体设置分析。


1.2.3 AnimatorSet

  • playTogether(Animator... items)

Sets up this AnimatorSet to play all of the supplied animations at the same time.

同时播放所有的Animator动画对象。


  • playSequentially(Animator... items)

Sets up this AnimatorSet to play each of the supplied animations when the previous animation ends.

顺序播放Animator动画对象

  • setInterpolator(TimeInterpolator interpolator)

Sets the TimeInterpolator for all current child animations of this AnimatorSet.

设置插值器


1.2.4 Animator

直接子类:AnimatorSetValueAnimator
间接子类:ObjectAnimatorTimeAnimator

Animator类的方法子类都可以直接使用。

  • addListener(Animator.AnimatorListener listener)

Adds a listener to the set of listeners that are sent events through the life of an animation, such as start, repeat, and end.

为动画添加一个监听过程的接口。如果不想实现AnimatorListener接口中的所有方法也可以继承AnimatorListenerAdapter

set.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animator) {
         toast("动画开始");
    }
    @Override
    public void onAnimationEnd(Animator animator) {
         toast("动画结束");
    }
    @Override
    public void onAnimationCancel(Animator animator) {
         toast("动画取消");
    }
    @Override
    public void onAnimationRepeat(Animator animator) {
        toast("动画重建");
    }
});

实现了接口中全部的方法。


继承AnimatorListenerAdapter:

set.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        toast("动画结束");
    }
});

根据实际需求,实现不同的方法。


  • addPauseListener(Animator.AnimatorPauseListener listener)

Adds a pause listener to this animator

为动画增加暂停监听


2.插值器和估值器

TimeInterpolator,时间插值器。用来根据时间流逝的百分比来计算出当前属性的值变化的百分比。

常用插值器:

名称 作用
LinearInterpolator 线性插值器。匀速动画
AccelerateDecelerateInterpolator 加速减速插值器
DecelerateInterpolator 匀减速插值器。动作越来越慢
BounceInterpolator 回弹插值器。到达平移后,回弹
CycleInterpolator 循环插值器。在两点间往还运动
PathInterpolator 路径插值器。根据单一方向定义的路径坐标运动
OvershootInterpolator 超越插值器。超出后,再返回来
AnticipateInterpolator 预期插值器。先反向运动再根据指定的方向运动

都是Interpolator的子类


TypeEvaluator,类型估值算法(估值器)。用来根据当前属性变化改变的百分比来计算改变后的属性值。

  • IntEvaluator,针对整型属性
  • FloatEvaluator,针对浮点型属性
  • ArgbEvaluator,针对Color属性

2.1简单Demo

模拟小球下落
private void initView() {
    Button bt = (Button) findViewById(R.id.bt_interpolator_activity);
    ImageView iv = (ImageView) findViewById(R.id.iv_interpolator_activity);
    LinearLayout root = (LinearLayout) findViewById(R.id.root_interpolator_activity);//根布局

    AnimatorSet set = new AnimatorSet();
    set.setInterpolator(new BounceInterpolator());
    set.setDuration(3000);
    //利用View的post方法拿到根布局的高度
    root.post(() -> {
        //计算下降高度
        int height = root.getHeight() - iv.getHeight() - bt.getHeight();
        //设置动画
        set.play(ObjectAnimator.ofFloat(iv, "translationY", height));
    });
        
    bt.setOnClickListener(v ->set.start());
    }

利用BounceInterpolator可以很方便的做出模拟小球下落的动画。也可以根据需求进行自定义插值器。


2.2 对任意属性做动画

Object的属性abc做动画,必须满足2个条件:

  1. object必须提供setAbc()的方法。如果动画的时候没有传递初始值,还要提供getAbc()方法。因为系统要去abc的初始值。如果不满足,程序直接Crash
  2. objectsetAbc对属性abc所做的改变必须能够通过某种方法反映出来,比如UI改变之类的。这条不满足,动画无效但程序不会Crash

2.2.1 改变Button的宽度

例如,想要利用属性对话来改变一个Button的宽度。

private void initView() {
    Button bt = (Button) findViewById(R.id.bt_button_activity);
    bt.setOnClickListener((v) -> performAnimate(bt));
}

private void performAnimate(Button bt) {
    ObjectAnimator.ofInt(bt, "width", 500).setDuration(1000).start();
}

实际测试,这段代码完全不起作用。


2.2.2不起作用的原因

Button继承的TextView

/**
 * Makes the TextView exactly this many pixels wide.
 * You could do the same thing by specifying this number in the
 * LayoutParams.
 *
 * @see #setMaxWidth(int)
 * @see #setMinWidth(int)
 * @see #getMinWidth()
 * @see #getMaxWidth()
 *
 * @attr ref android.R.styleable#TextView_width
 */
 @android.view.RemotableViewMethod
 public void setWidth(int pixels) {
    mMaxWidth = mMinWidth = pixels;
    mMaxWidthMode = mMinWidthMode = PIXELS;

    requestLayout();
    invalidate();
 }


 /**
  * Return the width of the your view.
  *
  * @return The width of your view, in pixels.
  */
  @ViewDebug.ExportedProperty(category = "layout")
  public final int getWidth() {
     return mRight - mLeft;
  }

getWidth()方法View的方法,是可以获取Button高度的。setWidth()方法TextView和子类的专属方法。是用来设置TextView的最大宽度和最小宽度的,并不是用来设置TextView的宽度的方法。TextView的宽度对应于XMLandroid:layout_widthsetWidth方法对应的是android:width

也就是说:ButtonsetWidth()getWidth()对应的就不是一个属性。只满足的条件1,不满足条件2


2.2.3 解决办法

有三种解决办法:

  • 如果有权限,给对象加上getset方法
  • 用一个类包装原始对象,间接提供getset方法
  • 采用ValueAnimation,监听动画过程,实现属性的改变

getset方法往往拿不到权限。

利用包装类方法:

private void initView() {
    Button bt = (Button) findViewById(R.id.bt_button_activity);
    bt.setOnClickListener((v) -> performAnimate(bt,bt.getWidth()));
}
    
 private void performAnimate(Button bt,int width) {
    ButtonWrapper wrapper = new ButtonWrapper(bt);
    wrapper.setWidth(width);
    ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(1000).start();
}

private static class ButtonWrapper {
    private View target;

    public ButtonWrapper(View target) {
        this.target = target;
    }

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

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

拿到Button的宽度后,设置给ButtonWrapper。这样动画开始后,Button会从原始大小开始变化。


利用ValueAnimation方法:

private void initView() {
    Button bt = (Button) findViewById(R.id.bt_button_activity);
       
    bt.setOnClickListener((v) -> performAnimate(bt,bt.getWidth(),500));
}
    
private void performAnimate(Button bt,int start, int end) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);

    //IntEvaluator对象,估值的时候使用
    IntEvaluator intEvaluator = new IntEvaluator();

    valueAnimator.addUpdateListener((animator -> {
        //获取当前动画的进度值 , 整型, 1到100
        int currentValue = (int) animator.getAnimatedValue();

        //获取当前进度的占整个动画过程的比例,浮点型, 0到1
        float fraction = animator.getAnimatedFraction();
        //直接利用整型估值器,通过比例计算宽度,然后Button设置
        bt.getLayoutParams().width = intEvaluator.evaluate(fraction,start,end);
        bt.requestLayout();
    }));
    //开启动画
    valueAnimator.setDuration(1000).start();
}
 

监控动画过程,利用IntEvaluator估值器


3.最后

动画基础知识大概介绍完,自定义TypeEvaluatorInterpolator以及配合自定义View更多高级的用法,以后再做补充。

接下来相当长的时间会用来学习自定义View。想通过一系列博客来记录学习,一开始先学习一些View的基础属性知识为学习自定义View做准备,再学习具体的测量,绘制过程,View的事件体系,工作原理。学习过程中间会加入继续对动画的深入学习,也可能会加入RxJava的后续学习或者其他的框架的学习。

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

推荐阅读更多精彩内容