一、要做什么 项目需要实现的效果:小球坠落
1. 首先绘制小球--自定义View 绘制圆;
2. 模拟小球坠落--属性动画,重绘小球轨迹;
3. 修改小球颜色--实现自定义TypeEvaluator;
实现的简单效果如下:
文章图片
二、思考怎么做 实现步骤如下:
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 的直接子类如下:
文章图片
我们看最简单的线性差值器的实现:
/**
* 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,应该掌握的知识点如下:
- View的知识点;重绘View 有 invalidate() 与 requestLayout();二者的区别。
- 常见的几种估值器 TypeEvaluator ,及如果根据需求自定义 TypeEvaluator ;
- 常见的差值器 Interpolator;
- fraction 因子值的计算规则;
(1)郭霖 http://blog.csdn.net/guolin_blog/article/details/44171115