Android动画(三):属性动画

一、View动画存在问题:就是动画移动后,点击动画的结束为止不会触发点击事件,当点击动画的起始位置,会触发点击事件。
而属性动画就不会,属性动画使用动画的效果得到了加强,不仅可以完全实现View动画的效果,更能实现更炫的效果。
属性动画是API11才有的,若想兼容以前的版本,可以使用开源的动画库nineoldandroids。

二、想要使用属性动画必须有两个条件:
1.第一个有参数的对象,友谊第二个参数为名字的成员变量
2.该成员变量必须有set,get方法,若没有的话,轻则无效,重则程序崩溃

三、属性:
Duration:动画的持续时间
TimeInterpolation:属性值的计算方式,如先快后慢
TypeEvaluator:根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值
Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以此动画一直重复,或播放完时再反向播放
Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响

四、ObjectAnimator官方文档:
extends ValueAnimator
This subclass of ValueAnimator provides support for animating properties on target objects. The constructors of this class take parameters to define the target object that will be animated as well as the name of the property that will be animated. Appropriate set/get functions are then determined internally and the animation will call these functions as necessary to animate the property.
Animators can be created from either code or resource files, as shown here:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueTo="200"
    android:valueType="floatType"
    android:propertyName="y"
    android:repeatCount="1"
    android:repeatMode="reverse"/>

When using resource files, it is possible to use PropertyValuesHolder and Keyframe to create more complex animations. Using PropertyValuesHolders allows animators to animate several properties in parallel, as shown in this sample:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="1000"
                android:repeatCount="1"
                android:repeatMode="reverse">
    <propertyValuesHolder android:propertyName="x" android:valueTo="400"/>
    <propertyValuesHolder android:propertyName="y" android:valueTo="200"/>
objectAnimator>

Using Keyframes allows animations to follow more complex paths from the start to the end values. Note that you can specify explicit fractional values (from 0 to 1) for each keyframe to determine when, in the overall duration, the animation should arrive at that value. Alternatively, you can leave the fractions off and the keyframes will be equally distributed within the total duration. Also, a keyframe with no value will derive its value from the target object when the animator starts, just like animators with only one value specified. In addition, an optional interpolator can be specified. The interpolator will be applied on the interval between the keyframe that the interpolator is set on and the previous keyframe. When no interpolator is supplied, the default linear interpolator will be used.

<propertyValuesHolder android:propertyName="x" >
    <keyframe android:fraction="0" android:value="800" />
    <keyframe android:fraction=".2"
              android:interpolator="@android:anim/accelerate_interpolator"
              android:value="1000" />
    <keyframe android:fraction="1"
              android:interpolator="@android:anim/accelerate_interpolator"
              android:value="400" />
propertyValuesHolder>
<propertyValuesHolder android:propertyName="y" >
    <keyframe/>
    <keyframe android:fraction=".2"
              android:interpolator="@android:anim/accelerate_interpolator"
              android:value="300"/>
    <keyframe android:interpolator="@android:anim/accelerate_interpolator"
              android:value="1000" />
propertyValuesHolder>

大致意思:
  ObjectAnimator继承ValueAnimator
  这个ValueAnimator子类给动画的目标对象上的属性提供支持。获取这类的构造方法的参数,定义目标对象的参数,使动画以及属性的名称更形象动画。适当的set/get方法决定了内部和动画会调用这些方法作为必要的动画属性。
  动画可以从创建代码或资源文件,如下所示:

  < objectAnimator xmlns:android = " http://schemas.android.com/apk/res/android "
  android:duration= " 1000 "
  android:valueTo = " 200 "
  android:valueType = " floatType "
  android:propertyName = " y "
  android:repeatCount = " 1 "
  android:repeatMode = "reverse" / >

  当使用资源文件时,可以使用PropertyValuesHolder和Keyframe 来创建更复杂的动画。使用并行PropertyValuesHolders允许生动的动画几个属性,如本例所示:
  

< objectAnimator xmlns:android = " http://schemas.android.com/apk/res/android "
  android:duration= " 1000 "
  android:repeatCount = " 1 "
  android:repeatMode =“反向”>
  < propertyValuesHolder android:propertyName = " x " android:valueTo = " 400 " / >
  < propertyValuesHolder android:propertyName = " y " android:valueTo = " 200 " / >
  < / objectAnimator >

使用关键帧允许动画可以遵循更复杂的路径从开始到结束的值。注,您可以指定明确的值(从0到1)对于每一个关键帧时确定,在整个持续时间中,动画应该到达这个值。或者,你可以把分割和关键帧内均匀分布的总时间。同样,当动画开始的时候,一个没有值的关键帧将从目标对象获得它的值,就像动画只有一个值指定。另外,可以指定一个可选的插入器。插入器将应用于插入器的关键帧之间的时间间隔,是设置在之前的关键帧。当没有提供插入器,将使用默认的线性差值器。

<propertyValuesHolder android:propertyName="x" >
    <keyframe android:fraction="0" android:value="800" />
    <keyframe android:fraction=".2"
              android:interpolator="@android:anim/accelerate_interpolator"
              android:value="1000" />
    <keyframe android:fraction="1"
              android:interpolator="@android:anim/accelerate_interpolator"
              android:value="400" />
propertyValuesHolder>
<propertyValuesHolder android:propertyName="y" >
    <keyframe/>
    <keyframe android:fraction=".2"
              android:interpolator="@android:anim/accelerate_interpolator"
              android:value="300"/>
    <keyframe android:interpolator="@android:anim/accelerate_interpolator"
              android:value="1000" />
propertyValuesHolder>

对于ObjectAnimator简单介绍:
1、提供了ofInt、ofFloat方法,这几个方法都是设置动画作用的元素、作用的属性、动画开始、结束、以及中间的任意个属性值。
这里第一个参数要求传入一个object对象,传入一个想要操作的对象。第二个参数是想要对该对象的哪个属性进行动画操作。后面的参数就是可变长数组了,根据需要传入相应的值。
而ofObject方法需要一个差值器,后面我们还介绍的。

2.当对于属性值,只设置一个的时候,会认为当然对象该属性的值为开始(getPropName反射获取),然后设置的值为终点。如果设置两个,则一个为开始、一个为结束。若设置多个,则会连续执行。
动画更新的过程中,会不断调用setPropName更新元素的属性,所有使用ObjectAnimator更新某个属性,必须得有getter(设置一个属性值的时候)和setter方法。

3.初始化需要指定对象和对象的属性以及值区间。
有些改变需要调用invalidate()方法,重绘。可以在onAnimationUpdate()方法中做。

五、Java代码中使用示例:

1.改变textView的背景颜色:

private void showColor() {
    //设置颜色变化
    ObjectAnimator objectAnimator = ObjectAnimator.ofInt(textView, "textColor", 0xFFFF8080, 0xFF8080FF);
    //无限循环
    objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
    objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
    //设置为色差估值器
    objectAnimator.setEvaluator(new ArgbEvaluator());
    //设置持续时间
    objectAnimator.setDuration(500);
    //开启动画
    objectAnimator.start();
}

2.平移textView:

private void showMove() {
    //设置x平移
    ObjectAnimator obj = ObjectAnimator.ofFloat(textView, "translationX", 50, -50, 30, -30, 20, -20, 10, -10, 5, -5, 1, -1);
    //设置差值器
    obj.setInterpolator(new DecelerateInterpolator());
    //设置持续时间
    obj.setDuration(5000);
    //开启动画
    obj.start();
}

3.联合使用动画:

private void showAll() {
    AnimatorSet set = new AnimatorSet();
    set.playTogether(
            //textView的X轴旋转360°
            ObjectAnimator.ofFloat(textView, "rotationX", 0, 360),
            //textView的Y轴旋转180°
            ObjectAnimator.ofFloat(textView, "rotationY", 0, 180),
            //设置平面旋转90°
            ObjectAnimator.ofFloat(textView, "rotation", 0, 90),
            //设置X轴的位移
            ObjectAnimator.ofFloat(textView, "translationX", 0, 90),
            //设置Y轴位移
            ObjectAnimator.ofFloat(textView, "translationY", 0, 90),
            //设置X轴缩放1.5倍
            ObjectAnimator.ofFloat(textView, "scaleX", 1, 1.5f),
            //设置Y轴缩放0.5倍
            ObjectAnimator.ofFloat(textView, "scaleY", 1, 0.5f),
            //设置透明度
            ObjectAnimator.ofFloat(textView, "alpha", 1, 0.5f, 1)
    );
    //设置持续时间
    set.setDuration(5000);
    //开启动画
    set.start();
}

六、插值器简单介绍:( TimeInterpolator)
AccelerateInterpolator 加速插值器
AccelerateDecelerateInterpolator 加速减速插值器
LinearInterpolator 线性插值器
BounceInterpolator 弹跳插值器
AnticipateInterpolator 回荡秋千插值器
AnticipateOvershootInterpolator开始的时候向后然后向前甩一定值后返回最后的值
CycleInterpolator 正弦周期变化插值器
OvershootInterpolator 向前甩一定值后再回到原来位置

七、可以看见我们上面并没有使用set和get方法,主要是因为textView的可以直接获取到属性,任何继承自View的对象都可以的。而我们下面要讲的是,当不能直接获取属性的时候,我们应该怎么办?

一共有3种解决方法:
1. 给你的对象加上get和set方法,如果你有权限的话
2. 用一个类来包装原始对象,间接为其提供get和set方法
3. 采用ValueAnimator,监听动画过程,自己实现属性的改变

八、我们使用第二种方式来实现:
1.首先获取到button的宽度:

testBtn.post(new Runnable() {
    @Override
    public void run() {
        width = testBtn.getWidth();
    }
});

顺便介绍一下post()方法:

官方文档:Causes the Runnable to be added to the message queue. The runnable will be run on the user interface thread.
Parameters
action
The Runnable that will be executed.
Returns
Returns true if the Runnable was successfully placed in to the message queue. Returns false on failure, usually because the looper processing the message queue is exiting.

大致意思:
  是runnable可被添加到消息队列。runnable可将用户界面线程上运行。
  参数:
  action:  runnable可被执行。
  Returns:  返回true,如果运行的成功放在消息队列。如果执行失败将返回false,这通常是因为looper退出处理消息队列。

2.MyButton类:


public class MyButton {

    private View button;

    public MyButton(View button) {
        this.button = button;
    }

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

    public void setWidth(int width) {
        button.getLayoutParams().width = width;
        //requestLayout:当view确定自身已经不再适合现有的区域时,
        // 该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。
       // 特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。
        //而,invalidate:View本身调用迫使view重画。
        button.requestLayout();
    }
}

3.MainActivity中使用:

MyButton b = new MyButton(testBtn);
ObjectAnimator.ofInt(b, "width", width, 500).setDuration(3000).start();

九、以上就是对属性动画做出的简单介绍,现在写出所有的代码,

MainActivity中:


public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button colorBtn, moveBtn, allBtn, testBtn;
    private TextView textView;
    int width;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.tv);
        colorBtn = (Button) findViewById(R.id.colorBtn);
        moveBtn = (Button) findViewById(R.id.moveBtn);
        allBtn = (Button) findViewById(R.id.allBtn);
        testBtn = (Button) findViewById(R.id.testBtn);
        colorBtn.setOnClickListener(this);
        moveBtn.setOnClickListener(this);
        allBtn.setOnClickListener(this);
        testBtn.setOnClickListener(this);


        testBtn.post(new Runnable() {
            @Override
            public void run() {
                width = testBtn.getWidth();
            }
        });
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.colorBtn:
                //改变textView的背景颜色
                showColor();
                break;
            case R.id.moveBtn:
                //平移textView
                showMove();
                break;
            case R.id.allBtn:
                //联合使用动画
                showAll();
                break;

            case R.id.testBtn:
                Toast.makeText(this, "dianji", Toast.LENGTH_SHORT).show();
                MyButton b = new MyButton(testBtn);
                ObjectAnimator.ofInt(b, "width", width, 500).setDuration(3000).start();

                break;
        }
    }

    private void showColor() {
        //设置颜色变化
        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(textView, "textColor", 0xFFFF8080, 0xFF8080FF);
        //无限循环
        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
        objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
        //设置为色差估值器
        objectAnimator.setEvaluator(new ArgbEvaluator());
        //设置持续时间
        objectAnimator.setDuration(500);
        //开启动画
        objectAnimator.start();
    }

    private void showMove() {
        //设置x平移
        ObjectAnimator obj = ObjectAnimator.ofFloat(textView, "translationX", 50, -50, 30, -30, 20, -20, 10, -10, 5, -5, 1, -1);
        //设置差值器
        obj.setInterpolator(new DecelerateInterpolator());
        //设置持续时间
        obj.setDuration(5000);
        //开启动画
        obj.start();
    }


    private void showAll() {
        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                //textView的X轴旋转360°
                ObjectAnimator.ofFloat(textView, "rotationX", 0, 360),
                //textView的Y轴旋转180°
                ObjectAnimator.ofFloat(textView, "rotationY", 0, 180),
                //设置平面旋转90°
                ObjectAnimator.ofFloat(textView, "rotation", 0, 90),
                //设置X轴的位移
                ObjectAnimator.ofFloat(textView, "translationX", 0, 90),
                //设置Y轴位移
                ObjectAnimator.ofFloat(textView, "translationY", 0, 90),
                //设置X轴缩放1.5倍
                ObjectAnimator.ofFloat(textView, "scaleX", 1, 1.5f),
                //设置Y轴缩放0.5倍
                ObjectAnimator.ofFloat(textView, "scaleY", 1, 0.5f),
                //设置透明度
                ObjectAnimator.ofFloat(textView, "alpha", 1, 0.5f, 1)
        );
        //设置持续时间
        set.setDuration(5000);
        //开启动画
        set.start();
    }

}

MainActivity的布局文件:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.mac.propertydemo.MainActivity">

    <Button
        android:id="@+id/colorBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="属性颜色动画" />

    <Button
        android:id="@+id/moveBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/colorBtn"
        android:text="属性移动动画" />

    <Button
        android:id="@+id/allBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/moveBtn"
        android:text="属性集合动画" />


    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/colorBtn"
        android:text="Hello World!"
        android:textSize="30sp" />

    <Button
        android:id="@+id/testBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv"
        android:text="测试" />

RelativeLayout>

MyButton类:

public class MyButton {

    private View button;

    public MyButton(View button) {
        this.button = button;
    }

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

    public void setWidth(int width) {
        button.getLayoutParams().width = width;
        //requestLayout:当view确定自身已经不再适合现有的区域时,
        // 该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。
       // 特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。
        //而,invalidate:View本身调用迫使view重画。
        button.requestLayout();
    }
}

本人菜鸟一个有什么不对的地方希望大家指出评论,大神勿喷,希望大家一起学习进步!

你可能感兴趣的:(Android)