Fate as hand palm, no matter how tortuous, is always in their own hands.
命运如同手中的掌纹,无论多曲折,终掌握在自己手中。
1. Evaluator
1.1 概述
这幅图讲述了从定义动画的数字区间到通过AnimatorUpdateListener中得到当前动画所对应数值的整个过程:
-
onInt(0,400)表示指定动画的数字区间,是从0运动到400;
-
加速器:动画开始后,通过加速器返回当前动画进度所对应的数字进度
-
Evaluator:Evaluator就是将从加速器返回的数字进度转化为对应的数字进度。
当前的值 = 100 + (400 - 100)* 显示进度
这个公式在Evaluator计算。拿到当前数字进度所对应的值以后,将其返回
-
监听器:通过在AnimatorUpdateListener监听器使用animation.getAnimatedValue()函数拿到Evaluator中返回的数字。
Evaluator即数字转化器,将小数进度转换成对应的数值位置
1.2 多种Evaluator
加速器返回小数值,表示当前动画的数值进度,无论是ofFloat()还是还是ofInt()定义的动画都是适用的。
Evaluator则不一样,其是根据加速器返回的小数进度转换成当前数值进度对应的值。如果用ofInt()定义动画,动画中的值应该都是Int类型,如果用ofFloat()定义动画,则应都是Float类型。所以如果我用ofInt()来定义动画,所对应的Evaluator在返回值时,必然要返回Int类型的值。同样,我们如果用ofFloat来定义动画,那么Evaluator在返回值时也必然返回的是Float类型的值。
所以每种定义方式所对应的Evaluator必然是它专用的;专用的原因在于动画数值类型不一样,在通过Evaluator返回时会报强转错误。ofInt()对应是IntEvaluator,而ofFloat对应FloatEvaluator;
在设置Evaluator时,通过animator.setEvaluator()来设置:
ValueAnimator animator = ValueAnimator.ofInt(0,600);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
tv.layout(tv.getLeft(),curValue,tv.getRight(),curValue+tv.getHeight());
}
});
animator.setDuration(1000);
animator.setEvaluator(new IntEvaluator());
animator.setInterpolator(new BounceInterpolator());
animator.start();
之前使用ofInt()等没有定义使用IntEvaluator来转换值是因为系统默认提供。
除了IntEvaluator与FloatEvaluator外,还存在ArgbEvaluator、RectEvaluator、PointFEvaluator、IntArrayEvaluator、FloatArrayEvaluator等估值器。
相关Evaluator使用示例参见Github
这里给出IntEvaluator的内部代码实现:
/**
* This evaluator can be used to perform type interpolation between <code>int</code> values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type <code>int</code> or
* <code>Integer</code>
* @param endValue The end value; should be of type <code>int</code> or <code>Integer</code>
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
只有一个函数evaluate(float fraction, Integer startValue, Integer endValue)
;
其中fraction就是加速器中的返回值,表示当前动画的数值进度,百分制小数表示;
startValue与endValue分别对应ofInt(int start, int end)中的start与end
1.3 自定义Evaluator
自定义Evaluator只需实现TypeEvaluator接口,重写evaluate()方法即可
public class MyEvaluator implements TypeEvaluator<Integer> {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(200+startInt + fraction * (endValue - startInt));
}
}
在IntEvaluator的基础上修改了下,让它返回值时增加了200;所以当我们定义的区间是ofInt(0,400)时,它的实际返回值区间应该是(200,600)
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
tv.layout(tv.getLeft(),curValue,tv.getRight(),curValue+tv.getHeight());
}
});
animator.setDuration(1000);
animator.setEvaluator(new MyEvaluator());
animator.start();
1.4 ArgbEvaluator
ArgbEvaluator 是其中比较重要的一个Evaluator,用来做颜色值的过渡转换。查看其源码:
public class ArgbEvaluator implements TypeEvaluator {
private static final ArgbEvaluator sInstance = new ArgbEvaluator();
/**
* Returns an instance of <code>ArgbEvaluator</code> that may be used in
* {@link ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may
* be used in multiple <code>Animator</code>s because it holds no state.
* @return An instance of <code>ArgbEvalutor</code>.
*
* @hide
*/
public static ArgbEvaluator getInstance() {
return sInstance;
}
/**
* This function returns the calculated in-between value for a color
* given integers that represent the start and end values in the four
* bytes of the 32-bit int. Each channel is separately linearly interpolated
* and the resulting calculated values are recombined into the return value.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue A 32-bit int value representing colors in the
* separate bytes of the parameter
* @param endValue A 32-bit int value representing colors in the
* separate bytes of the parameter
* @return A value that is calculated to be the linearly interpolated
* result, derived by separating the start and end values into separate
* color channels and interpolating each one separately, recombining the
* resulting values in the same way.
*/
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = ( startInt & 0xff) / 255.0f;
int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = ( endInt & 0xff) / 255.0f;
// convert from sRGB to linear
startR = (float) Math.pow(startR, 2.2);
startG = (float) Math.pow(startG, 2.2);
startB = (float) Math.pow(startB, 2.2);
endR = (float) Math.pow(endR, 2.2);
endG = (float) Math.pow(endG, 2.2);
endB = (float) Math.pow(endB, 2.2);
// compute the interpolated color in linear space
float a = startA + fraction * (endA - startA);
float r = startR + fraction * (endR - startR);
float g = startG + fraction * (endG - startG);
float b = startB + fraction * (endB - startB);
// convert back to sRGB in the [0..255] range
a = a * 255.0f;
r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;
return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
}
}
2. ofObject
ofObject()函数,可以传进去任何类型的变量。定义如下:
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values);
它有两个参数,第一个是自定义的Evaluator,第二个是可变长参数,Object类型.
ValueAnimator animator = ValueAnimator.ofObject(new CharEvaluator(),new Character('A'),new Character('Z'));
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
char text = (char)animation.getAnimatedValue();
tv.setText(String.valueOf(text));
}
});
animator.setDuration(10000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
public class CharEvaluator implements TypeEvaluator<Character> {
@Override
public Character evaluate(float fraction, Character startValue, Character endValue) {
int startInt = (int)startValue;
int endInt = (int)endValue;
int curInt = (int)(startInt + fraction *(endInt - startInt));
char result = (char)curInt;
return result;
}
}
2.1 自定义对象
-
自定义一个类Point:
public class Point { private int radius; public Point(int radius){ this.radius = radius; } public int getRadius() { return radius; } public void setRadius(int radius) { this.radius = radius; } }
-
自定义一个View:MyPointView
public class MyPointView extends View { private Point mCurPoint; public MyPointView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mCurPoint != null){ Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.RED); paint.setStyle(Paint.Style.FILL); canvas.drawCircle(300,300,mCurPoint.getRadius(),paint); } } public void doPointAnim(){ ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),new Point(20),new Point(200)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurPoint = (Point)animation.getAnimatedValue(); invalidate(); } }); animator.setDuration(1000); animator.setInterpolator(new BounceInterpolator()); animator.start(); } }
public class PointEvaluator implements TypeEvaluator<Point> { @Override public Point evaluate(float fraction, Point startValue, Point endValue) { int start = startValue.getRadius(); int end = endValue.getRadius(); int curValue = (int)(start + fraction * (end - start)); return new Point(curValue); } }
-
在main.xml中添加对应控件布局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:padding="10dp" android:text="start anim" /> <Button android:id="@+id/btn_cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:padding="10dp" android:text="cancel anim" /> <TextView android:id="@+id/tv" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:gravity="center" android:padding="10dp" android:background="#ffff00" android:text="Hello qijian"/> <com.harvic.BlogValueAnimator4.MyPointView android:id="@+id/pointview" android:layout_below="@id/tv" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
-
public class MyActivity extends Activity { private Button btnStart; private MyPointView mPointView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btnStart = (Button) findViewById(R.id.btn); mPointView = (MyPointView)findViewById(R.id.pointview); btnStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mPointView.doPointAnim(); } }); } }
示例2:
public class PositionView extends View {
public static final float RADIUS = 20f;
private PositionPoint currentPoint;
private Paint mPaint;
public PositionView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (currentPoint == null) {
currentPoint = new PositionPoint(RADIUS, RADIUS);
drawCircle(canvas);
startPropertyAni();
} else {
drawCircle(canvas);
}
}
private void drawCircle(Canvas canvas) {
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
public void startPropertyAni() {
ValueAnimator animator = ValueAnimator.ofObject(new PositionEvaluator(), createPoint(RADIUS, RADIUS),
createPoint(getWidth() - RADIUS, getHeight() - RADIUS));
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (PositionPoint) animation.getAnimatedValue();
invalidate();
}
});
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(10 * 1000);
animator.start();
}
public PositionPoint createPoint(float x, float y) {
return new PositionPoint(x, y);
}
class PositionPoint {
private float x;
private float y;
public PositionPoint(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public void setX(float x) {
this.x = x;
}
public void setY(float y) {
this.y = y;
}
}
}
public class PositionEvaluator implements TypeEvaluator<PositionView.PositionPoint> {
@Override
public PositionView.PositionPoint evaluate(float fraction, PositionView.PositionPoint startValue, PositionView.PositionPoint endValue) {
PositionView.PositionPoint point_1= (PositionView.PositionPoint) startValue;
float currentY = point_1.getY();
float x = forCurrentX(fraction);
float y = forCurrentY(fraction, currentY);
point_1.setX(x);
point_1.setY(y);
return point_1;
}
private float forCurrentY(float fraction, float currentY) {
float resultY = currentY;
if (fraction != 0f) {
resultY = fraction * 800f + 20f;
}
return resultY;
}
private float forCurrentX(float fraction) {
float range = 400f;
float resultX = 400f + (float) Math.sin((6 * fraction) * Math.PI) * range;
return resultX;
}
}
详细代码参见:Github