標籤:
原文地址:http://android.xsoftlab.net/training/custom-views/custom-drawing.html#draw
自訂View最重要的部分就是它的樣子了。自訂View的繪製根據應用的需要或者簡單亦或者複雜。這節課的內容涵蓋了大多數通用的知識點。
重寫onDraw()方法
繪製自訂View很重要的一個步驟就是重寫它的onDraw()方法。該方法含有一個Canvas對象作為參數,用來使View繪製它本身的內容。Canvas類定義了用於繪製文本,線條,位元影像以及許多其它基礎的物理圖形的方法。你可以在onDraw()方法中使用這些方法來建立屬於你自己的UI效果。
在開始任何繪製之前,必須先建立一個Paint對象。後面的部分會討論Paint的相關知識。
建立繪製對象
android.graphics架構將繪製分為了兩塊地區:
- 要繪製什麼,由Canvas控制
- 如何繪製,由Paint控制
舉個例子,Canvas提供了用於繪製線條的方法,而Paint則定義了線條的顏色。Canvas擁有繪製矩形的方法,而Paint則定義了是否要使顏色填充這個矩形。簡而言之,Canvas定義了你繪製在螢幕上的形狀,而Paint則定義了這些形狀的顏色、風格以及字型等等。
所以,在開始繪製任何事物之前,你需要先建立一個或多個Paint對象。樣本PieChart將這些工作放在了一個名為init的方法中,它由構造方法調用:
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)); ...
提前建立對象是一項非常重要的最佳化手段。View會很頻繁的繪製,建立繪製對象的開銷十分高昂。在onDraw()方法中建立繪製對象會明顯的降低應用的效能,並可能會導致UI出現停頓。
處理布局事件
為了可以正確的繪製View,首先需要知道View的尺寸。複雜一點的View經常需要執行多次布局計算。你永遠不要假定View的尺寸,就算是只有一款應用使用了你的View。因為APP需要處理不同的螢幕尺寸,不同的螢幕密度,以及在垂直模式及水平模式下不同的高寬比。
儘管View擁有很多測量尺寸的方法,但是絕大多數是不需要重寫的。如果View不需要特別控制它的尺寸,你只需要重寫一個方法:onSizeChanged().
onSizeChanged()方法會在首次分配尺寸的時候調用,如果尺寸再次變更時則會再次調用。我們在onSizeChanged()方法中計算View的位置、尺寸以及其它任何與尺寸相關的值,而不是在每次繪製的時候重新計算它們。在樣本PieChart中,onSizeChanged()方法內部計算了餅圖的矩形邊框以及文本和其它可視元素的相對位置。
當View被分配了一個尺寸時,布局管理器會假設該尺寸包含了View所有的內邊距。所以在計算View的尺寸時要將View的內邊距計算在內。下面的程式碼片段展示了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()方法。這個方法的兩個參數都為View.MeasureSpec。它們用於告訴View的父布局希望View是多大,這個View的尺寸是最大值還是只是一個建議值等等。隨著最佳化,這些值被存放在一個被封裝後的整型值內,你必須使用View.MeasureSpec的一個靜態方法來提取儲存在這個整型值內的資訊。
下面是onMeasure()方法的一個樣本。在這個實現中,PieCart嘗試將自己的地區擴大到內部標籤的大小。
@Overrideprotected 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的內邊距一併計算在內。正如前面所說的,這是View本身的職責。
- 輔助方法resolveSizeAndState()用於建立最終的寬度值與高度值。這個輔助方法通過比較View的期望值與onMeasure()回調的spec值,最後返回一個適當的View.MeasureSpec值。
- onMeasure()沒有傳回值。相反的,該方法通過調用setMeasuredDimension()方法與外界交流。調用這個方法是強制要求的。如果你忽略了這個調用,那麼View類會拋出一個運行時異常。
繪製
完成對象建立代碼與尺寸測量代碼之後,接下來就可以實現onDraw()方法了。每個View的onDraw()方法都不相同,但是它們還是有一些相同特點存在的:
- 使用drawText()方法繪製文本。通過setTypeface()方法指定字型類型,通過setColor()方法設定文本顏色。
- 通過drawRect()、drawOval()和drawArc()繪製基礎圖形。通過setStyle()更改圖形是填充模式還是繪製輪廓模式,或者兩者都不是。
- 通過Path類來繪製更加複雜的圖形。通過給Path對象添加線條及曲線來定義形狀,然後通過drawPath()方法繪製這些形狀。只是這些基礎形狀可以通過setStyle()方法來定義它們的Path風格。
- 通過建立LinearGradient對象可以定義梯度填充模式。調用setShader()方法來填充形狀。
- 通過drawBitmap()方法繪製位元影像。
舉個例子,下面的代碼用來繪製PieChart。它混合使用了文本、線條以及形狀。
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);}
Android官方開發文檔Training系列課程中文版:建立自訂View之View的繪製