我的GitHub | 我的博客 | 我的微信 | 我的邮箱 |
---|---|---|---|
baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
目录
扔物线自定义 View 系列教程总结-4
全文整理自 扔物线(HenCoder)自定义 View 系列文章
重新整理的目标:
- 内容压缩:去除活跃气氛的段子、图片,去除无意义的解释、代码,去除不刚兴趣的内容,压缩比至少 50%
- 排版优化:更清晰的结构,更精简的标题,更规范的缩进、标点符号、代码格式,好的结构才能更好的吸收
- MarkDown:以标准的 MarkDown 格式重新编排,纯文本更易迭代维护
扔物线自定义 View 系列教程分
绘制
、布局
和触摸反馈
三部分内容。
属性动画
ViewPropertyAnimator
使用方式:View.animate()
后跟 translationX()
等方法,动画会自动执行。
view.animate().translationX(500).setDuration(500);
带有
By
后缀的是增量版本的方法,例如,translationX(100)
表示用动画把View
的translationX
值渐变为100
,而translationXBy(100)
则表示用动画把View
的translationX
值渐变地增加100
。
ObjectAnimator
使用方式:
- 如果是自定义控件,需要添加
setter
/getter
方法 - 用
ObjectAnimator.ofXXX()
创建ObjectAnimator
对象 - 用
start()
方法执行动画
public class SportsView extends View {
float progress = 0;
// 创建 getter 方法
public float getProgress() {
return progress;
}
// 创建 setter 方法
public void setProgress(float progress) {
this.progress = progress;
invalidate();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawArc(arcRectF, 135, progress * 2.7f, false, paint);
}
}
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "progress", 0, 65);
animator.setDuration(2000);
animator.start();
Interpolator 速度模型
常用的 Interpolator
- AccelerateDecelerateInterpolator 先加速再减速
- 这是默认的
Interpolator
,也是最符合现实中物体运动的Interpolator
,所以如果你要做的是最简单的状态变化,那么一般就用这个默认的最好。 - 它的动画效果 看起来就像是物体从速度为 0 开始逐渐加速,然后再逐渐减速直到 0 的运动。它的
速度 / 时间
曲线以及动画完成度 / 时间
曲线都是一条正弦/余弦
曲线。
- 这是默认的
- LinearInterpolator 匀速
- AccelerateInterpolator 持续加速
- 在整个动画过程中,一直在加速,直到动画结束的一瞬间,直接停止。
- 它主要用在离场效果中,比如某个物体从界面中飞离,就可以用这种效果。
- 它给人的感觉就会是「这货从零起步,加速飞走了」,到了最后动画骤停的时候,物体已经飞出用户视野,看不到了,所以他们是并不会察觉到这个骤停的。
- DecelerateInterpolator 持续减速直到 0
- 动画开始的时候是最高速度,然后在动画过程中逐渐减速,直到动画结束的时候恰好减速到 0。
- 它主要用于入场效果,比如某个物体从界面的外部飞入界面后停在某处。
- 它给人的感觉会是「咦飞进来个东西,让我仔细看看,哦原来是 XXX」。
- AnticipateInterpolator 先回拉一下再进行正常动画轨迹
- 效果看起来有点像投掷物体或跳跃等动作前的蓄力。
- 如果是平移动画,那么就是位置上的回拉;如果是放大动画,那么就是先缩小一下再放大。
- OvershootInterpolator 动画会超过目标值一些,然后再弹回来
- 效果看起来有点像你一屁股坐在沙发上后又被弹起来一点的感觉。
- AnticipateOvershootInterpolator 上面这两个的结合版:开始前回拉,最后超过一些然后回弹。
- BounceInterpolator 在目标值处弹跳,有点像玻璃球掉在地板上的效果。
- CycleInterpolator 这个也是一个
正弦/余弦
曲线- 和
AccelerateDecelerateInterpolator
的区别是,它可以自定义曲线的周期 - 所以动画可以不到终点就结束,也可以到达终点后回弹
- 回弹的次数由曲线的周期决定,曲线的周期由
CycleInterpolator()
构造方法的参数决定
- 和
- PathInterpolator 自定义
动画完成度/时间完成度
曲线- 用这个可以定制出任何你想要的速度模型
- 定制的方式是使用一个
Path
对象来绘制出你要的动画完成度/时间完成度
曲线
PathInterpolator 案例
Path path = new Path();
path.lineTo(1, 1); // 匀速
Path path = new Path();
path.lineTo(0.25f, 0.25f); // 先以「动画完成度 : 时间完成度 = 1 : 1」的速度匀速运行 25%
path.moveTo(0.25f, 1.5f); // 然后瞬间跳跃到 150% 的动画完成度
path.lineTo(1, 1); // 再匀速倒车,返回到目标点
这条 Path
描述的其实是一个 y = f(x) (0 < x; x < 1)
(y 为动画完成度,x 为时间完成度)的曲线,所以同一段时间完成度上不能有两段不同的动画完成度,而且每一个时间完成度的点上都必须要有对应的动画完成度,否则会导致程序 FC。
新增加的 Interpolator
除了上面的这些,Android 5.0 (API 21)
引入了三个新的 Interpolator
模型,并把它们加入了 support v4
包中。这三个新的 Interpolator
每个都和之前的某个已有的 Interpolator
规则相似,只有略微的区别。
FastOutLinearInInterpolator
它和 AccelerateInterpolator
一样,都是一个持续加速的运动路线。只不过 FastOutLinearInInterpolator
的曲线公式是用的贝塞尔曲线,而 AccelerateInterpolator
用的是指数曲线。具体来说,它俩最主要的区别是 FastOutLinearInInterpolator
的初始阶段加速度比 AccelerateInterpolator
要快一些。
FastOutLinearInInterpolator
红色,AccelerateInterpolator
绿色
实际上,这点区别,在实际应用中用户根本察觉不出来。而且,AccelerateInterpolator
还可以在构造方法中调节变速系数,分分钟调节到和 FastOutLinearInInterpolator
(几乎)一模一样。所以你在使用加速模型的时候,这两个选哪个都一样,没区别的。
FastOutSlowInInterpolator
先加速再减速。FastOutSlowInInterpolator
用的是贝塞尔曲线, AccelerateDecelerateInterpolator
用的是正弦/余弦
曲线。具体来讲, FastOutSlowInInterpolator
的前期加速度要 快得多。
FastOutSlowInInterpolator
红色,AccelerateDecelerateInterpolator
绿色
FastOutSlowInInterpolator
的前期加速更猛一些,后期的减速过程的也减得更迅速。用更直观一点的表达就是, AccelerateDecelerateInterpolator
像是物体的自我移动,而 FastOutSlowInInterpolator
则看起来像有一股强大的外力「推」着它加速,在接近目标值之后又「拽」着它减速。总之, FastOutSlowInterpolator
看起来有一点「着急」的感觉。
LinearOutSlowInInterpolator
持续减速。它和 DecelerateInterpolator
主要区别在于,LinearOutSlowInInterpolator
的初始速度更高。
LinearOutSlowInInterpolator
红色,DecelerateInterpolator
绿色
动画监听
ViewPropertyAnimator
可以用 setListener()
和 setUpdateListener()
方法设置一个监听器,通过 set[Update]Listener(null)
来移除。
ObjectAnimator
可以用 addListener()
和 addUpdateListener()
来添加一个或多个监听器,通过 remove[Update]Listener()
来指定移除对象。
由于 ObjectAnimator
支持使用 pause()
方法暂停,所以它还多了一个 addPauseListener()
/ removePauseListener()
的支持
而 ViewPropertyAnimator
则独有 withStartAction()
和 withEndAction()
方法,可以设置一次性
的动画开始或结束的监听。
AnimatorListener
ViewPropertyAnimator.setListener() / ObjectAnimator.addListener()
回调方法:
onAnimationStart(Animator animation)
当动画开始执行时被调用onAnimationEnd(Animator animation)
当动画结束时被调用onAnimationCancel(Animator animation)
当动画被通过cancel()
方法取消时被调用onAnimationRepeat(Animator animation)
动画重复执行时被调用- 可通过
setRepeatMode()
/setRepeatCount()
或repeat()
方法让动画重复执行 - 由于
ViewPropertyAnimator
不支持重复,所以这个方法对ViewPropertyAnimator
无效
- 可通过
需要说明一下的是,就算动画被取消, onAnimationEnd()
也会被调用。所以当动画被取消时,如果设置了 AnimatorListener
,那么 onAnimationCancel()
和 onAnimationEnd()
都会被调用。 onAnimationCancel()
会先于 onAnimationEnd()
被调用。
AnimatorUpdateListener
ViewPropertyAnimator.setUpdateListener() / ObjectAnimator.addUpdateListener()
它只有一个回调方法:onAnimationUpdate(ValueAnimator animation)
当动画的属性更新时被调用。
参数 ValueAnimator
是 ObjectAnimator
的父类,也是 ViewPropertyAnimator
的内部实现,所以这个参数其实就是 ViewPropertyAnimator
内部的那个 ValueAnimator
,或者对于 ObjectAnimator
来说就是它自己本身。
ValueAnimator
有很多方法可以用,它可以查看当前的动画完成度、当前的属性值等等,具体内容后面再讲。
withStartAction/EndAction()
这两个方法是 ViewPropertyAnimator
的独有方法。它们和 set/addListener()
中回调的 onAnimationStart()
/ onAnimationEnd()
相比起来的不同主要有两点:
withStartAction()
/withEndAction()
是一次性的,在动画执行结束后就自动弃掉了,就算之后再重用ViewPropertyAnimator
来做别的动画,用它们设置的回调也不会再被调用。而set/addListener()
所设置的AnimatorListener
是持续有效的,当动画重复执行时,回调总会被调用。withEndAction()
设置的回调只有在动画正常结束时才会被调用,而在动画被取消时不会被执行。这点和AnimatorListener.onAnimationEnd()
的行为是不一致的。
TypeEvaluator 类型解析
在实际的开发中,除了可以用 ofInt()
来做整数
的属性动画和用 ofFloat()
来做小数
的属性动画外,可以做属性动画的还可以是其他类型
。当需要对其他类型来做属性动画的时候,就需要用到 TypeEvaluator
了。
TypeEvaluator
可以让你对同样的属性
有不同的解析方式
,有了 TypeEvaluator
,你的属性动画就有了更大的灵活性,从而有了无限的可能。
ArgbEvaluator
TypeEvaluator
最经典的用法是使用 ArgbEvaluator
来做颜色渐变
的动画。
ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xffff0000, 0xff00ff00);
animator.setEvaluator(new ArgbEvaluator());
animator.start();
//在 Android 5.0(API 21) 以上可以直接使用下面的方式
ObjectAnimator.ofArgb(view, "color", 0xffff0000, 0xff00ff00).start();
自定义 TypeEvaluator
自定义 HslEvaluator,把 ARGB 转换成 HSV
private class HsvEvaluator implements TypeEvaluator {
float[] startHsv = new float[3];
float[] endHsv = new float[3];
float[] outHsv = new float[3];
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
// 把 ARGB 转换成 HSV
Color.colorToHSV(startValue, startHsv);
Color.colorToHSV(endValue, endHsv);
// 计算当前动画完成度(fraction)所对应的颜色值
if (endHsv[0] - startHsv[0] > 180) {
endHsv[0] -= 360;
} else if (endHsv[0] - startHsv[0] < -180) {
endHsv[0] += 360;
}
outHsv[0] = startHsv[0] + (endHsv[0] - startHsv[0]) * fraction;
if (outHsv[0] > 360) {
outHsv[0] -= 360;
} else if (outHsv[0] < 0) {
outHsv[0] += 360;
}
outHsv[1] = startHsv[1] + (endHsv[1] - startHsv[1]) * fraction;
outHsv[2] = startHsv[2] + (endHsv[2] - startHsv[2]) * fraction;
// 计算当前动画完成度(fraction)所对应的透明度
int alpha = startValue >> 24 + (int) ((endValue >> 24 - startValue >> 24) * fraction);
// 把 HSV 转换回 ARGB 返回
return Color.HSVToColor(alpha, outHsv);
}
}
使用自定义的 HslEvaluator
ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xff00ff00);
animator.setEvaluator(new HsvEvaluator()); // 使用自定义的 HslEvaluator
animator.start();
ofObject()
借助于 TypeEvaluator
,属性动画就可以通过 ofObject()
来对不限定类型
的属性做动画了。
//在 API 21 中,已经自带了 PointFEvaluator 这个类
private class PointFEvaluator implements TypeEvaluator {
PointF newPoint = new PointF();
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
float x = startValue.x + (fraction * (endValue.x - startValue.x));
float y = startValue.y + (fraction * (endValue.y - startValue.y));
newPoint.set(x, y);
return newPoint;
}
}
ObjectAnimator animator = ObjectAnimator.ofObject(view, "position",
new PointFEvaluator(), new PointF(0, 0), new PointF(1, 1));
animator.start();
复杂的属性关系动画
- 使用
PropertyValuesHolder
来对多个属性同时做动画 - 使用
AnimatorSet
来同时管理调配多个动画 - 使用
PropertyValuesHolder.ofKeyframe()
来把一个属性拆分成多段,执行更加精细的属性动画
PropertyValuesHolder 多属性
很多时候,你在同一个动画
中会需要改变多个属性
,例如在改变透明度的同时改变尺寸。如果使用 ViewPropertyAnimator
,你可以直接用连写的方式来在一个动画中同时改变多个属性:
view.animate().scaleX(1).scaleY(1).alpha(1);
而对于 ObjectAnimator
,是不能这么用的。不过你可以使用 PropertyValuesHolder
来同时在一个动画中改变多个属性。
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);
ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3).start();
AnimatorSet 多动画
有的时候,你不止需要在一个动画中改变多个属性,还会需要多个动画
配合工作,比如,在内容的大小从 0 放大到 100% 大小 后 开始移动。这种情况使用 PropertyValuesHolder
是不行的,因为这些属性如果放在同一个动画中,需要共享动画的开始时间、结束时间、Interpolator 等等一系列的设定,这样就不能有 先后次序 地执行动画了。这就需要用到 AnimatorSet
了。
有了 AnimatorSet
,你就可以对多个 Animator
进行统一规划和管理,让它们按照要求的顺序来工作。
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(animator1, animator2); // 两个动画依次执行
//animatorSet.playTogether(animator1, animator2); // 两个动画同时执行
animatorSet.start();
还可以使用 AnimatorSet.play
(animatorA).with
/before
/after
(animatorB) 的方式来精确配置各个 Animator 之间的关系
animatorSet.play(animator1).with(animator2);
animatorSet.play(animator1).before(animator2);
animatorSet.play(animator1).after(animator2);
animatorSet.start();
Keyframe 关键帧
除了合并多个属性和调配多个动画,你还可以在 PropertyValuesHolder
的基础上更进一步,通过设置 Keyframe
(关键帧),把同一个动画属性拆分成多个阶段
。例如,你可以让一个进度增加到 100% 后再「反弹」回来。
Keyframe keyframe1 = Keyframe.ofFloat(0, 0); // 在 0% 处开始
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100); // 时间 50% 的时候,动画完成度 100%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80); // 时间 100% 的时候,动画完成度 80%,即反弹 20%
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);
ObjectAnimator.ofPropertyValuesHolder(view, holder).start();
ValueAnimator 最基本的轮子
除了 ViewPropertyAnimator 和 ObjectAnimator,还有第三个选择是 ValueAnimator
。很多时候,你用不到它,因为它的功能太基础了,只是在你使用一些第三方库的控件,而你想要做动画的属性却没有 setter/getter 方法的时候,会需要用到它。
- ValueAnimator 是 ObjectAnimator 的父类,实际上,ValueAnimator 就是一个不能指定
目标对象
的 ObjectAnimator - ObjectAnimator 是自动调用目标对象的
setter
方法来更新目标属性的值,以及很多的时候还会以此来改变目标对象的 UI - ValueAnimator 只是通过渐变的方式来改变一个独立的
数据
,这个数据不是属于某个对象的,至于在数据更新后要做什么事,全都由你来定。 - ViewPropertyAnimator、ObjectAnimator、ValueAnimator 这三种 Animator从左到右依次变得更加难用了,但也更加灵活了。但是它们的性能是一样的,内部实现其实都是 ValueAnimator。
ValueAnimator 功能最少、最不方便,但有时也是束缚最少、最灵活。比如有的时候,你要给一个第三方控件做动画,你需要更新的那个属性没有
setter
方法,只能直接修改,这样的话 ObjectAnimator 就不灵了啊。怎么办?这个时候你就可以用 ValueAnimator,在它的onUpdate()
里面更新这个属性的值,并且手动调用invalidate()
。
在实际使用时候,只需遵循一个原则:尽量用简单的!能用 View.animate()
实现就不用 ObjectAnimator
,能用 ObjectAnimator 就不用 ValueAnimator
。
2021-5-5