Android|Android 属性动画(实现小球坠落)

一、要做什么 项目需要实现的效果:小球坠落

1. 首先绘制小球--自定义View 绘制圆; 2. 模拟小球坠落--属性动画,重绘小球轨迹; 3. 修改小球颜色--实现自定义TypeEvaluator;

实现的简单效果如下:
Android|Android 属性动画(实现小球坠落)
文章图片

二、思考怎么做 实现步骤如下:
1、自定义 AnimPointView:
/** * Created by Troy on 2017/3/20. * * 通过对对象进行值操作来实现动画效果的功能,这就是ValueAnimator的高级用法 */ public class AnimPointView extends View {public static final float sRADIUS = 20F; private Point mCurrentPoint; private Paint mPaint; private Paint mTextPaint; //动画持续时间 默认5S private int mAnimDuration; private int mDefaultAnimDuration = 5; //小球序号 private String mBallText; private String mDefaultBallText = "1"; //初始颜色 private String mBallStartColor; private String mDefaultBallStartColor = "#0000FF"; //结束颜色 private String mBallEndColor; private String mDefaultBallEndColor = "#FF0000"; public AnimPointView(Context context) { super(context); init(); }public AnimPointView(Context context, AttributeSet attrs) { super(context, attrs); //自定义属性 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Ball); mAnimDuration = typedArray.getInt(R.styleable.Ball_anim_duration, mDefaultAnimDuration); mBallText = typedArray.getString(R.styleable.Ball_ball_text); mBallStartColor = typedArray.getString(R.styleable.Ball_start_color); mBallEndColor = typedArray.getString(R.styleable.Ball_end_color); if(TextUtils.isEmpty(mBallText)){ mBallText = mDefaultBallText; } if(TextUtils.isEmpty(mBallStartColor)){ mBallStartColor = mDefaultBallStartColor; } if(TextUtils.isEmpty(mBallEndColor)){ mBallEndColor = mDefaultBallEndColor; } //回收typedArray typedArray.recycle(); init(); }public AnimPointView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); }private void init(){ //画圆的画笔 mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.RED); //画文字的画笔 mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); mTextPaint.setColor(Color.WHITE); mTextPaint.setTextSize(sRADIUS); mTextPaint.setTextAlign(Paint.Align.CENTER); }@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(mCurrentPoint == null){ mCurrentPoint = new Point(sRADIUS, sRADIUS); drawCircle(canvas); startAnimation(); }else { drawCircle(canvas); } }//绘制圆球 private void drawCircle(Canvas canvas){ float x = mCurrentPoint.getX(); float y = mCurrentPoint.getY(); canvas.drawCircle(x, y, sRADIUS, mPaint); canvas.drawText(mBallText, x, y + 5, mTextPaint); }// 调用了invalidate()方法,这样的话 onDraw()方法就会重新调用,并且由于currentPoint 对象的坐标已经改变了, // 那么绘制的位置也会改变,于是一个平移的动画效果也就实现了; private void startAnimation(){ //改变小球的位置 ValueAnimator Point startPoint = new Point(getWidth() / 2, sRADIUS); Point endPoint = new Point(getWidth() / 2, getHeight() - sRADIUS); Log.i("TEST", "startPoint:" + startPoint.getX() + "-" + startPoint.getY()); Log.i("TEST", "endPoint:" + endPoint.getX() + "-" + endPoint.getY()); ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint); //动画监听事件,不断重绘viewanim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurrentPoint = (Point) animation.getAnimatedValue(); //invalidate() 与 requestLayout()的区别,这个地方也可以用requestLayout(); invalidate(); } }); //设置动画的弹跳差值器 anim.setInterpolator(new BounceInterpolator()); //改变小球的颜色 ObjectAnimator ObjectAnimator anim2 = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(), mBallStartColor, mBallEndColor); //组合动画 AnimatorSet animSet = new AnimatorSet(); animSet.play(anim).with(anim2); animSet.setDuration(mAnimDuration*1000); animSet.start(); }private String color; public String getColor() { return color; }public void setColor(String color) { this.color = color; mPaint.setColor(Color.parseColor(color)); invalidate(); } }

2、自定义属性及布局使用
在attrs.xml 文件中定义属性:

在activity 布局中使用:
//开始颜色

3、小球位置估值器
public class PointEvaluator implements TypeEvaluator { @Override public Object evaluate(float fraction, Object startValue, Object endValue) { //fraction 与时间有关的系数,该值由差值器计算得出,由ValueAnimator调用 animateValue Point startPoint = (Point)startValue; Point endPoint = (Point)endValue; float x = startPoint.getX() + fraction*(endPoint.getX() - startPoint.getX()); float y = startPoint.getY() + fraction*(endPoint.getY() - startPoint.getY()); return new Point(x, y); } }

4、关于 evaluate 方法中fraction 因子的值来源
首先应该明白差值器的概念和基本使用,我们一般在代码里给动画设置一个差值器:
anim.setInterpolator(new BounceInterpolator());

如果没有设置差值器,系统默认使用加速减速差值器:
// The time interpolator to be used if none is set on the animation private static final TimeInterpolator sDefaultInterpolator = new AccelerateDecelerateInterpolator();

如果设置 null ,系统默认使用线性差值器:
/** * 1、interpolator 的作用:The time interpolator used in calculating the elapsed fraction of this animation. The * 2、差值器的赋值:interpolator determines whether the animation runs with linear or non-linear motion, * such as acceleration and deceleration. The default value is * {@link android.view.animation.AccelerateDecelerateInterpolator} * * @param value the interpolator to be used by this animation. A value of null * will result in linear interpolation. */ @Override public void setInterpolator(TimeInterpolator value) { if (value != null) { mInterpolator = value; } else { // 当设置 null 时,使用线性差值器 mInterpolator = new LinearInterpolator(); } }

看ValueAnimator 的源码可知 fraction 是由差值器计算出来的:
float fraction = mInterpolator.getInterpolation(fraction); //getInterpolation 是父接口的方法,具体实现在子类中;

Interpolator 的直接子类如下:
Android|Android 属性动画(实现小球坠落)
文章图片

我们看最简单的线性差值器的实现:
/** * 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(); } }

4、颜色改变估值器
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; // Integer.parseInt(String s ,int radix)方法: 输出一个十进制数; radix 表示原来的进制; 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; }}

基本就是上面一些内容。
三、总结: 做完这个DEMO,应该掌握的知识点如下:
  1. View的知识点;重绘View 有 invalidate() 与 requestLayout();二者的区别。
  2. 常见的几种估值器 TypeEvaluator ,及如果根据需求自定义 TypeEvaluator ;
  3. 常见的差值器 Interpolator;
  4. fraction 因子值的计算规则;
【Android|Android 属性动画(实现小球坠落)】参考致谢:
(1)郭霖 http://blog.csdn.net/guolin_blog/article/details/44171115

    推荐阅读