本猿自诩Android小白,天然呆谷粉米粉,爱开源,更爱漂亮妹纸(\(^o^)/~来跟我一起唱:原谅我一生放荡不羁爱自由~~~请自行脑补掌声三分钟^O^);好了好了说人话:本猿中南大学大三狗一个,每天最快乐的时光都与14寸的DeskTop为伴,曾经我是一个热爱并略懂Java和Android的小白,现在我是一个热爱并熟悉Java和Android的小白,就这样!聊技术谈梦想欢迎私信 @哈皮小猿_wondertwo

自定义Interpolator本篇博客是浅析 Android动画 系列博客的第三篇,也是收尾工作!会在前两篇的基础上继续深入一步,介绍自定义 Interpolator 和自定义如果对Android动画了解的比较深刻,应该都有同感,只有熟练的掌握自定义 Interpolator TypeEvaluator 的技巧,才能做出一些酷炫的 动画 ,那么,本篇博客就会带大家去揭开自定义 Interpolator TypeEvaluator 的面纱一探究竟!先来看 Interpolator 和的继承关系如下图:

Interpolator 直译过来就是插补器,也译作插值器,直接控制动画的变化速率,这涉及到变化率概念,形象点说就是加速度,可以简单理解为变化的快慢。从上面的继承关系可以清晰的看出来, Interpolator 是一个接口,并未提供插值逻辑的具体实现,它的非直接子类有很多,比较常用的我都用红色下划线标出,有下面四个:当你没有为动画设置插值器时,系统默认会帮你设置加减速插值器 AccelerateDecelerateInterpolator ,我们不妨来看一个实例效果图如下,小球从静止开始先做加速运动再做减速运动最后停止,效果还是很明显的:

那么这个先加速后减速的过程是怎样实现的呢?来看 package android.view.animation; import android.content.Context; import android.util.AttributeSet; /** * An interpolator where the rate of change starts and ends slowly but * accelerates through the middle. */ public class AccelerateDecelerateInterpolator extends BaseInterpolatorimplements 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; } } 我们只关注 getInterpolation(float input) 这个方法, getInterpolation 方法接收一个 input 参数, input 参数是系统根据设置的动画持续时间计算出来的,取值范围是[0,1],从0匀速增加到1,那怎么根据这个匀速增加的参数计算出一个先加速后减速效果的 返回值 呢?方法中的代码也很简单,只是返回了一个计算式: (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f ,翻译成数学表达式就是: {0.5*cos[(input + 1)π] + 0.5}

很容易看懂,一个最基本的余弦函数变换,它的图像就对应了余弦函数π到2π范围内的曲线,如下所示,曲线的斜率先增大后减小,相应的,小球运动先加速后减速:

在第二篇博客中出现过一个参数 fraction ,同学们还记得吗?它表示动画时间流逝的百分比。其实 fraction 参数就是根据上面的 input 参数计算出来的,很容易 联想 到,最简单的情况就是对 input 参数不作任何计算处理,直接把 input 作为返回值返回,那么对应的插值器应该是线性插值器,为了验证一下我们的推理是否正确,来看看线性插值器 LinearInterpolator 的方法果然是直接把 input 参数作为返回值返回了!由于 input 参数是从0匀速增加到1的,所以自然就是线性插值器啦。好了,现在已经清楚插值器的计算逻辑是在方法中完成的,那我们来看一个稍微复杂一点的插值器 BounceInterpolator 可以实现弹跳效果,一般用来实现小球落地后的弹跳效果还是挺形象的哈,源码如下: /** * An interpolator where the change bounces at the end. */ public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { public BounceInterpolator { } @SuppressWarnings({"UnusedDeclaration"}) public BounceInterpolator(Context context, AttributeSet attrs) { } private static float bounce(float t) { return t * t * 8.0f; } public float getInterpolation(float t) { t *= 1.1226f; if (t < 0.3535f) return bounce(t); else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f; else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f; else return bounce(t - 1.0435f) + 0.95f; } /** @hide */ @Override public long createNativeInterpolator { return NativeInterpolatorFactoryHelper.createBounceInterpolator; } } 方法干的第一件事就是重新为变量t赋值,然后根据t取值范围的不同,调用 bounce(float t) 方法来计算返回值,而方法很明显是一个二次函数,再稍微观察一下你会发现它其实就是一个分段二次函数,看完下图的详细推导过程你就会明白为什么啦:

分析到这里相信同学们很快就能举一反三,瞬间明白其他几种常见插值器的实现原理了,这里我就不继续解释源码了,而是要放出大招——自定义先减速后加速插值器 DeceAcceInterpolator 需要实现 /** * DeceAcceInterpolator自定义减速加速插值器 * Created by wondertwo on 2016/3/25. */ public class DeceAcceInterpolator implements Interpolator { @Override public float getInterpolation(float input) { return ((4*input-2)*(4*input-2)*(4*input-2))/16f + 0.5f; } } 在方法中返回值只有一行代码,很简单吧!把返回值 ((4*input-2)*(4*input-2)*(4*input-2))/16f + 0.5f [(4*input-2)^3]/16 + 0.5

涉及到了三次函数变换,呃呃我假装你们都听得懂哈,其实也蛮简单的就是三次函数的简单变换啊,先来看三次函数的函数曲线如下:

还不明白?详细的数学推导过程请看下图:

可以看出来返回值的范围依然是[0,1],或许这样还不够直观,那我们就看一下函数对应的曲线的变化率,如下图所示,明显可以看到函数图像的变化率先减小后增大,这也是我们先减速后加速的动画效果的数学反映:

接下来把我们自定义的插值器设置给属性动画,代码如下:

// 设置自定义的减速加速插值器DeceAcceInterpolator animator.setInterpolator(new DeceAcceInterpolator);

那么我们自定义减速加速插值器的效果怎样呢?请看下图,可以看到小球确实是先做减速运动,速度减为0后又继续加速运动:

很明显可以看出来,小球的运动是先减速直到停下来,再加速到达终点。下图展示了动画执行过程中fraction参数的变化情况,由于数据太多我只展示了后半段数据,可以看到[0.5~0.6]区段打印了81个数据,而[0.9~1.0]区段只打印了10个数据,这也可以看出来后半段是一个加速的过程:

自定义TypeEvaluator

同学们应该记得在我,介绍了这样一个场景:在6秒内把一个按钮控件的背景颜色从蓝色渐变到红色,记得当时是怎样实现颜色渐变的吗?效果图如下:

当时提出了两种方式可以实现这种效果,第一种实现方式已经介绍过了,那我们现在就来看看第二种方式,通过自定义 TypeEvaluator 来实现颜色渐变效果!那就来定义一个颜色估值器,创建 ColorEvaluator 类实现 public class ColorEvaluator implements TypeEvaluator { private int mCurrentRed = -1; private int mCurrentGreen = -1; private int mCurrentBlue = -1; @Override public Object evaluate(float fraction, Object startValue, Object endValue) { String startColor = (String) startValue; String endColor = (String) endValue; int startRed = Integer.parseInt(startColor.substring(1, 3), 16); int startGreen = Integer.parseInt(startColor.substring(3, 5), 16); int startBlue = Integer.parseInt(startColor.substring(5, 7), 16); int endRed = Integer.parseInt(endColor.substring(1, 3), 16); int endGreen = Integer.parseInt(endColor.substring(3, 5), 16); int endBlue = Integer.parseInt(endColor.substring(5, 7), 16); // 初始化颜色的值 if (mCurrentRed == -1) { mCurrentRed = startRed; } if (mCurrentGreen == -1) { mCurrentGreen = startGreen; } if (mCurrentBlue == -1) { mCurrentBlue = startBlue; } // 计算初始颜色和结束颜色之间的差值 int redDiff = Math.abs(startRed - endRed); int greenDiff = Math.abs(startGreen - endGreen); int blueDiff = Math.abs(startBlue - endBlue); int colorDiff = redDiff + greenDiff + blueDiff; if (mCurrentRed != endRed) { mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0, fraction); } else if (mCurrentGreen != endGreen) { mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff, redDiff, fraction); } else if (mCurrentBlue != endBlue) { mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff, redDiff + greenDiff, fraction); } // 将计算出的当前颜色的值组装返回 String currentColor = "#" + getHexString(mCurrentRed) + getHexString(mCurrentGreen) + getHexString(mCurrentBlue); return currentColor; } /** * 根据fraction值来计算当前的颜色。 */ private int getCurrentColor(int startColor, int endColor, int colorDiff, int offset, float fraction) { int currentColor; if (startColor > endColor) { currentColor = (int) (startColor - (fraction * colorDiff - offset)); if (currentColor < endColor) { currentColor = endColor; } } else { currentColor = (int) (startColor + (fraction * colorDiff - offset)); if (currentColor > endColor) { currentColor = endColor; } } return currentColor; } /** * 将10进制颜色值转换成16进制。 */ private String getHexString(int value) { String hexString = Integer.toHexString(value); if (hexString.length == 1) { hexString = "0" + hexString; } return hexString; } } 关于颜色计算的逻辑和第二篇博客中 evaluateForColor 方法的逻辑一模一样! evaluate(float fraction, Object startValue, Object endValue) 方法返回一个经过计算的十六进制字符串颜色值。在创建 ObjectAnimator 对象的时候,传入的第三个参数就是我们自定义的颜色估值器 ObjectAnimator anim = ObjectAnimator.ofObject( targetView, "color", new ColorEvaluator, "#0000FF", "#FF0000"); anim.setDuration(5000); anim.start; 需要注意,不同点是这次我们的目标对象 targetView 需要有 “color” 这个属性,并且要有 “color” 属性的, getter 方法。经过上面的学习你应该对 TypeEvaluator 的工作原理比较熟悉了!看完接下来的这个实例,才算真正学会了自定义估值器。红色小圆点从屏幕的中央最高点向下做曲线运动,如果把轨迹记录下来,就是一条正弦曲线,效果图如下:

系统提供的view控件是不能直接通过设置坐标来改变位置的,因此我们首先需要自定义一个view控件, PositionView 类中定义了一个内部类 PositionPoint 代表小圆点对象,这个小圆点对象有两个成员变量 y 表示其坐标,可以通过 getter 方法来获取坐标值,并可以通过构造方法创建 PositionPoint 对象,另外在 PositionView 类中定义了 createPoint(float x, float y) 方法对其进行了封装;绘制小圆点 drawCircle(Canvas canvas) 和启动动画 startPropertyAni 的逻辑在 /** * Created by wondertwo on 2016/3/27. */ public class PositionView extends View { public static final float RADIUS = 20f; private PositionPoint currentPoint; private Paint mPaint; public PositionView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.RED); } @Override protected void onDraw(Canvas canvas) { if (currentPoint == null) { currentPoint = new PositionPoint(RADIUS, RADIUS); drawCircle(canvas); startPropertyAni; } else { drawCircle(canvas); } } private void drawCircle(Canvas canvas) { float x = currentPoint.getX; float y = currentPoint.getY; canvas.drawCircle(x, y, RADIUS, mPaint); } /** * 启动动画 */ private void startPropertyAni { ValueAnimator animator = ValueAnimator.ofObject( new PositionEvaluator, createPoint(RADIUS, RADIUS), createPoint(getWidth - RADIUS, getHeight - RADIUS)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener { @Override public void onAnimationUpdate(ValueAnimator animation) { currentPoint = (PositionPoint) animation.getAnimatedValue; invalidate; } }); // 设置自定义的减速加速插值器DeceAcceInterpolator animator.setInterpolator(new DeceAcceInterpolator); animator.setDuration(10 * 1000).start; } /** * createPoint创建PositionPointView对象 */ public PositionPoint createPoint(float x, float y) { return new PositionPoint(x, y); } /** * 小圆点内部类 */ class PositionPoint { private float x; private float y; public PositionPoint(float x, float y) { this.x = x; this.y = y; } public float getX { return x; } public float getY { return y; } } } 有了上面的准备工作,我们来看自定义坐标位置估值器 PositionEvaluator.java /** * PositionEvaluator位置估值器 * Created by wondertwo on 2016/3/23. */ public class PositionEvaluator implements TypeEvaluator { // 创建PositionView对象,用来调用createPoint方法创建当前PositionPoint对象 PositionView positionView = new PositionView(PositionDeAcActivity.mainActivity, null); @Override public Object evaluate(float fraction, Object startValue, Object endValue) { // 将startValue,endValue强转成PositionView.PositionPoint对象 PositionView.PositionPoint point_1 = (PositionView.PositionPoint) startValue; // 获取起始点Y坐标 float currentY = point_1.getY; /*// 计算起始点到结束点Y坐标差值 float diffY = Math.abs(point_1.getY - point_2.getY);*/ // 调用forCurrentX方法计算X坐标 float x = forCurrentX(fraction); // 调用forCurrentY方法计算Y坐标 float y = forCurrentY(fraction, currentY); return positionView.createPoint(x, y); } /** * 计算Y坐标 */ private float forCurrentY(float fraction, float currentY) { float resultY = currentY; if (fraction != 0f) { resultY = fraction * 400f + 20f; } return resultY; } /** * 计算X坐标 */ private float forCurrentX(float fraction) { float range = 120f;// 振幅 float resultX = 160f + (float) Math.sin((6 * fraction) * Math.PI) * range;// 周期为3,故为6fraction return resultX; } } 位置估值器 PositionEvaluator 需要实现 TypeEvaluator 接口,重写方法,首先将 startValue endValue 强转成 PositionView.PositionPoint 对象,也就是我们的小圆点对象,再分别调用 forCurrentX forCurrentY 方法计算新的坐标创建新的小圆点对象并将其返回!需要注意,实现上面介绍的正弦曲线效果的逻辑就是在这两个方法中实现的,我们让Y坐标匀速增加,而X坐标做正弦振动,它们的合成运动就是正弦曲线的效果。最后在 PositionView 类的 startAnimation 方法中,创建 ValueAnimator 对象时,传入的第一个参数就是我们自定义估值器 ValueAnimator animator = ValueAnimator.ofObject( newPositionEvaluator, createPoint(RADIUS, RADIUS), createPoint(getWidth - RADIUS, getHeight - RADIUS)); 最后在Activity的布局文件中添加我们的自定义控件 PositionView 即可看到效果,代码如下:

总结:

  1. 自定义Interpolator需要重写getInterpolation(float input),控制时间参数的变化;
  2. 自定义TypeEvaluator需要重写evaluate方法,计算对象的属性值并将其封装成一个新对象返回;

更多相关文章

  1. android Matrix处理图片原理及方法整理
  2. android studio打开旧项目遇到build.gradle相关的问题解决方法
  3. android判断模拟器的三种方法
  4. android获取资源文件R.drawable中的图片的相关方法
  5. android获取资源文件非主流方法
  6. Android权限申请:自带方法 + 第三方库实现

随机推荐

  1. android中怎么调整字体的间距和行间距
  2. Android四大视图动画图文详解
  3. 浅谈android的selector背景选择器
  4. Eclipse开发Android的配置(包括ADT安装,SDK
  5. Android(安卓)R文件丢失解决办法
  6. Android提供了Animation
  7. Android(安卓)Otto框架浅析
  8. android 巧用动画使您app风骚起来
  9. android:singleLine="true",[...]没有全
  10. 简单明了的分析Android触摸事件,看完再也