前言:又一个礼拜过去了,原本定下的目标又被落在了脑后,但是懊悔已没什么用,收收心,再向目标前进吧! 等目标被一个一个实现的时候,那才是真正收获的时刻,加油!!

时间过去了这么久了,都忘了前两篇博客中都讲了什么了,现在我们先来回顾一下:我们在Android属性动画详解(上),初始属性动画的基本用法中讲解了属性动画的几种常见的操作(淡入淡出、缩放、平移、旋转等),在 Android属性动画解析(中),ValueAnimator和ObjectAnimator的高级用法中我们详细的介绍了属性动画的高级用法,主要从对对象的以及对View背景的操作给出了详细的例子,有忘记的可以回头去看看。

今天我们主要将的内容包括以下两点:InterPolator以及ViewPropertyAnimator的用法

InterPolator的用法

InterPolator这个东西很难进行翻译,从字面上直接翻译过来是插入器的意思,它的主要作用是用来控制动画的变化速率的,比如我们想要实现一种非线性运动的动画效果,那么什么是非线性的动画效果呢?其实也很好理解,就是说动画改变的速率不是一层不变的,像加速运动以及减速运动都属于非线性运动,不过InterPolator并不是属性动画中新增的技术,实际上从Android1.0版本就已经存在InterPolator这一接口了,也就是说之前的补间动画也是支持这一功能的,只不过在属性动画中新增了TimeInterPolator这一接口,而这个接口是兼容之前的InterPolator这个接口的,这使得之前InterPolator的所有实现类我们都可以直接拿过来放到属性动画中使用,下面我们就来看看TimeInterPolator的所有实现类:

可以看到的是,TimeInterPolator接口已经有很多的实现类了,包括我们的InterPolator都是TimeInterPolator的实现类,每个InterPolator都有它各自的实现效果,比如说AccelerateInterPolator就是一个加速运动的InterPolator,而DecelerateInterPolator则是一个减速运动的InterPolator

不知道大家还记不记的前面我们在第一遍博客中介绍属性动画的ValueAnimator时,使用ValueAnimator的ofFloat对0f-1f的值进行了打印,给大家看下图:

细心的朋友可能早就发现了,这个值的过渡做的其实就不是一个线性运动,我们可以明显的看到值的递增并不是有规律的向上递增的,刚开始值的递增明显比较快,而后面从0.90到1.0之间明显比前面要慢上许多,因此能我们可以认为这是一个先加速后减速的InterPolator,大家可能觉得我们的结论下的过早了,没事,我们还可以看看我们前面完成的小球变色的功能,如下图所示:

从这个操作中我们也可以很明显的看到,刚开始小球的运动速度是非常的快的,但是当快要到底的时候,速度明显的慢了许多,最后缓缓停住,另外颜色的变化也是一样,刚开始颜色的变化比较慢,到后面颜色的变化越来越快,到最后又变得越来越慢。

因此,从上面的几点我们就可以得出一个结论,使用属性动画时,系统默认的InterPolator其实就是一个先加速后减速的InterPolator,而对应的实现类其实就是我们的AccelerateDecelerateInterPolator。

当然,如果我们想使用其他的效果的InterPolator也是可以的,我们这里就拿“中”篇文章中的代码来举例吧,我们在原有的基础上(PointView)拷贝一份出来(AccelerateInterpolatorPointView),然后对Point对象的坐标稍微修改一下,将原有的效果改为垂直掉落的效果:
看代码:

...private void startAnimation() {        //初始化初始位置的Point以及结束位置的Point        Point startPoint = new Point(getWidth() / 2, RADIUS);        Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);        ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvalutor(), startPoint, endPoint);        //添加监听,用于更新point坐标        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mPoint = (Point) animation.getAnimatedValue();                //通知动画系统重新绘制                invalidate();            }        });        valueAnimator.setInterpolator(new AccelerateInterpolator(2f));        valueAnimator.setDuration(5000).start();    } ...

可以看到我们这里仅仅是对startAnimation这个方法进行了改变,将起始的Point的坐标初始位置放到了屏幕的正中间,将结束的Point的位置也放到了屏幕底部的正中间,其他地方都一样,我们通过使用ValueAnimator 的setInterpolator方法设置了一个加速运动的Interpolator(AccelerateInterpolator)是不是很简单???
看效果图:

OK,效果是不是很明显,可以明显看到小球的下落速度是越来越快,这就说明我们已经替换掉了系统默认的InterPolator,AccelerateInterPolator确实是已经生效了,但是现在的动画效果有没有感觉怪怪的?按理说一个小球从高处落下掉到地面上是不可能马上就静止的,它肯定会有一个回弹的效果,经过反复的回弹,最后静止。这才是比较符合逻辑的,那么这个效果我们怎么实现呢?其实这个效果我们是可以自己来实现的,但是,所幸的是,这个效果的InterPolator系统已经为我们实现了,我们只需要拿过来直接用就可以了,来我们试着做一下:

...private void startAnimation() {        //初始化初始位置的Point以及结束位置的Point        Point startPoint = new Point(getWidth() / 2, RADIUS);        Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);        ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvalutor(), startPoint, endPoint);        //添加监听,用于更新point坐标        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mPoint = (Point) animation.getAnimatedValue();                //通知动画系统重新绘制                invalidate();            }        });        valueAnimator.setInterpolator(new BounceInterpolator());        valueAnimator.setDuration(5000).start();    } ...

代码还是一样的代码,这里只不过是将valueAnimator.setInterpolator(new AccelerateInterpolator(2f));替换成了valueAnimator.setInterpolator(new BounceInterpolator());
看效果:

OK,效果还是相当不错的,那么几种常见的InterPolator到这里我们就已经讲完了,由于内置的InterPolator比较多,这里我们就不一个一个说了,喜欢研究的可以自己去试一下其他的InterPolator的效果

但是,只会用一下系统提供好的InterPolator,我们显然对自己的要求太低了,既然是学习属性动画的高级用法,那么自然要将它研究透了,下面我们就来看一下InterPolator的内部实现机制是怎样的吧,并且来尝试写一个自定义的InterPolator。
首先看一下TimeInterPolator的接口定义,代码如下所示:

package android.animation;/** * A time interpolator defines the rate of change of an animation. This allows animations * to have non-linear motion, such as acceleration and deceleration. */public interface TimeInterpolator {    /**     * Maps a value representing the elapsed fraction of an animation to a value that represents     * the interpolated fraction. This interpolated value is then multiplied by the change in     * value of an animation to derive the animated value at the current elapsed animation time.     *     * @param input A value between 0 and 1.0 indicating our current point     *        in the animation where 0 represents the start and 1.0 represents     *        the end     * @return The interpolation value. This value can be more than 1.0 for     *         interpolators which overshoot their targets, or less than 0 for     *         interpolators that undershoot their targets.     */    float getInterpolation(float input);}

OK,可以看到其实这个接口还是非常简单的,仅仅只有一个getInterpolation方法,有兴趣的朋友可以通过注释来对这个接口进行了解。在这里我就简单的解释一下,getInterpolation方法中接受一个input参数,这个参数的值会随着动画的运行而不断变化,不过它的变化是非常有规律的,就是根据设定的动画的时长匀速增加,变化范围是0到1,也就是说当动画刚开始运行时,input的值为0,到动画结束后,input的值为1,而中间的值则是随着动画运行时长在0和1之间进行变化。
其实说到这里,大家可能就疑惑了,input的值是0到1之间的,而我们在上篇文章中也提到过fraction其实也是在0到1之间的,那么他们两者之间是不是有什么联系呢?答案很简单,其实input的值决定了fraction的值,说白了input的值是由系统计算后传入到getInterPolator()方法中,然后我们可以自己实现getInterPolator()方法中的算法,根据input的值来计算出一个返回值,而这个值就是fraction。

因此,最简单的情况就是input的值和fraction的值是一样的,这种情况下由于input的值是匀速增加的,因此fraction的值也是匀速增加的,所以动画的运动效果也就是匀速增加的,不信我们可以看一下系统内置的一个匀速运动的InterPolator(LinearInterpolator)的getInterPolator方法的实现:

package android.view.animation;import android.content.Context;import android.util.AttributeSet;import com.android.internal.view.animation.HasNativeInterpolator;import com.android.internal.view.animation.NativeInterpolatorFactory;import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;/** * An interpolator where the rate of change is constant */@HasNativeInterpolatorpublic class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {    public LinearInterpolator() {    }    public LinearInterpolator(Context context, AttributeSet attrs) {    }    public float getInterpolation(float input) {        return input;    }    /** @hide */    @Override    public long createNativeInterpolator() {        return NativeInterpolatorFactoryHelper.createLinearInterpolator();    }}

这里我们暂时只看getInterPolator方法,可以看到这个方法完全没有任何逻辑而言,就是把input这个参数给直接返回了,因此fraction的值就等于input的值,这就是匀速运算的InterPolator的实现方式。

当然,这是最简单的InterPolator的,我们在来看一个稍微复杂点的InterPolator,既然大家都知道系统默认的InterPolator是AccelerateDecelerateInterPolator,那我们就来看看它的getInterPolator方法是怎么实现的吧:

package android.view.animation;import android.content.Context;import android.util.AttributeSet;import com.android.internal.view.animation.HasNativeInterpolator;import com.android.internal.view.animation.NativeInterpolatorFactory;import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;/** * An interpolator where the rate of change starts and ends slowly but * accelerates through the middle. */@HasNativeInterpolatorpublic class AccelerateDecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {    public AccelerateDecelerateInterpolator() {    }    @SuppressWarnings({"UnusedDeclaration"})    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {    }    public float getInterpolation(float input) {        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;    }    /** @hide */    @Override    public long createNativeInterpolator() {        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();    }}

代码虽然没有变化很多,但是getInterPolator方法中的逻辑明显复杂了许多,不再是简单的将参数的input返回,而是进行了复杂的数学运算,
这里我们就来分析一下它的算法实现,可以看到,算法中主要使用了余弦函数,由于input的取值范围是0到1,那么cos函数中的取值范围就是π到2π,而cos(π)的结果是-1,cos(2π)的结果是1,那么这个值除以2在加上0.5之后,getInterPolator方法最终返回的结果还是0到1之间,只不过经过余弦运算之后,最终的结果不在是匀速增加了,而是经历了一个先减速后加速的过程,我们可以将这个算法的运行情况通过曲线图的方式绘制出来,结果如下图所示:

可以看到这是一个S型的曲线图,当横坐标从0到0.2的时候,纵坐标的变化幅度比较小,但是从0.2开始开始明显加速,最后在0.8到1的时候速度有明显慢了下来

OK,通过分析LinearInterpolator和AccelerateDecelerateInterpolator,我们已经对InterPolator的内部实现机制有了一个比较清楚的认识,那么接下来我们就开始编写一个自定义的InterPolator吧。
其实自定义InterPolator并不难,主要难点在于数学计算方面,对于数学好的同学可能编写一个InterPolator相当简单,由于本人数学并不是很好,因此这里也就写一个简单的InterPolator来给大家做一个演示,既然属性动画默认的InterPolator是一个先加速后减速的这么一个效果,那么我们就来对它进行一下小小的修改,让它变成先减速后加速的方法,新建DecelerateAccelerateInterpolator类,让它实现TimeInterPolator接口,代码如下所示:

package demo.mk.com.valueanimatordemo.InterPolator;import android.animation.TimeInterpolator;/** * Created by Lenovo-MK on 2016/1/14. */public class DecelerateAccelerateInterpolator implements TimeInterpolator {    @Override    public float getInterpolation(float input) {        float result;        if (input <= 0.5) {            result = (float) (Math.sin(Math.PI * input)) / 2;        } else {            result = (float) (2 - Math.sin(Math.PI * input)) / 2;        }        return result;    }}

代码也非常简单,使用的是正弦函数来实现的先减速后加速的功能的,因为正弦函数初始弧度的变化值非常大,刚好和余弦函数是相反的,而随着弧度的增加,正弦函数的变化值也会逐渐变小,这样就实现了减速的效果,当弧度大于π/2之后,整个过程相反了过来,现在正弦函数弧度的变化值非常小,渐渐随着弧度继续增加,变化值越来越大,弧度到π时结束,这样就从0过渡到了π,也就实现了先减速后加速的效果,同样我们可以讲这个算法的执行情况通过曲线图的方式绘制出来,结果如下图所示:

可以看到,这也是一个S型的曲线,只不过曲线的方向和刚才的是反的,从图中我们可以清晰的看到,刚开始纵坐标的变化幅度比较大,然后逐渐变小,横坐标到0.5的时候纵坐标变化幅度机会接近于零,之后随着横坐标继续增加横坐标的变化幅度又开始变大,的确是先减速后加速的效果,现在我们就来用一下我们自定义的InterPolator吧:

private void startAnimation() {        //初始化初始位置的Point以及结束位置的Point        Point startPoint = new Point(getWidth() / 2, RADIUS);        Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);        ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvalutor(), startPoint, endPoint);        //添加监听,用于更新point坐标        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mPoint = (Point) animation.getAnimatedValue();                //通知动画系统重新绘制                invalidate();            }        });// valueAnimator.setInterpolator(new BounceInterpolator());        valueAnimator.setInterpolator(new DecelerateAccelerateInterpolator());        valueAnimator.setDuration(5000).start();    }

非常简单,就是将DecelerateAccelerateInterpolator实例传入到setInterpolator方法中,运行下代码效果显示如下:

ViewPropertyAnimator的用法

ViewpropertyAnimator其实并不是什么高级技巧,它的用法格外简单,只不过和前面我们一直提到的属性动画的知识不同,它并不是在3.0系统中引入的,而是在3.1系统当中附赠的一个新功能,我们都知道,属性动画的机制已经不单单是针对view进行设计的了,而是一种不断对值进行操作的机制,它可以讲值赋值到指定对象的指定属性上,但是,大多数情况下,相信大家主要还是对view进行动画操作的,而Android开发团队也意识到了这一点,没有对view的动画提供一种更加便捷的用法确实是有些不任性化了,于是在Android3.1系统当中补充了ViewPropertyAnimator这个机制。

我们先来回顾一下我们之前使用ObjectAnimator时是怎么对一个view进行淡入淡出动画操作的:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f); 02.animator.start(); 

代码看上去复杂么?其实习惯了也就不感觉到复杂了,但是却有点不是很好理解,我们要对一个view进行动画操作,需要将要操作的view、属性、变化的值都一起传入到ObjectAnimator.ofFloat方法中,虽然代码没有几行,但是感觉好像违背了我们之前的面向对象的思维?有没有?
下面我们就来看一个ViewPropertyAnimator怎么实现同样的效果,ViewPropertyAnimator提供了更加易懂,更加面向对象的API:

textview.animate().alphe(0f);

是不是看起来比之前简化了很多?animate()方法就是3.1系统新增的方法,这个方法的返回值是ViewPropertyAnimator对象,也就是说拿到这个对象之后,我们就可以它的各种方法来实现动画效果了,这里我们调用了alpha()方法并传入0,表示将当前的textview透明的变成0.
除此之外,ViewPropertyAnimator还可以将多个动画组合到一起,比如我们想让view运动到500,500这个坐标上就可以这样写:

textview.animate().x(500).y(500);

可以看出ViewPropertyAnimator是支持连缀用法的,我们想让textview移动到横坐标500这个位置上调用了x(500)这个方法,然后让textview移动到纵坐标500这个位置上调用了y(500),将所有想要组合的动画通过这种连缀的方式拼接起来,这样全部动画就会一起被执行

设置动画时长:

textview.animate().x(500).y(500).setDuration(5000); 

设置InterPolator:

textview.animate().x(500).y(500).setDuration(5000).setInterpolator(new BounceInterpolator()); 

好了,ViewPropertyAnimator的基本用法我们就已经讲完了,下面我们就来说一下ViewPropertyAnimator的几个需要注意的细节:

  • 整个ViewPropertyAnimator的功能都是建立在view类新增的animate()方法上的,这个方法会创建并返回一个ViewPropertyAnimator的实例,之后调用所以的方法,设置的所有的属性都是通过这个实例完成的。
  • 大家注意到,在使用ViewPropertyAnimator时,我们自始至终没有调用过start()方法,这是因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成后,动画就会自动启动。并且这个动画对于组合动画也同样有用,只要我们不断地连缀新的方法,那么动画就不会立即执行,等所有在ViewPropertyAnimator设置的动画都执行完毕后,动画就会自动启动,当然如果不想使用这一默认机制的话,我们也可以显示地调用start()方法来启动动画。
  • ViewPropertyAnimator的所有接口都是使用连缀的语法来设计的,每个方法的返回值都是它自身的实例,因此没调完一个方法后可以继续调用它的其他方法,这样就能把所有的功能都串起来了,我们甚至可以仅仅通过一行代码就完成任意复杂度的动画功能,但是阅读性会不会很差就不好说了。

布局动画(LayoutAnimator)

基本使用LayoutTransition为布局的容器设置动画,当容器中的视图层次发生变化时,存在的过渡动画效果。
先看下基本用法,下面会详细讲解:

LayoutTransition transition = new LayoutTransition();transition.setAnimator(LayoutTransition.CHANGW_APPEARING,transition.getAnimator(LayoutTransiton.CHANGE_APPEARING));transition.setAnimator(LayoutTransition.APPEARING,null);transition.setAnimator(LayoutTransition.DISAPPEARING,null);transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,null);mGridLayout.setLayoutTransition(transition);

过渡的类型一共分为四种:

  • LayoutTransition.CHANGE_APPEARING表示一个view在ViewGroup中出现时,对其他View位置造成影响,对其他View设置的动画
  • LayoutTransition.APPEARING 表示一个View在ViewGroup中出现时,对此View设置的动画
  • LayoutTransition.DISAPPEARING 表示一个View在ViewGroup中消失时,对此View设置的动画
  • LayoutTransition.CHANGE_DISAPPEARING表示一个View在ViewGroup中消失时,对其他View位置造成影响,对其他View设置的动画
package demo.mk.com.valueanimatordemo;import android.animation.LayoutTransition;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.CheckBox;import android.widget.CompoundButton;import android.widget.GridLayout;import android.widget.LinearLayout;/** * Created by Lenovo-MK on 2015/12/26. */public class ObjectAnimator12 extends Activity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {    private Button bt_addView;    private CheckBox layouttransiton_change_appearing, layouttransiton_appearing, layouttransiton_change_disappearing, layouttransiton_disappearing;    private GridLayout mGridLayout;    private LinearLayout ll_content;    private int mVal;    private LayoutTransition mTransition;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.layout_animator);        ll_content = (LinearLayout) findViewById(R.id.ll_content);        bt_addView = (Button) findViewById(R.id.bt_addView);        layouttransiton_change_appearing = (CheckBox) findViewById(R.id.LayoutTransiton_CHANGE_APPEARING);        layouttransiton_appearing = (CheckBox) findViewById(R.id.LayoutTransiton_APPEARING);        layouttransiton_change_disappearing = (CheckBox) findViewById(R.id.LayoutTransiton_CHANGE_DISAPPEARING);        layouttransiton_disappearing = (CheckBox) findViewById(R.id.LayoutTransiton_DISAPPEARING);        bt_addView.setOnClickListener(this);        layouttransiton_change_appearing.setOnCheckedChangeListener(this);        layouttransiton_appearing.setOnCheckedChangeListener(this);        layouttransiton_change_disappearing.setOnCheckedChangeListener(this);        layouttransiton_disappearing.setOnCheckedChangeListener(this);        //用于存放view的GridLayout        mGridLayout = new GridLayout(this);        //设置每列5个按钮        mGridLayout.setColumnCount(5);        //添加到主布局        ll_content.addView(mGridLayout);        mTransition = new LayoutTransition();        mGridLayout.setLayoutTransition(mTransition);    }    @Override    public void onClick(View v) {        final Button button = new Button(this);        button.setText((++mVal) + "");        mGridLayout.addView(button, Math.min(1, mGridLayout.getChildCount()));        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                mGridLayout.removeView(button);            }        });    }    @Override    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {// //初始化布局动画// if (mTransition == null) {            mTransition = new LayoutTransition();// }        //CHANGE_APPEARING View在ViewGroup出现时,对其他View的位置造成影响,对其他view设置的动画        mTransition.setAnimator(                LayoutTransition.CHANGE_APPEARING,                layouttransiton_change_appearing.isChecked() ? mTransition.getAnimator(LayoutTransition.CHANGE_APPEARING) : null);        //APPEARING View在ViewGroup中出现时,对此View设置的动画        mTransition.setAnimator(                LayoutTransition.APPEARING,                layouttransiton_appearing.isChecked() ? mTransition.getAnimator(LayoutTransition.APPEARING) : null);        //CHANGE_DISAPPEARING View在ViewGroup中消失时,对其他View的位置造成影响,对其他View设置的动画        mTransition.setAnimator(                LayoutTransition.CHANGE_DISAPPEARING,                layouttransiton_change_disappearing.isChecked() ? mTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING) : null);        //DISAPPEARING View在ViewGroup中消失时,对此View设置的动画        mTransition.setAnimator(                LayoutTransition.DISAPPEARING,                layouttransiton_disappearing.isChecked() ? mTransition.getAnimator(LayoutTransition.DISAPPEARING) : null);        //为GridLayout设置布局动画        mGridLayout.setLayoutTransition(mTransition);    }}

效果图:

好了,到这里位置,关于属性动画的内容我们就结束了。

相关文章:

  • http://blog.csdn.net/lmj623565791/article/details/38067475
  • http://blog.csdn.net/lmj623565791/article/details/37992017

更多相关文章

  1. Android中显示GIF动画的实现代码
  2. Android文件命名规范初版
  3. Android移动view动画问题 关于view的位置移动了,但view里面绑定的
  4. 听说谷歌Baba更新了 Material UI ...
  5. (4.2.6)【android开源组件】SwitchButton 开关按钮 的多种实现方式
  6. 如何优雅的实现Android(安卓)屏幕适配方案
  7. Android通过ksoap向webserice传递复杂类型数据
  8. Android获取当前应用分配的最大内存和目前使用内存的方法
  9. 动态化部署:Android热修复之代码修复(一)

随机推荐

  1. Android(安卓)生成keystore,两种方式 【包
  2. Android核心模块及相关技术(自IT168)
  3. Android(安卓)开发环境下载地址 -- 百度
  4. Android(安卓)多线程之 Handler 基本使用
  5. Android(安卓)上的安全性
  6. Android设置透明、半透明等效果
  7. 为Android内核添加新驱动,并添加到menucon
  8. android获取GPS位置信息
  9. Android(安卓)中的MimeType与MimeTypeMap
  10. Android设置透明、半透明效果