Android自定義view之屬性動畫熟悉
最近項目中有一個這樣的需求,當用戶填寫完數據之后,傳后臺去計算,然后需要跑一個這樣的動畫,由于這個動畫效果同事用的是后臺切的圖組成的幀動畫,然后一向強迫癥如我就非常不喜歡那么多圖片就是為了成就這么一個動畫,于是乎我決定自己用最近學習的屬性動畫來寫一個,如有錯誤或者更好的解決辦法,請及時指正,話不多說,先看圖:
我們可以來分析一下,首先呢我們可以把這個圖分成四部分,三串數字一個圓,每一串數字一個接一個的從圓的上側滾動到下側,但是注意并不是同時的,我們可以用三個動畫為三串數字來實現這個效果(這是我的想法),先從中間開始,因為中間部分坐標什么的都比較簡單:
在這里把自定義view的幾個步驟講的稍微詳細一點,為了自己能得到復習的同時也為了能幫助需要了解自定義view的同學,順便說一下,如果你還沒有看我的另外一篇關于自定義view之屬性動畫的,請移步: Android自定義view之屬性動畫初見(http://www.jianshu.com/p/0e10a6ed80dc)
1 自定義屬性
先自定義屬性,以便以后可以自己定制,圓的顏色,半徑,字體顏色及大小,我我們暫時就需要這么幾個屬性:
<declare-styleable name="CustomNumAnimView">
<attr name="round_radius" format="dimension" />
<attr name="round_color" format="color" />
<attr name="text_color" format="color" />
<attr name="text_size" format="dimension" />
</declare-styleable>
2 獲得屬性所對應的值
好了,然后我們得在構造方法中獲得屬性所對應的值:
<span style="box-sizing:border-box;">private</span> <span style="box-sizing:border-box;">int</span> roundColor; <span style="box-sizing:border-box;">//圓的顏色<br /><br /></span><span style="box-sizing:border-box;">private</span> <span style="box-sizing:border-box;">int</span> textColor; <span style="box-sizing:border-box;">//數字的顏色<br /><br /></span><span style="box-sizing:border-box;">private</span> <span style="box-sizing:border-box;">float</span> textSize; <span style="box-sizing:border-box;">//數字字體大小<br /><br /></span><span style="box-sizing:border-box;">private</span> <span style="box-sizing:border-box;">float</span> roundRadius; <span style="box-sizing:border-box;">//圓的半徑<br /><br /></span><span style="box-sizing:border-box;">private</span> Paint mPaint; <span style="box-sizing:border-box;">//畫筆<br /><br /></span><span style="box-sizing:border-box;">private</span> Rect textRect; <span style="box-sizing:border-box;">//包裹數字的矩形<br /><br /></span><span style="box-sizing: border-box;"><span style="box-sizing:border-box;">public</span> <span style="box-sizing:border-box;">CustomNumAnimView</span><span style="box-sizing: border-box;">(Context context, AttributeSet attrs)</span> </span>{ super(context, attrs); <span style="box-sizing:border-box;">//獲取自定義屬性</span> TypedArray <span style="box-sizing:border-box;">array</span> = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomNumAnimView, defStyleAttr, <span style="box-sizing:border-box;">0</span>); roundColor = <span style="box-sizing:border-box;">array</span>.getColor(R.styleable.CustomNumAnimView_round_color, ContextCompat.getColor(context, R.color.colorPrimary)); roundRadius = <span style="box-sizing:border-box;">array</span>.getDimension(R.styleable.CustomNumAnimView_round_radius, <span style="box-sizing:border-box;">50</span>); textColor = <span style="box-sizing:border-box;">array</span>.getColor(R.styleable.CustomNumAnimView_text_color, Color.WHITE); textSize = <span style="box-sizing:border-box;">array</span>.getDimension(R.styleable.CustomNumAnimView_text_size, <span style="box-sizing:border-box;">30</span>); <br /> <span style="box-sizing:border-box;">array</span>.recycle(); mPaint = <span style="box-sizing:border-box;">new</span> Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setTextSize(textSize); textRect = <span style="box-sizing:border-box;">new</span> Rect(); <span style="box-sizing:border-box;">//得到數字矩形的寬高,以用來畫數字的時候糾正數字的位置</span> mPaint.getTextBounds(middleNum, <span style="box-sizing:border-box;">0</span>, middleNum.length(), textRect); }
3 畫圓
獲取完屬性之后,我們得要畫一個圓,畫在哪里呢?當然是屏幕的中心了,在onDraw方法中做如下操作:
@Overrideprotected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setAntiAlias(true); //設置抗鋸齒
mPaint.setStyle(Paint.Style.FILL_AND_STROKE); //設置畫筆填充,畫實心圓
mPaint.setColor(roundColor); //設置圓的顏色
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, roundRadius, mPaint); //畫圓
}
4 畫數字
我們可以畫數字了,在畫數字之前我想讓大家知道Android中手機屏幕的坐標系的結構,如下圖所示:
好了,對坐標系有一定了解之后,我們開始畫數字,先把中間數字的效果做出來,我記得我在上一篇關于動畫的博客中有講到TypeEvaluator,這真是個好東西,有不清楚的同學請移步我之前的博客,在這里也給大家推薦一個學習屬性動畫的博客: Android自定義控件三部曲文章索引(http://blog.csdn.net/harvic880925/article/details/50995268)
public class CustomPointEvaluator implements TypeEvaluator {
/**
*
* @param fraction 系數
* @param startValue 起始值
* @param endValue 終點值
* @return
*/
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
CustomPoint startPoint = (CustomPoint) startValue;
CustomPoint endPoint = (CustomPoint) endValue;
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
CustomPoint point = new CustomPoint(x, y);
return point;
}
}
這個類幫助我們告訴系統如何在設置的時間內從初始值過渡到結束值,并獲取中間的狀態
5 CustomPoint
上面的類中還有一個東西CustomPoint,這個表示每一個數字行進過程中的坐標,我們把它當作一個點來處理,這樣更加方便:
public class CustomPoint {
private float x; //點的x坐標
private float y; //點的y坐標
public CustomPoint(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
}
6 開始動畫的過程
然后我們就開始動畫的過程:
private boolean isFirstInit = false; //是否是第一次初始化
private CustomPoint middlePoint; //中間的數字的實時點
private ValueAnimator middleAnim; //中間數字動畫
private String middleNum = "9";
private boolean isMiddleNumInvalidate = false; //中間數字是否重繪界面
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isFirstInit) {
middlePoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2, getMeasuredHeight() / 2 - roundRadius - textRect.height() / 2);
drawText(canvas);
startAnimation(); //開始動畫
isFirstInit = true;
} else {
drawText(canvas);
}
}
/**
* 畫數字
* @param canvas
*/
private void drawText(Canvas canvas) {
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(roundColor);
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, roundRadius, mPaint);
mPaint.setColor(textColor);
mPaint.setTextSize(textSize);
if (isMiddleNumInvalidate) {
canvas.drawText(middleNum, middlePoint.getX(), middlePoint.getY(), mPaint);
isMiddleNumInvalidate = false;
}
}
7 startAnimation
這個過程還是比較好理解的,首先所有的點我們只初始化一遍,然后開始動畫也只在第一次初始化中執行,因為我們設置的動畫是無限循環的,然后就是我們的startAnimation()方法了:
private void startAnimation() {
//初始化中間數字的開始點的位置
final CustomPoint startPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2, getMeasuredHeight() / 2 - roundRadius - textRect.height() / 2); //初始化中間數字的結束點的位置
final CustomPoint endPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2, getMeasuredHeight() / 2 + roundRadius + textRect.height() / 2);
middleAnim = ValueAnimator.ofObject(new CustomPointEvaluator(), startPoint, endPoint); //監聽從起始點到終點過程中點的變化,并獲取點然后重新繪制界面
middleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
middlePoint = (CustomPoint) animation.getAnimatedValue();
isMiddleNumInvalidate = true;
invalidate();
}
});
middleAnim.setDuration(300);
middleAnim.setRepeatCount(ValueAnimator.INFINITE);
}
8 動畫效果
這個過程也還是比較好理解的,首先我們創建了兩個點,也就是數字的初始位置以及結束位置,然后再利用ValueAnimator的ofObject方法來對數字行進路線進行分析,然后每一次監聽的時候都讓界面進行重繪,這樣就能感覺數字一直在移動,讓我們來看一下效果:
9 在一次動畫結束之后,取隨機數
OMG,圖片錄制的不太友好,實際效果可不是這樣子的,但是我們不難發現,數字好像沒有變,只是單一的數字,下面我們要做的就是在一次動畫結束之后,取隨機數,怎么樣才能知道動畫一次運行完成了呢?萬能的google肯定會有方法的,我們只需要再加一個監聽就好了:
middleAnim.addListener(new CustomAnimListener() {
@Override
public void onAnimationRepeat(Animator animation) {
middleNum = getRandom();
}
});
/**
* 獲取0-9之間的隨機數
*
* @return
*/
private String getRandom() {
int random = (int) (Math.random() * 9);
return String.valueOf(random);
}
10 動畫重復之后效果
這個監聽就是當動畫重復之后會執行這個方法,我們也可以認為每當動畫執行完一遍之后都會執行這個方法。好了,讓我們再來看看效果吧:(可能錄屏軟件都有點問題,看起來并不和諧)
11 分析
完成一個之后,剩下的兩個就簡單了,我們只需要找到旁邊兩個點的坐標就行了,我是這么分析的,我們來看一張圖:
首先呢,左右兩邊肯定是關于 Y 軸對稱的,而且我的想法是,這三個數字所在的點將 X 軸平分成了四段(只包括整個圓),然后根據這個可以算出左邊點的橫縱坐標,縱左邊是橫坐標的根號三倍,算出坐標之后就好辦了
12 完整效果
好了,完整的代碼已上傳github,該有的注釋我都加上去了,有不懂得地方可以私信我,如果還有更好的解決方法也請私戳我,下面來看一下最后的效果:
好吧!看起來也不怎么和諧了,不過大家可以下載代碼去跑一遍,真正運行起來的不是這個樣子的,代碼我已上傳至GitHub( https://github.com/Jakemesdg/CustomNumAnimDemo ),有需要的同學可以下載,star
來自:http://mp.weixin.qq.com/s/9AvvuKkiW8tveiEVQ0y5oA