属性动画之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 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();