淘寶評分ratingbar及invilidate方法原始碼簡單分析
一、概述
現在Android系統自帶的有ratingbar,一般用於評分的功能,今天我們自己自定義一個ratingbar,一來熟悉自定義view的套路和繪製流程,二來可以去優化,找出不足,加深對原始碼的理解。我們先看下效果圖:
二、寫程式碼的思路
看gif動態圖,首先是5個(評分數量)灰色的五角星評分圖示(正常時的圖示狀態),代表評分等級,然後,點選後變色(選中),移動後也變色(選中),星星之間有padding,控制元件的上下也有padding。
我們看上面的這段話,就可以吧相關自定義屬性抽取出來,一個是評分等級,一個是正常狀態下的圖片資源,還有一個是選中狀態下的圖片資源,還有左右上下間距,至於選中後變色,就是測量和繪製工作了。
好了,我們看下具體的程式碼
2.1定義的屬性
private Bitmap mSelectedStar; //選中時的圖片資源
private Bitmap mNormalStar; //正常時的圖片資源
private int mGrade = 5; //總的分數 預設為5
private int mCurrentPosition;
private int mStarPaddingleft = 4;
private int mStarPaddingRight = 4;
private int mWidth; //一個星星的繪製寬度 包含左右的padding距離
這裡解釋下mStarPaddingleft和mStarPaddingRight,一個是五角星左邊的padding值,一個是右邊的padding值,這裡我寫死了,你們也可以寫成自定義屬性,通過XML傳進來,也可以寫成getter,setter方法暴露給使用者。其他的不做解釋,看註釋。
構造方法裡面我就不多說了,都是簡單的獲取屬性賦值的一個過程,不過這裡要注意的是要把圖片資源轉為bitmap:
//把圖片資源轉化為bitmap
mNormalStar = BitmapFactory.decodeResource(getResources(), normalId);
mSelectedStar = BitmapFactory.decodeResource(getResources(), selectedId);
2.2其他的方法
2.2.1 onmeasure測繪寬高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = mNormalStar.getHeight()
+ getPaddingTop() + getPaddingBottom(); //高度等於上下間距 + 星星圖片的高度
mWidth = mNormalStar.getWidth() + mStarPaddingleft + mStarPaddingRight;
int width = mWidth * mGrade;
setMeasuredDimension(width, height); //設定寬高
}
2.2.2 onTouchEnven處理使用者手指觸控
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
float x = event.getX(); //獲得觸控控制元件手指的位置
int curPosition = (int) ((x / mWidth) + 1);
mCurrentPosition = curPosition;
invalidate(); //呼叫invalidate方法 不斷的去重繪(呼叫ondraw方法)
break;
}
return true;
}
這裡,在使用者手指按下,移動和抬起的時候進行監聽,獲取使用者在控制元件內的x座標,然後計算出是哪個星星(星星的位置),然後呼叫invalidate方法,不斷重繪。
2.2.3呼叫onDraw方法繪製五角星
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//1.首先 把正常狀態下的五角星畫出來 通過for迴圈畫五角星
/**
* @param bitmap The bitmap to be drawn
* @param left The position of the left side of the bitmap being drawn
* @param top The position of the top side of the bitmap being drawn
* @param paint The paint used to draw the bitmap (may be null)
*/
for (int i = 0; i < mGrade; i++) {
if (mCurrentPosition > i) {
//畫選中的星星
canvas.drawBitmap(mSelectedStar, i * mWidth + mStarPaddingleft, getPaddingTop(), null);
} else {
//畫未選中的星星
canvas.drawBitmap(mNormalStar, i * mWidth + mStarPaddingleft, getPaddingTop(), null);
}
}
}
for迴圈裡面,通過位置判斷,進行選中和非選中的五角星的圖片的繪製。drawBitmap方法可以自己看原始碼去理解,多試一試。
三、後續的優化
通過上述的分析,基本上完成了自定義ratingbar的程式碼書寫,功能完成後,我們還要考慮優化問題,這裡應該怎麼優化呢?
主要是在onTouchEvent裡面操作,首先,手指抬起不需要監聽,就不需要呼叫繪製方法,然後,在同一個位置的時候我們不需要呼叫invilidate方法,invilidate方法呼叫後,會一層一層往上呼叫。這裡做下invilidate原始碼的簡單分析。
3.1Android原始碼:
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
呼叫invilidate方法後,如果父容器不為空,就會呼叫 p.invalidateChild(this, damage)這句程式碼,這句程式碼由父佈局實現,
在父佈局中,我們看到:
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
通過while迴圈,一直找到最頂層的佈局ViewRootImpl,呼叫其invalidateChildInParent方法,接著我們看ViewRootImpl的invalidateChildInParent方法做了啥,看關鍵程式碼程式碼:
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
invalidateRectOnScreen(dirty);
第一句程式碼主要檢測執行緒,這就是為啥不能在主執行緒更新UI的原因,接著我們看invalidateRectOnScreen(dirty)這個方法,關鍵程式碼:
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
scheduleTraversals();這個方法往下執行,會呼叫TraversalRunnable子執行緒裡的doTraversal();方法, performTraversals()方法,performDraw()方法,draw()方法,一層一層呼叫,最終會呼叫 mAttachInfo.mTreeObserver.dispatchOnDraw();方法,一層一層觸發子控制元件重新繪製。
說了這麼多,也就是想說明一個道理,就是呼叫invalidate方法後,先是呼叫invalidateChildInParent一層一層往上傳遞,然後一層一層往下呼叫draw方法重新繪製,這個過程就比較耗效能。所以我們應該要減少其呼叫。
3.2具體優化方案
去掉onTouchEvent方法中up的監聽,判斷位置,沒有變化就不往下執行,程式碼如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// case MotionEvent.ACTION_UP:
float x = event.getX(); //獲得觸控控制元件手指的位置
Log.e("RatingBar",x+"");
int curPosition = (int) ((x / mWidth) + 1);
if (curPosition == mCurrentPosition) { //觸控在同一個控制元件的範圍內,不進行重新繪製
return true;
}
mCurrentPosition = curPosition;
invalidate(); //呼叫invalidata方法 不斷的去重繪(呼叫ondraw方法)
break;
}
return true;
}
可以通過列印日誌來個前後對比,這裡我就不截圖了。
四、結語
分析完畢,程式碼地址:https://github.com/lcty1201/AndroidLearn.git
相關文章
- MediaScanner原始碼簡單分析原始碼
- 自定義簡單的RatingBar
- jQuery方法原始碼定位簡單方法jQuery原始碼
- butterknife原始碼簡單分析&原理簡述原始碼
- Java 8 ArrayList 原始碼簡單分析Java原始碼
- stream pipe的原理及簡化原始碼分析原始碼
- 淘寶訂單評論API文件分析,PHP開發攻略APIPHP
- ArrayList相關方法介紹及原始碼分析原始碼
- hashCode()方法原始碼執行簡要分析原始碼
- 事件分發原始碼分析事件原始碼
- 【MyBatis原始碼分析】select原始碼分析及小結MyBatis原始碼
- ArrayList方法原始碼分析原始碼
- 拜讀及分析Element原始碼-form表單元件篇原始碼ORM元件
- SDWebImage使用及原始碼分析Web原始碼
- SpringBoot2.0原始碼分析(一):SpringBoot簡單分析Spring Boot原始碼
- 淘寶詳情api介面獲取的方式及簡單示例API
- Managed DirectX中的DirectShow應用(簡單Demo及原始碼)原始碼
- 第 23 期 Drone 簡單介紹和部分原始碼分析原始碼
- linux原始碼分析方法Linux原始碼
- Gin使用及原始碼簡析原始碼
- 簡單介紹Vue之vue.$set()方法原始碼案例Vue原始碼
- ZipperDown漏洞簡單分析及防護
- ReentrantLock解析及原始碼分析ReentrantLock原始碼
- ArrayMap詳解及原始碼分析原始碼
- react-Router 及原始碼分析React原始碼
- EventBus詳解及原始碼分析原始碼
- AQS的原理及原始碼分析AQS原始碼
- Tinker接入及原始碼分析(一)原始碼
- Tinker接入及原始碼分析(二)原始碼
- Tinker接入及原始碼分析(三)原始碼
- 實現一個簡單版本的Vue及原始碼解析(一)Vue原始碼
- 實現一個簡單版本的vue及原始碼解析(二)Vue原始碼
- 第 19 期 如何開發一個簡單高效能的 http router 及 gorouter 原始碼分析HTTPGo原始碼
- JUC之執行緒池基礎與簡單原始碼分析執行緒原始碼
- 原始碼|jdk原始碼之棧、佇列及ArrayDeque分析原始碼JDK佇列
- DRF-Throttle元件原始碼分析及改編原始碼元件原始碼
- DRF-Permission元件原始碼分析及改編原始碼元件原始碼
- [ thanos原始碼分析系列 ]thanos query元件原始碼簡析原始碼元件