Android 屬性動畫:實現小球墜落
阿新 • • 發佈:2019-01-25
一、要做什麼
專案需要實現的效果:小球墜落
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);
//動畫監聽事件,不斷重繪view
anim.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 檔案中定義屬性:
<declare-styleable name="Ball">
<attr name="ball_text" format="string"/>
<attr name="start_color" format="string"/>
<attr name="end_color" format="string"/>
<attr name="anim_duration" format="integer"/>
</declare-styleable>
在activity 佈局中使用:
<com.troy.bargraph.view.AnimPointView
android:id="@+id/anim_point_view1"
android:layout_width="0dp"
android:layout_weight="1.2"
android:layout_height="match_parent"
app:ball_text="1" //小球序號
app:end_color="#66CDAA" //結束顏色
app:anim_duration="6"/> //開始顏色
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 <code>null</code>
* 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 因子值的計算規則;