自訂檢視區塊最重要的部分是外觀。自訂繪圖 可根據應用程式需求而簡化或複雜這份文件 包含一些最常見的作業
若需更多資訊,請參閲 可繪項目總覽。
覆寫 onDraw()
繪製自訂檢視區塊的最重要步驟是覆寫
onDraw()
方法。onDraw()
的參數是
Canvas
物件,讓檢視畫面可以用來繪製本身。Canvas
類別
定義繪製文字、線條、點陣圖以及其他許多圖形的方法
以及一些基本問題您可以在 onDraw()
中使用這些方法建立
以及自訂使用者介面 (UI)。
請先建立
Paint
物件。
下一節將詳細討論 Paint
。
建立繪圖物件
android.graphics
框架將繪圖分為以下兩個領域:
- 要繪製的內容,由
Canvas
處理。 - 如何繪製,由
Paint
處理。
舉例來說,Canvas
提供繪製線條的方法;
Paint
提供定義該線條顏色的方法。
Canvas
具有繪製矩形的方法,而 Paint
定義要以顏色填滿整個矩形,還是將矩形留白。
Canvas
定義可在螢幕上繪製的形狀;以及
Paint
定義每個形狀的顏色、樣式、字型等
你繪製的內容
繪製任何內容之前,請先建立一或多個 Paint
物件。
以下範例會在名為 init
的方法中執行此操作。此方法
從 Java 的建構函式呼叫,但也可內嵌在
Kotlin。
Kotlin
@ColorInt private var textColor // Obtained from style attributes. @Dimension private var textHeight // Obtained from style attributes. private val textPaint = Paint(ANTI_ALIAS_FLAG).apply { color = textColor if (textHeight == 0f) { textHeight = textSize } else { textSize = textHeight } } private val piePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL textSize = textHeight } private val shadowPaint = Paint(0).apply { color = 0x101010 maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.NORMAL) }
Java
private Paint textPaint; private Paint piePaint; private Paint shadowPaint; @ColorInt private int textColor; // Obtained from style attributes. @Dimension private float textHeight; // Obtained from style attributes. private void init() { textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setColor(textColor); if (textHeight == 0) { textHeight = textPaint.getTextSize(); } else { textPaint.setTextSize(textHeight); } piePaint = new Paint(Paint.ANTI_ALIAS_FLAG); piePaint.setStyle(Paint.Style.FILL); piePaint.setTextSize(textHeight); shadowPaint = new Paint(0); shadowPaint.setColor(0xff101010); shadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL)); ... }
預先建立物件是重要的最佳化措施。觀看次數:
而且許多繪圖物件需要高昂的初始化作業。
在 onDraw()
方法中大量建立繪圖物件
會降低效能,並讓 UI 看起來很緩慢。
處理版面配置事件
如要正確繪製自訂檢視區塊,請確認其大小。複雜自訂 檢視畫面通常需要根據大小 以及螢幕區域的形狀請勿在 在畫面上檢視畫面即使只有一個應用程式會使用檢視畫面,該應用程式也必須 處理不同的螢幕大小、多種螢幕密度和各種顯示設定 垂直和橫向模式下的顯示比例差異
雖然View
有許多方法處理評估方法,
已覆寫。如果檢視區塊不需要特別控制大小
覆寫一個方法:
onSizeChanged()
。
系統會呼叫 onSizeChanged()
,
以及檢視大小因任何原因而改變時再次顯示計算
位置、尺寸以及任何其他與視圖大小有關
onSizeChanged()
,不必在每次繪圖時重新計算。
在以下範例中,onSizeChanged()
是檢視畫面的位置
計算圖表的邊界矩形與
文字標籤和其他視覺元素
為檢視畫面指派大小時,版面配置管理員會假設該尺寸
包括檢視畫面的邊框間距計算時的邊框間距值
檢視區塊的大小以下是 onSizeChanged()
的程式碼片段,說明瞭
步驟如下:
Kotlin
private val showText // Obtained from styled attributes. private val textWidth // Obtained from styled attributes. override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) // Account for padding. var xpad = (paddingLeft + paddingRight).toFloat() val ypad = (paddingTop + paddingBottom).toFloat() // Account for the label. if (showText) xpad += textWidth.toFloat() val ww = w.toFloat() - xpad val hh = h.toFloat() - ypad // Figure out how big you can make the pie. val diameter = Math.min(ww, hh) }
Java
private Boolean showText; // Obtained from styled attributes. private int textWidth; // Obtained from styled attributes. @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // Account for padding. float xpad = (float)(getPaddingLeft() + getPaddingRight()); float ypad = (float)(getPaddingTop() + getPaddingBottom()); // Account for the label. if (showText) xpad += textWidth; float ww = (float)w - xpad; float hh = (float)h - ypad; // Figure out how big you can make the pie. float diameter = Math.min(ww, hh); }
如果您需要更精細地控制檢視區塊的版面配置參數,請實作
onMeasure()
。
這個方法的參數
View.MeasureSpec
值讓您瞭解檢視畫面父項希望檢視的規模
此大小可以是強制上限
或只是建議比方說
這些值都會儲存為封裝的整數,而您可以使用
View.MeasureSpec
:將每個整數中儲存的資訊解壓縮。
以下是 onMeasure()
的實作範例。在本
就會嘗試放大區域的範圍,讓圖表更大
視為標籤:
Kotlin
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { // Try for a width based on your minimum. val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth val w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1) // Whatever the width is, ask for a height that lets the pie get as big as // it can. val minh: Int = View.MeasureSpec.getSize(w) - textWidth.toInt() + paddingBottom + paddingTop val h: Int = View.resolveSizeAndState(minh, heightMeasureSpec, 0) setMeasuredDimension(w, h) }
Java
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on your minimum. int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); // Whatever the width is, ask for a height that lets the pie get as big as it // can. int minh = MeasureSpec.getSize(w) - (int)textWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(minh, heightMeasureSpec, 0); setMeasuredDimension(w, h); }
在這個程式碼中,有三個重點需要注意:
- 在計算時,會考量檢視畫面的邊框間距。如先前所述 這是檢視畫面的責任
- Helper 方法
resolveSizeAndState()
用來建立最終的寬度和高度值。這個輔助程式會傳回 取得適當的View.MeasureSpec
值 所需的檢視畫面大小到傳入onMeasure()
的值。 onMeasure()
沒有傳回值。相反地 會藉由呼叫setMeasuredDimension()
。 必須呼叫這個方法。如果您省略此呼叫,View
類別會擲回執行階段例外狀況。
繪圖
定義物件建立作業並評估程式碼後
onDraw()
。每個檢視畫面都會以不同的方式實作 onDraw()
。
但以下介紹幾種常見做法:
- 繪製文字時使用
drawText()
。 呼叫 來指定字體setTypeface()
以及文字顏色setColor()
。 - 運用以下方式繪製基本形狀
drawRect()
,drawOval()
, 和drawArc()
。 呼叫setStyle()
。 - 使用
Path
類別在Path
中加入線條和曲線,即可定義形狀 然後用模型繪製形狀drawPath()
。 和原始形狀一樣,可以將路徑填滿、填滿路徑,或兩者同時顯示。 依setStyle()
而定。 -
建立
LinearGradient
,定義漸層填滿 如需儲存大量結構化物件 建議使用 Cloud Bigtable致電setShader()
將LinearGradient
用於填滿形狀。 - 使用以下應用程式繪製點陣圖:
drawBitmap()
。
下方程式碼可繪製文字、線條和形狀的組合:
Kotlin
private val data = mutableListOf<Item>() // A list of items that are displayed. private var shadowBounds = RectF() // Calculated in onSizeChanged. private var pointerRadius: Float = 2f // Obtained from styled attributes. private var pointerX: Float = 0f // Calculated in onSizeChanged. private var pointerY: Float = 0f // Calculated in onSizeChanged. private var textX: Float = 0f // Calculated in onSizeChanged. private var textY: Float = 0f // Calculated in onSizeChanged. private var bounds = RectF() // Calculated in onSizeChanged. private var currentItem: Int = 0 // The index of the currently selected item. override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.apply { // Draw the shadow. drawOval(shadowBounds, shadowPaint) // Draw the label text. drawText(data[currentItem].label, textX, textY, textPaint) // Draw the pie slices. data.forEach {item -> piePaint.shader = item.shader drawArc( bounds, 360 - item.endAngle, item.endAngle - item.startAngle, true, piePaint ) } // Draw the pointer. drawLine(textX, pointerY, pointerX, pointerY, textPaint) drawCircle(pointerX, pointerY, pointerRadius, textPaint) } } // Maintains the state for a data item. private data class Item( var label: String, var value: Float = 0f, @ColorInt var color: Int = 0, // Computed values. var startAngle: Float = 0f, var endAngle: Float = 0f, var shader: Shader )
Java
private List<Item> data = new ArrayList<Item>(); // A list of items that are displayed. private RectF shadowBounds; // Calculated in onSizeChanged. private float pointerRadius; // Obtained from styled attributes. private float pointerX; // Calculated in onSizeChanged. private float pointerY; // Calculated in onSizeChanged. private float textX; // Calculated in onSizeChanged. private float textY; // Calculated in onSizeChanged. private RectF bounds; // Calculated in onSizeChanged. private int currentItem = 0; // The index of the currently selected item. protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the shadow. canvas.drawOval( shadowBounds, shadowPaint ); // Draw the label text. canvas.drawText(data.get(currentItem).label, textX, textY, textPaint); // Draw the pie slices. for (int i = 0; i < data.size(); ++i) { Item it = data.get(i); piePaint.setShader(it.shader); canvas.drawArc( bounds, 360 - it.endAngle, it.endAngle - it.startAngle, true, piePaint ); } // Draw the pointer. canvas.drawLine(textX, pointerY, pointerX, pointerY, textPaint); canvas.drawCircle(pointerX, pointerY, pointerRadius, textPaint); } // Maintains the state for a data item. private class Item { public String label; public float value; @ColorInt public int color; // Computed values. public int startAngle; public int endAngle; public Shader shader; }
套用圖像效果
Android 12 (API 級別 31) 新增了
RenderEffect
類別,套用模糊、色彩濾鏡等常見的圖像效果。
Android 著色器效果等等
View
物件和
呈現的階層結構可將效果合併為連鎖效果
呈現出內外效果或混合效果支援這項功能
會視裝置的處理效能而定。
您也可以將效果套用至
RenderNode
:
呼叫 View
View.setRenderEffect(RenderEffect)
。
如要實作 RenderEffect
物件,請按照下列步驟操作:
view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))
您可以透過程式輔助方式建立檢視畫面,也可以利用 XML 版面配置和
使用 View 繫結 或
findViewById()
。