属性动画之ViewPropertyAnimator
这是属性动画最简单的一种
代码也最简单
iv.animate().translationY(500);复制代码
通常一些简单的android 原生的view动画 我们都优先考虑这种方法,因为真的很方便啊。
/** * This method returns a ViewPropertyAnimator object, which can be used to animate * specific properties on this View. * * @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View. */ public ViewPropertyAnimator animate() { if (mAnimator == null) { mAnimator = new ViewPropertyAnimator(this); } return mAnimator; }复制代码
看下这个函数返回就知道了。
有很多方法可以供我们选择,具体各位自查。
总体来说ViewPropertyAnimator 使用简单也比较好理解,也支持链式调用。不再多说
ObjectAnimator
ViewPropertyAnimator虽然好用,但是自定义view很难使用这个,且支持的属性有限。很多情况我们要自己支持一些属性
就得用到ObjectAnimator
总体来说 分几步:
- 动画执行过程中要改变的属性 必须有gettter和setter方法
- ObjectAnimator.ofXXX() 创建 ObjectAnimator 对象
- 最后start执行动画即可
看下代码:
public class LoadingView extends View { Paint mPaint = new Paint(); public float getProgress() { return progress; } public void setProgress(float progress) { this.progress = progress; //setter方法是肯定会被ObjectAnimator调用的,调用完以后 我们要主动invalidate方法 //onDraw方法才会主动执行,否则,只改变一个属性的值而不重绘 肯定是没效果的。 //这也就是为什么属性动画 不是直接更改属性的值,而要调用属性的setter方法,因为直接 //更改属性的值 invalidate没地方调用了,动画自然没效果了。 invalidate(); } /** * 进度条 */ float progress = 0; public LoadingView(Context context) { super(context); } public LoadingView(Context context, AttributeSet attrs) { super(context, attrs); } public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //这里没有用0,0 width,hegiht 这2个点来定位这个矩形 是因为我们的圆形边有宽度,所以要 //稍微窄一点 不然的话 边界处会有丢失的部分 很难看 RectF oval = new RectF(10, 10, getWidth()-10, getHeight()-10); //先画圆弧 mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(20); canvas.drawArc(oval, -90, 360 * progress/100, false, mPaint); //再画文字 mPaint.reset(); mPaint.setColor(Color.BLUE); mPaint.setTextSize(80); mPaint.setTextAlign(Paint.Align.CENTER); canvas.drawText((int)progress + "%", oval.centerX(), oval.centerY(), mPaint); }}复制代码
// 创建 ObjectAnimator 对象 ObjectAnimator animator = ObjectAnimator.ofFloat(loadingView, "progress", 0, 85); animator.setDuration(3000); animator.start();复制代码
ofXXX有很多方法,可以满足我们任何自定义view属性的要求。
Interpolator 插值器
这个其实挺好理解的,打个比方 一个人从起跑点0m处跑到终点100m处。可以有很多种跑法
- 一直加速跑 跑到终点
- 跑到一半减速再加速到终点
- 跑到一半停下来休息一下 再跑到终点
- .....等等 有无限种跑法 ,全看你自己想怎么跑。甚至都可以跑过终点跑到150m处再跑回去
Interpolator 也是这样,Interpolator 就是设置你动画执行过程的,以不同的速度模型来将你的动画执行完毕
系统自带的Interpolator已经足够多,我不准备一一介绍了,网上太多资料了,自己跑跑看就可以了。
PathInterpolator这个较为特殊,尤其是配合 贝塞尔曲线使用的时候 会有很多酷炫的特效,我写个简单的 demo 大家体会一下这其中的奥妙即可。 讲白了还是那个跑步的跑法 可以由我们自己设定
Path interpolatorPath = new Path(); // 先以「动画完成度 : 时间完成度 = 1 : 1」的速度匀速运行 25% 50的百分之25 就是12.5 interpolatorPath.lineTo(0.25f, 0.25f); // 然后瞬间跳跃到 100% 的动画完成度 在这里其实也就是从50 直接跳跃到100 也就是圆形直接画满 interpolatorPath.moveTo(0.25f, 2.0f); // 再匀速倒车,返回到目标点 画满以后 再回到 目标值 interpolatorPath.lineTo(1, 1); // 创建 ObjectAnimator 对象 ObjectAnimator animator = ObjectAnimator.ofFloat(loadingView, "progress", 0, 50); animator.setDuration(3000); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { PathInterpolator pathInterpolator=new PathInterpolator(interpolatorPath); animator.setInterpolator(pathInterpolator); } animator.start();复制代码
ofXXX方法无法满足我咋办?Evaluator来帮你
比如说我自定义了某个view,这个view要完成我想要的动画需要改变的属性是一个自定义对象那咋办呢?自定义Evaluator呗
稍微改变下我们的代码:
public class LoadingView extends View { Paint mPaint = new Paint(); public CustomProperty getCustomProperty() { return customProperty; } public void setCustomProperty(CustomProperty customProperty) { this.customProperty = customProperty; invalidate(); } /** * 进度条 */ CustomProperty customProperty = new CustomProperty(0,0); public LoadingView(Context context) { super(context); } public LoadingView(Context context, AttributeSet attrs) { super(context, attrs); } public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); RectF oval = new RectF(30, 30, getWidth()-30, getHeight()-30); //先画圆弧 mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(customProperty.getStrokeWidth()); canvas.drawArc(oval, -90, 360 * customProperty.getProgress()/100, false, mPaint); }}复制代码
自定义属性
public class CustomProperty { float progress; public CustomProperty(float progress, int strokeWidth) { this.progress = progress; this.strokeWidth = strokeWidth; } public float getProgress() { return progress; } public void setProgress(float progress) { this.progress = progress; } public int getStrokeWidth() { return strokeWidth; } public void setStrokeWidth(int strokeWidth) { this.strokeWidth = strokeWidth; } @Override public String toString() { return "CustomProperty{" + "progress=" + progress + ", strokeWidth=" + strokeWidth + '}'; } int strokeWidth;}复制代码
最重要的自定义Evaluator
class CustomPropertyEvaluator implements TypeEvaluator{ CustomProperty customProperty=new CustomProperty(0,0); @Override public CustomProperty evaluate(float fraction, CustomProperty startValue, CustomProperty endValue) { float progress=startValue.progress+ fraction*endValue.getProgress(); int strokeWidth=(int)(startValue.strokeWidth+fraction*endValue.getStrokeWidth()); customProperty.setProgress(progress); customProperty.setStrokeWidth(strokeWidth); return customProperty; } }复制代码
最后调用动画
// 创建 ObjectAnimator 对象 ObjectAnimator animator = ObjectAnimator.ofObject(loadingView, "customProperty", new CustomPropertyEvaluator(),new CustomProperty(0,0), new CustomProperty(75,30)); animator.setDuration(3000); animator.start();复制代码
然后看下我们的效果:
PropertyValuesHolder 组合动画
组合动画无非就是动画执行的顺序集合,大概也就是分三种,先说前两种
一起执行和顺序执行
//ofPropertyValuesHolder 代表一起执行动画的集合,holder1 holder2 holder3 可以一起执行 共享一个插值器 interpolator PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1); PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1); PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1); ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(loadingView, holder1, holder2, holder3); animator.start(); AnimatorSet animatorSet=new AnimatorSet(); ObjectAnimator animator1 = ObjectAnimator.ofPropertyValuesHolder(loadingView, holder1, holder2, holder3); ObjectAnimator animator2 = ObjectAnimator.ofPropertyValuesHolder(loadingView, holder1, holder2); //如果需要动画依次播放: animatorSet.playSequentially(animator1,animator2); //也可以指定顺序 animatorSet.play(animator1).before(animator2); animatorSet.start();复制代码
比较好理解是吧。这里就不上效果图了,自己试试就知道。
最后一种关键帧动画着重说一下,还记得前面插值器的介绍吗?有一个path插值器的,我们写了个demo带有回弹效果的, 利用关键帧动画的写法 可以不用那么复杂的插值器即可完成
//从0开始 Keyframe keyframe1 = Keyframe.ofFloat(0, 0); //时间走到一半 我们应该圆圈画完 Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100); //时间走完的时候 我们的圆圈应该回到一半的位置 Keyframe keyframe3 = Keyframe.ofFloat(1, 50); PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3); ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(loadingView, holder); animator.setDuration(3000); animator.start();复制代码