前言:又一个礼拜过去了,原本定下的目标又被落在了脑后,但是懊悔已没什么用,收收心,再向目标前进吧! 等目标被一个一个实现的时候,那才是真正收获的时刻,加油!!
时间过去了这么久了,都忘了前两篇博客中都讲了什么了,现在我们先来回顾一下:我们在Android属性动画详解(上),初始属性动画的基本用法中讲解了属性动画的几种常见的操作(淡入淡出、缩放、平移、旋转等),在 Android属性动画解析(中),ValueAnimator和ObjectAnimator的高级用法中我们详细的介绍了属性动画的高级用法,主要从对对象的以及对View背景的操作给出了详细的例子,有忘记的可以回头去看看。
今天我们主要将的内容包括以下两点:InterPolator以及ViewPropertyAnimator的用法
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 */
@HasNativeInterpolator
public 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. */
@HasNativeInterpolator
public 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其实并不是什么高级技巧,它的用法格外简单,只不过和前面我们一直提到的属性动画的知识不同,它并不是在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的几个需要注意的细节:
基本使用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);
过渡的类型一共分为四种:
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);
}
}
效果图:
好了,到这里位置,关于属性动画的内容我们就结束了。
相关文章: