本文整理自: Google 官方文檔之自定義 View哄尔,筆者省略了對自己幫助不大的章節(jié),拜讀原文請點鏈接乌企。
一晌该、繼承一個View
Android Framework 里面定義的 View 類都繼承自 View 。你自定義的 View 也可以直接繼承 View弥臼,或者你可以通過繼承既有的一個子類(例如 Button )來節(jié)約一點時間态贤。
為了讓 Android Developer Tools 能夠識別你的 View,你必須至少提供一個 constructor醋火,它包含一個 Context 與一個 AttributeSet 對象作為參數(shù)悠汽。這個 constructor 允許 layout editor 創(chuàng)建并編輯你的 View 的實例。
class PieChart extends View {
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
1.定義自定義屬性
為了添加一個內(nèi)置的 View 到你的 UI 上芥驳,你需要通過 XML 屬性來指定它的樣式與行為柿冲。良好的自定義 Views 可以通過 XML 添加和改變樣式,為了讓你的自定義的 View 也有如此的行為兆旬,你應(yīng)該:
- 為你的 View 在資源標(biāo)簽下定義自設(shè)的屬性
- 在你的 XML layout 中指定屬性值
- 在運行時獲取屬性值
- 把獲取到的屬性值應(yīng)用在你的 View 上
為了定義自設(shè)的屬性假抄,添加 資源到你的項目中。放置于 res/values/attrs.xml 文件中丽猬。下面是一個 attrs.xml 文件的示例:
<resources>
<declare-styleable name="PieChart">
<attr name="showText" format="boolean" />
<attr name="labelPosition" format="enum">
<enum name="left" value="0"/>
<enum name="right" value="1"/>
</attr>
</declare-styleable>
</resources>
上面的代碼聲明了 2 個自設(shè)的屬性宿饱,showText 與 labelPosition,它們都?xì)w屬于 PieChart 的項目下的 styleable 實例脚祟。styleable 實例的名字谬以,通常與自定義的 View 名字一致。盡管這并沒有嚴(yán)格規(guī)定要遵守這個 convention由桌,但是許多流行的代碼編輯器都依靠這個命名規(guī)則來提供 statement completion为黎。
一旦你定義了自設(shè)的屬性,你可以在 layout XML 文件中使用它們行您,就像內(nèi)置屬性一樣铭乾。唯一不同的是你自設(shè)的屬性是歸屬于不同的命名空間。不是屬于「 http://schemas.android.com/apk/res/android 」 的命名空間娃循,它們歸屬于「 http://schemas.android.com/apk/res/你的包名 」炕檩。例如,下面演示了如何為 PieChart 使用上面定義的屬性:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">
<com.example.customviews.charting.PieChart
custom:showText="true"
custom:labelPosition="left" />
</LinearLayout>
為了避免輸入長串的 namespace 名字捌斧,示例上面使用了 xmlns 指令笛质,這個指令可以指派 custom 作為「http://schemas.android.com/apk/res/com.example.customviewsnamespace 」的別名吹泡。你也可以選擇其他的別名作為你的 namespace。請注意经瓷,如果你的 View 是一個 inner class爆哑,你必須指定這個 View 的 outer class。同樣的舆吮,如果 PieChart 有一個 inner class 叫做 PieView 揭朝。為了使用這個類中自設(shè)的屬性,你應(yīng)該使用 com.example.customviews.charting.PieChart$PieView.
2.應(yīng)用自定義屬性
當(dāng) View 從 XML layout 被創(chuàng)建的時候色冀,在 xml 標(biāo)簽下的屬性值都是從 resource 下讀取出來并傳遞到 View 的 constructor 作為一個 AttributeSet 參數(shù)潭袱。盡管可以從 AttributeSet 中直接讀取數(shù)值,可是這樣做有些弊端:
- 擁有屬性的資源并沒有經(jīng)過解析
- Styles 并沒有運用上
翻譯注:通過 attrs 的方法是可以直接獲取到屬性值的锋恬,但是不能確定值類型屯换,如:
String title = attrs.getAttributeValue(null, "title");
int resId = attrs.getAttributeResourceValue(null, "title", 0);
title = context.getText(resId));
都能獲取到 "title" 屬性,但你不知道值是字符串還是 resId与学,處理起來就容易出問題彤悔,下面的方法則能在編譯時就發(fā)現(xiàn)問題
取而代之的是,通過 obtainStyledAttributes() 來獲取屬性值索守。這個方法會傳遞一個 TypedArray 對象晕窑,它是間接 referenced 并且 styled 的。
Android資源編譯器幫你做了許多工作來使調(diào)用 [obtainStyledAttributes()](http://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int)) 更簡單卵佛。對 res 目錄里的每一個 <declare-styleable>
資源杨赤,自動生成的 R.java 文件定義了存放屬性 ID 的數(shù)組和常量,常量用來索引數(shù)組中每個屬性截汪。你可以使用這些預(yù)先定義的常量來從
TypedArray 中讀取屬性疾牲。這里就是 PieChart 類如何讀取它的屬性:
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.PieChart,
0, 0);
try {
mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
} finally {
a.recycle();
}
}
清注意 TypedArray 對象是一個共享資源,必須被在使用后進(jìn)行回收衙解。
3.添加屬性和事件
Attributes 是一個強(qiáng)大的控制view的行為與外觀的方法阳柔,但是他們僅僅能夠在 View 被初始化的時候被讀取到。為了提供一個動態(tài)的行為丢郊,需要暴露出一些合適的 getter 與 setter 方法盔沫。下面的代碼演示了如何使用這個技巧:
public boolean isShowText() {
return mShowText;
}
public void setShowText(boolean showText) {
mShowText = showText;
invalidate();
requestLayout();
}
請注意医咨,在 setShowText 方法里面有調(diào)用 invalidate()
and requestLayout()枫匾。 這兩個調(diào)用是確保穩(wěn)定運行的關(guān)鍵。當(dāng) View 的某些內(nèi)容發(fā)生變化的時候拟淮,需要調(diào)用 invalidate 來通知系統(tǒng)對這個 View 進(jìn)行 redraw干茉,當(dāng)某些元素變化會引起組件大小變化時,需要調(diào)用 requestLayout 方法很泊。調(diào)用時若忘了這兩個方法角虫,將會導(dǎo)致 hard-to-find bugs沾谓。
自定義的 View 也需要能夠支持響應(yīng)事件的監(jiān)聽器。例如戳鹅,PieChart 暴露了一個自定義的事件 OnCurrentItemChanged 來通知監(jiān)聽器均驶,用戶已經(jīng)切換了焦點到一個新的組件上。我們很容易忘記了暴露屬性與事件枫虏,特別是當(dāng)你是這個 View 的唯一用戶時妇穴。請花費一些時間來仔細(xì)定義你的
View 的交互。一個好的規(guī)則是總是暴露任何屬性與事件隶债。
二腾它、實現(xiàn)自定義 View 的繪制
重繪一個自定義的 View 的最重要的步驟是重寫 onDraw() 方法。onDraw() 的參數(shù)是一個 Canvas 對象死讹。Canvas 類定義了繪制文本瞒滴,線條,圖像與許多其他圖形的方法赞警。你可以在 onDraw 方法里面使用那些方法來創(chuàng)建你的 UI妓忍。
在你調(diào)用任何繪制方法之前,你需要創(chuàng)建一個 Paint 對象愧旦。
1.創(chuàng)建繪圖對象
android.graphics framework 把繪制定義為下面兩類:
- 繪制什么单默,由 Canvas 處理
- 如何繪制,由 Paint 處理
例如 Canvas 提供繪制一條直線的方法忘瓦,Paint 提供直線顏色搁廓。Canvas 提供繪制矩形的方法,Paint 定義是否使用顏色填充耕皮。簡單來說:Canvas 定義你在屏幕上畫的圖形境蜕,而 Paint 定義顏色,樣式凌停,字體粱年,所以在繪制之前,你需要創(chuàng)建一個或者多個 Paint 對象罚拟。在這個 PieChart 的例子台诗,是在 init() 方法實現(xiàn)的,由 constructor 調(diào)用赐俗。
private void init() {
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(mTextColor);
if (mTextHeight == 0) {
mTextHeight = mTextPaint.getTextSize();
} else {
mTextPaint.setTextSize(mTextHeight);
}
mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPiePaint.setStyle(Paint.Style.FILL);
mPiePaint.setTextSize(mTextHeight);
mShadowPaint = new Paint(0);
mShadowPaint.setColor(0xff101010);
mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));
...
剛開始就創(chuàng)建對象是一個重要的優(yōu)化技巧拉队。Views 會被頻繁的重新繪制,初始化許多繪制對象需要花費昂貴的代價阻逮。在 onDraw 方法里面創(chuàng)建繪制對象會嚴(yán)重影響到性能并使得你的 UI 顯得卡頓粱快。
2.處理布局事件
為了正確的繪制你的 View,你需要知道 View 的大小。復(fù)雜的自定義 View 通常需要根據(jù)在屏幕上的大小與形狀執(zhí)行多次 layout 計算事哭。而不是假設(shè)這個 View 在屏幕上的顯示大小漫雷。即使只有一個程序會使用你的 View,仍然是需要處理屏幕大小不同鳍咱,密度不同降盹,方向不同所帶來的影響。
盡管 View 有許多方法是用來計算大小的谤辜,但是大多數(shù)是不需要重寫的澎现。如果你的 View 不需要特別的控制它的大小,唯一需要重寫的方法是[ onSizeChanged() ](http://developer.android.com/reference/android/view/View.html#onSizeChanged(int, int, int, int))每辟,當(dāng)你的 View 第一次被賦予一個大小時剑辫,或者你的 View 大小被更改時會被執(zhí)行。在 onSizeChanged 方法里面計算位置渠欺,間距等其他與你的 View 大小值妹蔽。
當(dāng)你的 View 被設(shè)置大小時,layout manager (布局管理器)假定這個大小包括所有的 View 的內(nèi)邊距 (padding) 挠将。當(dāng)你計算你的 View 大小時胳岂,你必須處理內(nèi)邊距的值。這段 PieChart.onSizeChanged() 中的代碼演示該怎么做:
// Account for padding
float xpad = (float)(getPaddingLeft() + getPaddingRight());
float ypad = (float)(getPaddingTop() + getPaddingBottom());
// Account for the label
if (mShowText) xpad += mTextWidth;
float ww = (float)w - xpad;
float hh = (float)h - ypad;
// Figure out how big we can make the pie.
float diameter = Math.min(ww, hh);
如果你想更加精確的控制你的 View 的大小舔稀,需要重寫[ onMeasure() ](http://developer.android.com/reference/android/view/View.html#onMeasure(int, int))方法乳丰。這個方法的參數(shù)是 View.MeasureSpec,它會告訴你的 View 的父控件的大小内贮。那些值被包裝成 int 類型产园,你可以使用靜態(tài)方法來獲取其中的信息。
這里是一個實現(xiàn) onMeasure() 的例子夜郁。在這個例子中 PieChart 試著使它的區(qū)域足夠大什燕,使pie可以像它的 label 一樣大:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Try for a width based on our minimum
int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
int w = resolveSizeAndState(minw, widthMeasureSpec, 1);
// Whatever the width ends up being, ask for a height that would let the pie
// get as big as it can
int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);
setMeasuredDimension(w, h);
}
上面的代碼有三個重要的事情需要注意:
- 計算的過程有把 View 的 padding 考慮進(jìn)去。這個在后面會提到竞端,這部分是 View 所控制的屎即。
- 幫助方法 resolveSizeAndState() 是用來創(chuàng)建最終的寬高值的。這個方法比較 View 的期望值與傳遞給 onMeasure 方法的 spec 值事富,然后返回一個合適的 View.MeasureSpec 值技俐。
- onMeasure() 沒有返回值。它通過調(diào)用 setMeasuredDimension() 來獲取結(jié)果统台。調(diào)用這個方法是強(qiáng)制執(zhí)行的雕擂,如果你遺漏了這個方法,會出現(xiàn)運行時異常饺谬。
3.繪圖
每個 View 的 onDraw 都是不同的捂刺,但是有下面一些常見的操作:
- 繪制文字使用 drawText() 谣拣。指定字體通過調(diào)用 setTypeface() 募寨,通過 setColor() 來設(shè)置文字顏色.
- 繪制基本圖形使用 drawRect() , drawOval() , drawArc() . 通過 setStyle() 來指定形狀是否需要 filled, outlined.
- 繪制一些復(fù)雜的圖形族展,使用 Path 類. 通過給 Path 對象添加直線與曲線, 然后使用 drawPath() 來繪制圖形. 和基本圖形一樣,paths 也可以通過
setStyle 來設(shè)置是outlined, filled, both. - 通過創(chuàng)建 LinearGradient 對象來定義漸變拔鹰。調(diào)用 setShader() 來使用 LinearGradient仪缸。
- 通過使用 drawBitmap 來繪制圖片.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the shadow
canvas.drawOval(
mShadowBounds,
mShadowPaint
);
// Draw the label text
canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);
// Draw the pie slices
for (int i = 0; i < mData.size(); ++i) {
Item it = mData.get(i);
mPiePaint.setShader(it.mShader);
canvas.drawArc(mBounds,
360 - it.mEndAngle,
it.mEndAngle - it.mStartAngle,
true, mPiePaint);
}
// Draw the pointer
canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
}
三、使得 View 可交互
繪制 UI 僅僅是創(chuàng)建自定義 View 的一部分列肢。你還需要使得你的 View 能夠以模擬現(xiàn)實世界的方式來進(jìn)行反饋恰画。對象應(yīng)該總是與現(xiàn)實情景能夠保持一致。例如瓷马,圖片不應(yīng)該突然消失又從另外一個地方出現(xiàn)拴还,因為在現(xiàn)實世界里面不會發(fā)生那樣的事情。正確的應(yīng)該是欧聘,圖片從一個地方移動到另外一個地方片林。
用戶應(yīng)該可以感受到 UI 上的微小變化,并對模仿現(xiàn)實世界的細(xì)微之處反應(yīng)強(qiáng)烈怀骤。例如费封,當(dāng)用戶 fling (迅速滑動)一個對象時,應(yīng)該在開始時感到摩擦帶來的阻力蒋伦,在結(jié)束時感到 fling 帶動的動力弓摘。應(yīng)該在滑動開始與結(jié)束的時候給用戶一定的反饋。
1.處理輸入的手勢
像許多其他 UI 框架一樣痕届,Android 提供一個輸入事件模型韧献。用戶的動作會轉(zhuǎn)換成觸發(fā)一些回調(diào)函數(shù)的事件,你可以重寫這些回調(diào)方法來定制你的程序應(yīng)該如何響應(yīng)用戶的輸入事件研叫。在 Android 中最常用的輸入事件是 touch势决,它會觸發(fā) onTouchEvent(android.view.MotionEvent) 的回調(diào)。重寫這個方法來處理 touch 事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
Touch 事件本身并不是特別有用蓝撇。如今的 touch UI 定義了 touch 事件之間的相互作用果复,叫做 gestures 。例如 tapping,pulling,flinging 與
zooming 渤昌。為了把那些 touch 的源事件轉(zhuǎn)換成 gestures, Android 提供了 GestureDetector 虽抄。
通過傳入 GestureDetector.OnGestureListener 的一個實例構(gòu)建一個 GestureDetector 。如果你只是想要處理幾種 gestures (手勢操作)你可以繼承 GestureDetector.SimpleOnGestureListener 独柑,而不用實現(xiàn) GestureDetector.OnGestureListener 接口迈窟。例如,下面的代碼創(chuàng)建一個繼承 GestureDetector.SimpleOnGestureListener 的類忌栅,并重寫 onDown(MotionEvent) 车酣。
class mListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
}
mDetector = new GestureDetector(PieChart.this.getContext(), new mListener());
不管你是否使用 GestureDetector.SimpleOnGestureListener,曲稼,你必須總是實現(xiàn) onDown() 方法,并返回 true 湖员。這一步是必須的贫悄,因為所有的
gestures 都是從 onDown() 開始的。如果你在 onDown() 里面返回 false娘摔,系統(tǒng)會認(rèn)為你想要忽略后續(xù)的 gesture窄坦,那么GestureDetector.OnGestureListener 的其他回調(diào)方法就不會被執(zhí)行到了。一旦你實現(xiàn)了 GestureDetector.OnGestureListener 并且創(chuàng)建了GestureDetector 的實例, 你可以使用你的 GestureDetector 來中止你在 onTouchEven t里面收到的 touch 事件凳寺。
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = mDetector.onTouchEvent(event);
if (!result) {
if (event.getAction() == MotionEvent.ACTION_UP) {
stopScrolling();
result = true;
}
}
return result;
}
當(dāng)你傳遞一個 touch 事件到 onTouchEvent() 時鸭津,若這個事件沒有被辨認(rèn)出是何種 gesture,它會返回 false 肠缨。你可以執(zhí)行自定義的 gesture-decection 代碼逆趋。
2.創(chuàng)建基本合理的物理運動
Gestures 是控制觸摸設(shè)備的一種強(qiáng)有力的方式,但是除非你能夠產(chǎn)出一個合理的觸摸反饋晒奕,否則將是違反用戶直覺的闻书。一個很好的例子是 fling 手勢,用戶迅速的在屏幕上移動手指然后抬手離開屏幕吴汪。這個手勢應(yīng)該使得 UI 迅速的按照 fling 的方向進(jìn)行滑動惠窄,然后慢慢停下來,就像是用戶旋轉(zhuǎn)一個飛輪一樣漾橙。
但是模擬這個飛輪的感覺并不簡單杆融,要想得到正確的飛輪模型,需要大量的物理霜运,數(shù)學(xué)知識脾歇。幸運的是,Android 有提供幫助類來模擬這些物理行為淘捡。 Scroller 是控制飛輪式的 fling 的基類藕各。
要啟動一個 fling,需調(diào)用 fling()焦除,并傳入啟動速率 x 激况、y 的最小值和最大值,對于啟動速度值膘魄,可以使用 GestureDetector 計算得出乌逐。
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mScroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
postInvalidate();
}
Note: 盡管速率是通過 GestureDetector 來計算的,許多開發(fā)者感覺使用這個值使得 fling 動畫太快创葡。通常把 x 與 y 設(shè)置為 4 到 8 倍的關(guān)系浙踢。
調(diào)用[ fling() ](http://developer.android.com/reference/android/widget/Scroller.html#fling(int, int, int, int, int, int, int, int))時會為 fling 手勢設(shè)置物理模型。然后灿渴,通過調(diào)用定期調(diào)用 Scroller.computeScrollOffset() 來更新 Scroller 洛波。 computeScrollOffset() 通過讀取當(dāng)前時間和使用物理模型來計算 x 和 y 的位置更新 Scroller 對象的內(nèi)部狀態(tài)胰舆。調(diào)用 getCurrX() 和 getCurrY() 來獲取這些值。
大多數(shù) View 通過 Scroller 對象的 x , y 的位置直接到[ scrollTo() ](http://developer.android.com/reference/android/view/View.html#scrollTo(int, int))蹬挤,PieChart 例子稍有不同缚窿,它使用當(dāng)前滾動 y 的位置設(shè)置圖表的旋轉(zhuǎn)角度。
if (!mScroller.isFinished()) {
mScroller.computeScrollOffset();
setPieRotation(mScroller.getCurrY());
}
Scroller 類會為你計算滾動位置闻伶,但是他不會自動把哪些位置運用到你的View 上面滨攻。你有責(zé)任確保 View 獲取并運用到新的坐標(biāo)够话。你有兩種方法來實現(xiàn)這件事情:
- 在調(diào)用 fling() 之后執(zhí)行 postInvalidate()蓝翰,這是為了確保能強(qiáng)制進(jìn)行重畫。這個技術(shù)需要每次在 onDraw 里面計算過 scroll offsets (滾動偏移量)之后調(diào)用 postInvalidate()女嘲。
- 使用 ValueAnimator 在 fling 是展現(xiàn)動畫畜份,并且通過調(diào)用addUpdateListener() 增加對 fling 過程的監(jiān)聽。
這個 PieChart 的例子使用了第二種方法欣尼。這個方法使用起來會稍微復(fù)雜一點爆雹,但是它更有效率并且避免了不必要的重畫的 View 進(jìn)行重繪。缺點是 ValueAnimator 是從API Level 11 才有的愕鼓。因此他不能運用到 3.0 的系統(tǒng)之前的版本上钙态。
Note: ValueAnimator 雖然是 API 11 才有的,但是你還是可以在最低版本低于 3.0 的系統(tǒng)上使用它菇晃,做法是在運行時判斷當(dāng)前的 API Level册倒,如果低于 11 則跳過。
mScroller = new Scroller(getContext(), null, true);
mScrollAnimator = ValueAnimator.ofFloat(0,1);
mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (!mScroller.isFinished()) {
mScroller.computeScrollOffset();
setPieRotation(mScroller.getCurrY());
} else {
mScrollAnimator.cancel();
onScrollFinished();
}
}
});
3.使過渡平滑
用戶期待一個 UI 之間的切換是能夠平滑過渡的磺送。UI 元素需要做到漸入淡出來取代突然出現(xiàn)與消失驻子。Android 從 3.0 開始有提供 property animation framework ,用來使得平滑過渡變得更加容易估灿。
使用這套動畫系統(tǒng)時崇呵,任何時候?qū)傩缘母淖兌紩绊懙侥愕囊晥D,所以不要直接改變屬性的值馅袁。而是使用 ValueAnimator 來實現(xiàn)改變域慷。在下面的例子中,在 PieChart 中更改選擇的部分將導(dǎo)致整個圖表的旋轉(zhuǎn)汗销,以至選擇的進(jìn)入選擇區(qū)內(nèi)犹褒。ValueAnimator 在數(shù)百毫秒內(nèi)改變旋轉(zhuǎn)量,而不是突然地設(shè)置新的旋轉(zhuǎn)值大溜。
mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0);
mAutoCenterAnimator.setIntValues(targetAngle);
mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
mAutoCenterAnimator.start();
如果你想改變的是 View 的某些基礎(chǔ)屬性化漆,你可以使用 ViewPropertyAnimator ,它能夠同時執(zhí)行多個屬性的動畫。
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();
四钦奋、優(yōu)化自定義 View
1.Do Less, Less Frequently
為了加速你的 View座云,對于頻繁調(diào)用的方法疙赠,需要盡量減少不必要的代碼。先從 onDraw 開始朦拖,需要特別注意不應(yīng)該在這里做內(nèi)存分配的事情圃阳,因為它會導(dǎo)致 GC,從而導(dǎo)致卡頓璧帝。在初始化或者動畫間隙期間做分配內(nèi)存的動作捍岳。不要在動畫正在執(zhí)行的時候做內(nèi)存分配的事情。
你還需要盡可能的減少 onDraw 被調(diào)用的次數(shù)睬隶,大多數(shù)時候?qū)е?onDraw 都是因為調(diào)用了 invalidate()锣夹。因此請盡量減少調(diào)用 invaildate() 的次數(shù)。如果可能的話苏潜,盡量調(diào)用含有4個參數(shù)的 invalidate() 方法而不是沒有參數(shù)的 invalidate()银萍。沒有參數(shù)的 invalidate 會強(qiáng)制重繪整個 View 。
另外一個非常耗時的操作是請求layout恤左。任何時候執(zhí)行 requestLayout()贴唇,會使得 Android UI 系統(tǒng)去遍歷整個 View 的層級來計算出每一個 View 的大小。如果找到有沖突的值飞袋,它會需要重新計算好幾次戳气。另外需要盡量保持 View 的層級是扁平化的,這樣對提高效率很有幫助巧鸭。
如果你有一個復(fù)雜的 UI瓶您,你應(yīng)該考慮寫一個自定義的 ViewGroup 來執(zhí)行他的 layout 操作。與內(nèi)置的 View 不同蹄皱,自定義的 View 可以使得程序僅僅測量這一部分览闰,這避免了遍歷整個view的層級結(jié)構(gòu)來計算大小。這個 PieChart 例子展示了如何繼承 ViewGroup 作為自定義 View 的一部分巷折。PieChart 有子 views压鉴,但是它從來不測量它們。而是根據(jù)他自身的 layout 法則锻拘,直接設(shè)置它們的大小油吭。
2.使用硬件加速
從 Android 3.0 開始创橄,Android 的 2D 圖像系統(tǒng)可以通過 GPU (Graphics Processing Unit)) 來加速尖啡。GPU 硬件加速可以提高許多程序的性能。但是這并不是說它適合所有的程序拌夏。Android Framework 讓你能過隨意控制你的程序的各個部分是否啟用硬件加速推穷。
參考 Android Developers Guide 中的 Hardware Acceleration 來學(xué)習(xí)如何在 application心包,activity,或 window 層啟用加速馒铃。注意除了 Android Guide 的指導(dǎo)之外蟹腾,你必須要設(shè)置你的應(yīng)用的 target API 為 11痕惋,或更高,通過在你的 AndroidManifest.xml 文件中增加 < uses-sdk android:targetSdkVersion="11"/> 娃殖。
一旦你開啟了硬件加速值戳,性能的提示并不一定可以明顯察覺到。移動設(shè)備的 GPU 在某些例如 scaling炉爆,rotating 與 translating 的操作中表現(xiàn)良好堕虹。但是對其他一些任務(wù),比如畫直線或曲線芬首,則表現(xiàn)不佳赴捞。為了充分發(fā)揮 GPU 加速,你應(yīng)該最大化 GPU 擅長的操作的數(shù)量衩辟,最小化 GPU 不擅長操作的數(shù)量螟炫。
在下面的例子中波附,繪制 pie 是相對來說比較費時的艺晴。解決方案是把 pie 放到一個子 View 中,并設(shè)置 View 使用 LAYER_TYPE_HARDWARE 來進(jìn)行加速掸屡。
private class PieView extends View {
public PieView(Context context) {
super(context);
if (!isInEditMode()) {
setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Item it : mData) {
mPiePaint.setShader(it.mShader);
canvas.drawArc(mBounds,
360 - it.mEndAngle,
it.mEndAngle - it.mStartAngle,
true, mPiePaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mBounds = new RectF(0, 0, w, h);
}
RectF mBounds;
}
通過這樣的修改以后封寞,PieChart.PieView.onDraw() 只會在第一次現(xiàn)實的時候被調(diào)用。之后仅财,pie chart 會被緩存為一張圖片狈究,并通過 GPU 來進(jìn)行重畫不同的角度。GPU 特別擅長這類的事情盏求,并且表現(xiàn)效果突出抖锥。
緩存圖片到 hardware layer 會消耗 video memory,而 video memory 又是有限的碎罚“醴希基于這樣的考慮,僅僅在用戶觸發(fā) scrolling 的時候使用LAYER_TYPE_HARDWARE荆烈,在其他時候拯勉,使用 LAYER_TYPE_NONE。