這個程式碼研究室是「Android Kotlin 進階功能」課程的一部分。如果您按部就班完成每一堂程式碼研究室課程,就能充分體驗到本課程的價值,但這不是強制要求。如要查看所有課程程式碼研究室,請前往 Android Kotlin 進階功能程式碼研究室登陸頁面。
簡介
Android 提供大量 View 子類別,例如 Button、TextView、EditText、ImageView、CheckBox 或 RadioButton。您可以使用這些子類別建構 UI,讓使用者與應用程式互動,並顯示資訊。如果沒有任何 View 子類別符合需求,您可以建立稱為「自訂」 檢視區塊的 View 子類別。
如要建立自訂檢視區塊,您可以擴充現有的 View 子類別 (例如 Button 或 EditText),也可以建立自己的 View 子類別。直接擴充 View,即可覆寫 onDraw() 方法,為 View 繪製任何大小和形狀的互動式 UI 元素。
建立自訂檢視區塊後,您可以將其新增至活動版面配置,做法與新增 TextView 或 Button 相同。
本課程說明如何從頭開始建立自訂檢視區塊,方法是擴充 View。
必備知識
- 瞭解如何使用 Android Studio 建立含有 Activity 的應用程式並執行。
課程內容
- 如何擴充
View來建立自訂檢視區塊。 - 如何繪製圓形的自訂檢視區塊。
- 如何使用監聽器處理使用者與自訂檢視區塊的互動。
- 如何在版面配置中使用自訂檢視區塊。
學習內容
CustomFanController 應用程式會示範如何擴充 View 類別,建立自訂檢視子類別。新子類別稱為 DialView。
應用程式會顯示類似實體風扇控制項的圓形 UI 元素,並提供關閉 (0)、低 (1)、中 (2) 和高 (3) 等設定。使用者輕觸檢視區塊時,選取指標會移至下一個位置:0-1-2-3,然後返回 0。此外,如果選取 1 以上,檢視畫面圓形部分的背景顏色會從灰色變為綠色 (表示風扇電源已開啟)。


View 是應用程式 UI 的基本建構區塊。View 類別提供許多子類別 (稱為「UI 小工具」),可滿足一般 Android 應用程式使用者介面的許多需求。
Button 和 TextView 等 UI 建構區塊是擴充 View 類別的子類別。為節省時間和開發工作,您可以擴充其中一個 View 子類別。自訂檢視畫面會沿用上層檢視畫面的外觀和風格,而您可以覆寫要變更的行為或外觀屬性。舉例來說,如果您擴充 EditText 來建立自訂檢視區塊,該檢視區塊的行為就如同 EditText 檢視區塊,但也可以自訂顯示內容,例如顯示 X 按鈕,清除文字輸入欄位中的文字。
您可以擴充任何 View 子類別 (例如 EditText),取得自訂檢視畫面,並選擇最符合您需求的項目。接著,您可以在一或多個版面配置中,將自訂檢視區塊當做 XML 元素 (含屬性) 使用,就像使用任何其他 View 子類別一樣。
如要從頭開始建立自訂檢視區塊,請擴充 View 類別本身。您的程式碼會覆寫 View 方法,定義檢視區塊的外觀和功能。建立自訂檢視區塊的關鍵在於,您必須負責在畫面上繪製任何大小和形狀的整個 UI 元素。如果您將現有檢視區塊 (例如 Button) 設為子類別,該類別會為您處理繪圖作業。(您將在本程式碼研究室稍後的部分中,進一步瞭解繪圖)。
如要建立自訂檢視畫面,請按照下列一般步驟操作:
- 建立擴充
View的自訂檢視區塊類別,或擴充View子類別 (例如Button或EditText)。 - 如果擴充現有的
View子類別,請只覆寫要變更的行為或外觀。 - 如果擴充
View類別,請在新類別中覆寫onDraw()和onMeasure()等View方法,繪製自訂檢視區塊的形狀並控制其外觀。 - 新增程式碼來回應使用者互動,並視需要重新繪製自訂檢視區塊。
- 在活動的 XML 版面配置中,將自訂檢視區塊類別當做 UI 小工具使用。您也可以為檢視區塊定義自訂屬性,以便在不同版面配置中自訂檢視區塊。
在這項工作中,您將:
- 建立應用程式,並以
ImageView做為自訂檢視區塊的暫時預留位置。 - 擴充
View即可建立自訂檢視區塊。 - 使用繪圖和繪畫值初始化自訂檢視區塊。
步驟 1:建立含有 ImageView 預留位置的應用程式
- 使用「Empty Activity」範本建立名為
CustomFanController的 Kotlin 應用程式。請確認套件名稱為com.example.android.customfancontroller。 - 在「Text」分頁中開啟
activity_main.xml,即可編輯 XML 程式碼。 - 將現有的
TextView換成這段程式碼。這段文字會做為自訂檢視區塊活動中的標籤。
<TextView
android:id="@+id/customViewLabel"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Display3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:textColor="@android:color/black"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="24dp"
android:text="Fan Control"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>- 將這個
ImageView元素新增至版面配置。這是您將在本程式碼研究室中建立的自訂檢視區塊預留位置。
<ImageView
android:id="@+id/dialView"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/darker_gray"
app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"/>- 在兩個 UI 元素中擷取字串和維度資源。
- 按一下「設計」分頁標籤。版面配置應如下所示:

步驟 2:建立自訂檢視區塊類別
- 建立名為
DialView的新 Kotlin 類別。 - 修改類別定義,擴充
View。在系統提示時匯入android.view.View。 - 按一下
View,然後按一下紅色燈泡。選擇「Add Android View constructors using '@JvmOverloads'」(使用「@JvmOverloads」新增 Android 檢視區塊建構函式)。Android Studio 會從View類別新增建構函式。@JvmOverloads註解會指示 Kotlin 編譯器為這個函式產生多載,以取代預設參數值。
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {- 在
DialView類別定義上方 (匯入下方),新增頂層enum,代表可用的風扇轉速。請注意,由於值是字串資源而非實際字串,因此這個enum的類型為Int。Android Studio 會針對每個值中缺少的字串資源顯示錯誤,您將在後續步驟中修正這個問題。
private enum class FanSpeed(val label: Int) {
OFF(R.string.fan_off),
LOW(R.string.fan_low),
MEDIUM(R.string.fan_medium),
HIGH(R.string.fan_high);
}- 在
enum下方新增這些常數。 您將使用這些值繪製撥號指標和標籤。
private const val RADIUS_OFFSET_LABEL = 30
private const val RADIUS_OFFSET_INDICATOR = -35- 在
DialView類別中,定義繪製自訂檢視區塊所需的幾個變數。視需要匯入android.graphics.PointF。
private var radius = 0.0f // Radius of the circle.
private var fanSpeed = FanSpeed.OFF // The active selection.
// position variable which will be used to draw label and indicator circle position
private val pointPosition: PointF = PointF(0.0f, 0.0f)radius是圓形的目前半徑。這個值是在檢視區塊繪製於畫面上時設定。fanSpeed是風扇的目前速度,屬於FanSpeed列舉中的其中一個值。預設值為OFF。- 最後
postPosition是 X、Y 點,用於在畫面上繪製多個檢視區塊的元素。
這些值是在這裡建立及初始化,而不是在實際繪製檢視區塊時,確保實際繪製步驟盡可能快速執行。
- 同樣在
DialView類別定義中,使用一些基本樣式初始化Paint物件。依要求匯入android.graphics.Paint和android.graphics.Typeface。與先前的變數一樣,這些樣式會在此初始化,以加快繪製步驟。
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0f
typeface = Typeface.create( "", Typeface.BOLD)
}- 開啟
res/values/strings.xml,然後新增風扇轉速的字串資源:
<string name="fan_off">off</string>
<string name="fan_low">1</string>
<string name="fan_medium">2</string>
<string name="fan_high">3</string>建立自訂檢視區塊後,您必須能夠繪製該檢視區塊。擴充 View 子類別 (例如 EditText) 時,該子類別會定義檢視區塊的外觀和屬性,並在畫面上繪製自身。因此您不必編寫程式碼來繪製檢視區塊。您可以覆寫父項的方法,改為自訂檢視區塊。
如果您要從頭建立自己的檢視區塊 (方法是擴充 View),則每次畫面重新整理時,您都必須負責繪製整個檢視區塊,並覆寫處理繪圖的 View 方法。如要正確繪製延伸 View 的自訂檢視區塊,請完成下列步驟:
- 覆寫
onSizeChanged()方法,計算檢視區塊首次顯示時的大小,以及每次檢視區塊大小變更時的大小。 - 覆寫
onDraw()方法,使用Canvas物件 (樣式由Paint物件設定) 繪製自訂檢視區塊。 - 回應使用者點選動作時,請呼叫
invalidate()方法,變更檢視區塊的繪製方式,使整個檢視區塊失效,進而強制呼叫onDraw()重新繪製檢視區塊。
每次螢幕重新整理時,系統都會呼叫 onDraw() 方法,每秒可能會呼叫多次。基於效能考量,並避免發生視覺故障,您應盡可能減少 onDraw() 中的工作。特別是不要將分配項目放在 onDraw() 中,因為分配項目可能會導致垃圾收集,進而造成視覺上的頓挫。
Canvas 和 Paint 類別提供許多實用的繪圖快速鍵:
- 使用
drawText()繪製文字。呼叫setTypeface()指定字體,呼叫setColor()指定文字顏色。 - 使用
drawRect()、drawOval()和drawArc()繪製基本形狀。呼叫setStyle(),即可變更形狀的填滿、外框或兩者。 - 使用
drawBitmap()繪製點陣圖。
您將在後續的程式碼研究室中進一步瞭解 Canvas 和 Paint。如要進一步瞭解 Android 如何繪製檢視區塊,請參閱「Android 如何繪製檢視區塊」。
在這項工作中,您將使用 onSizeChanged() 和 onDraw() 方法,在畫面上繪製風扇控制器自訂檢視區塊,包括撥號本身、目前位置指標和指標標籤。您也會建立輔助方法 computeXYForSpeed(),,計算錶盤上指標標籤目前的 X、Y 位置。
步驟 1:計算位置並繪製檢視區塊
- 在
DialView類別中,於初始化下方覆寫View類別的onSizeChanged()方法,計算自訂檢視區塊轉盤的大小。匯入kotlin。依要求匯入math.min。
每當檢視區塊的大小變更時,系統就會呼叫onSizeChanged()方法,包括版面配置加載時首次繪製檢視區塊。覆寫onSizeChanged()即可計算位置、尺寸和與自訂檢視區塊大小相關的任何其他值,不必在每次繪製時重新計算。在本例中,您會使用onSizeChanged()計算錶盤圓形元素的目前半徑。
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
radius = (min(width, height) / 2.0 * 0.8).toFloat()
}- 在
onSizeChanged()下方新增這段程式碼,為PointF類別定義computeXYForSpeed()擴充功能函式。依要求匯入kotlin.math.cos和kotlin.math.sin。這個PointF類別的擴充功能函式會根據目前的FanSpeed位置和錶盤半徑,計算文字標籤和目前指標 (0、1、2 或 3) 在螢幕上的 X、Y 座標。你將在onDraw().中使用這項功能。
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
// Angles are in radians.
val startAngle = Math.PI * (9 / 8.0)
val angle = startAngle + pos.ordinal * (Math.PI / 4)
x = (radius * cos(angle)).toFloat() + width / 2
y = (radius * sin(angle)).toFloat() + height / 2
}- 覆寫
onDraw()方法,使用Canvas和Paint類別在畫面上算繪檢視區塊。依要求匯入android.graphics.Canvas。這是骨架覆寫:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}- 在
onDraw()中新增這行程式碼,根據風扇轉速是否為OFF或任何其他值,將繪製顏色設為灰色 (Color.GRAY) 或綠色 (Color.GREEN)。依要求匯入android.graphics.Color。
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN- 新增這段程式碼,使用
drawCircle()方法繪製錶盤的圓圈。這個方法會使用目前的檢視區塊寬度和高度,找出圓圈的中心、圓圈的半徑,以及目前的繪製顏色。width和height屬性是View超類別的成員,表示檢視區塊目前的尺寸。
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)- 新增下列程式碼,使用
drawCircle()方法繪製風扇速度指標標記的小圓圈。這個部分會使用PointF。computeXYforSpeed()擴充方法,根據目前的風扇轉速計算指標中心的 X、Y 座標。
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)- 最後,在轉盤周圍的適當位置繪製風扇速度標籤 (0、1、2、3)。這個方法部分會再次呼叫
PointF.computeXYForSpeed(),取得每個標籤的位置,並重複使用pointPosition物件,避免分配作業。使用drawText()繪製標籤。
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}已完成的 onDraw() 方法如下所示:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}
}步驟 2:將檢視區塊新增至版面配置
如要將自訂檢視區塊新增至應用程式的 UI,請在活動的 XML 版面配置中將其指定為元素。與其他 UI 元素一樣,您可以使用 XML 元素屬性控制其外觀和行為。
- 在
activity_main.xml中,將dialView的ImageView標記變更為com.example.android.customfancontroller.DialView,並刪除android:background屬性。DialView和原始ImageView都會繼承View類別的標準屬性,因此不需要變更任何其他屬性。新的DialView元素如下所示:
<com.example.android.customfancontroller.DialView
android:id="@+id/dialView"
android:layout_width="@dimen/fan_dimen"
android:layout_height="@dimen/fan_dimen"
app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin" />- 執行應用程式。活動中會顯示風扇控制檢視畫面。

最後一項工作是讓自訂檢視區塊在使用者輕觸檢視區塊時執行動作。每次輕觸時,選取指標應會移至下一個位置:關閉 - 1 - 2 - 3,然後返回關閉。此外,如果選取 1 以上的值,背景應會從灰色變更為綠色,表示風扇電源已開啟。
如要讓自訂檢視畫面可供點選,請:
- 將檢視區塊的
isClickable屬性設為true。這可讓自訂檢視區塊回應點擊。 - 實作
View類別的performClick(),在檢視畫面遭到點選時執行作業。 - 呼叫
invalidate()方法。這會告知 Android 系統呼叫onDraw()方法,重新繪製檢視區塊。
一般來說,使用標準 Android 檢視區塊時,您會實作 OnClickListener(),在使用者點選該檢視區塊時執行動作。如果是自訂檢視區塊,請改為實作 View 類別的 performClick() 方法,並呼叫 super。performClick(). 預設的 performClick() 方法也會呼叫 onClickListener(),因此您可以將動作新增至 performClick(),並保留 onClickListener(),供您或其他開發人員進一步自訂自訂檢視區塊。
- 在
DialView.kt的FanSpeed列舉中,新增擴充功能函式next(),將目前的風扇轉速變更為清單中的下一個轉速 (從OFF變更為LOW、MEDIUM和HIGH,然後返回OFF)。完整的列舉現在看起來像這樣:
private enum class FanSpeed(val label: Int) {
OFF(R.string.fan_off),
LOW(R.string.fan_low),
MEDIUM(R.string.fan_medium),
HIGH(R.string.fan_high);
fun next() = when (this) {
OFF -> LOW
LOW -> MEDIUM
MEDIUM -> HIGH
HIGH -> OFF
}
}- 在
DialView類別中,於onSizeChanged()方法之前新增init()區塊。將檢視區塊的isClickable屬性設為 true,即可讓該檢視區塊接受使用者輸入內容。
init {
isClickable = true
}- 在
init(),下方,使用下列程式碼覆寫performClick()方法。
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
contentDescription = resources.getString(fanSpeed.label)
invalidate()
return true
}撥打給「super」的電話。您必須先執行 performClick(),才能啟用無障礙事件和呼叫 onClickListener()。
接下來的兩行會使用 next() 方法調高風扇速度,並將檢視區塊的內容說明設為代表目前速度 (關閉、1、2 或 3) 的字串資源。
最後,invalidate() 方法會使整個檢視區塊失效,強制呼叫 onDraw() 重新繪製檢視區塊。如果自訂檢視區塊中的任何項目因任何原因 (包括使用者互動) 而變更,且需要顯示變更,請呼叫 invalidate().
- 執行應用程式。輕觸
DialView元素,將指標從關閉移至 1。撥號鍵應會變成綠色。每次輕觸時,指標應移至下一個位置。指標返回關閉狀態時,錶盤應會再次變成灰色。


這個範例說明如何使用自訂檢視區塊的自訂屬性。您為 DialView 類別定義的自訂屬性,會為每個風扇轉盤位置提供不同顏色。
- 建立並開啟
res/values/attrs.xml。 - 在
<resources>中,新增<declare-styleable>資源元素。 - 在
<declare-styleable>資源元素中,為每個屬性新增一個attr元素,並加入name和format。format類似於型別,在本例中為color。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DialView">
<attr name="fanColor1" format="color" />
<attr name="fanColor2" format="color" />
<attr name="fanColor3" format="color" />
</declare-styleable>
</resources>- 開啟
activity_main.xml版面配置檔案。 - 在
DialView中,為fanColor1、fanColor2和fanColor3新增屬性,並將其值設為下方顯示的顏色。請使用app:做為自訂屬性的序言 (如app:fanColor1),而非android:,因為自訂屬性屬於schemas.android.com/apk/res/your_app_package_name命名空間,而非android命名空間。
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"如要在 DialView 類別中使用屬性,必須先擷取這些屬性。如果存在,系統會將這些檔案儲存在 AttributeSet 中,並在建立課程時交給課程。您可以在 init 中擷取屬性,並將屬性值指派給本機變數以進行快取。
- 開啟
DialView.kt類別檔案。 - 在
DialView中,宣告變數來快取屬性值。
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0- 在
init區塊中,使用withStyledAttributes擴充功能函式新增下列程式碼。您提供屬性和檢視畫面,並設定本機變數。匯入withStyledAttributes時,系統也會匯入右側的getColor()函式。
context.withStyledAttributes(attrs, R.styleable.DialView) {
fanSpeedLowColor = getColor(R.styleable.DialView_fanColor1, 0)
fanSpeedMediumColor = getColor(R.styleable.DialView_fanColor2, 0)
fanSeedMaxColor = getColor(R.styleable.DialView_fanColor3, 0)
}- 在
onDraw()中使用本機變數,根據目前的風扇轉速設定撥號盤顏色。將設定顏料顏色的行 (paint.color=if(fanSpeed== FanSpeed.OFF) Color.GRAYelseColor.GREEN) 替換為下列程式碼。
paint.color = when (fanSpeed) {
FanSpeed.OFF -> Color.GRAY
FanSpeed.LOW -> fanSpeedLowColor
FanSpeed.MEDIUM -> fanSpeedMediumColor
FanSpeed.HIGH -> fanSeedMaxColor
} as Int- 執行應用程式,然後按一下撥號盤,每個位置的顏色設定應會不同,如下所示。
|
|
|
|
如要進一步瞭解自訂檢視區塊屬性,請參閱「建立檢視區塊類別」。
無障礙功能是一組設計、實作和測試技術,可讓所有人 (包括身心障礙者) 都能使用您的應用程式。
失能情況會影響使用 Android 裝置,而常見的失能情況包括失明、視障、色盲、失聰或聽障及動作技巧受限。開發應用程式時將無障礙設計納入考量,不僅能為上述身心障礙使用者帶來更優質的使用體驗,也能造福所有其他使用者。
Android 會在標準 UI 檢視畫面 (例如 TextView 和 Button) 中預設提供多項無障礙功能。不過,建立自訂檢視區塊時,您需要考慮該檢視區塊如何提供無障礙功能,例如朗讀螢幕內容的說明。
在這項工作中,您將瞭解 Android 螢幕閱讀器 TalkBack,並修改應用程式,為 DialView 自訂檢視區塊加入可朗讀的提示和說明。
步驟 1:探索 TalkBack
TalkBack 是 Android 的內建螢幕閱讀器。啟用 TalkBack 後,Android 會朗讀螢幕上的元素,因此使用者不必看螢幕就能與 Android 裝置互動。視障使用者可能需要透過 TalkBack 才能使用您的應用程式。
在這項工作中,您將啟用 TalkBack,瞭解螢幕閱讀器的運作方式,以及如何瀏覽應用程式。
- 在 Android 裝置或模擬器上,依序前往「設定」>「無障礙設定」>「TalkBack」。
- 輕觸「開啟/關閉」切換按鈕,開啟 TalkBack。
- 輕觸「確定」確認權限。
- 如果系統要求,請確認裝置密碼。如果這是你第一次執行 TalkBack,系統會啟動教學課程。(舊款裝置可能無法使用教學課程)。
- 建議您閉上眼睛瀏覽教學課程。日後如要再次開啟教學課程,請依序前往「設定」>「無障礙設定」>「TalkBack」>「設定」>「啟動 TalkBack 教學課程」。
- 編譯並執行
CustomFanController應用程式,或使用裝置上的「總覽」或「最近」按鈕開啟應用程式。開啟 TalkBack 後,你會發現系統會朗讀應用程式名稱和標籤文字TextView(「風扇控制」)。不過,如果輕觸DialView檢視畫面本身,系統不會朗讀檢視畫面的狀態 (撥號的目前設定),也不會朗讀輕觸檢視畫面以啟動時會執行的動作。
步驟 2:為錶面標籤新增內容說明
內容說明會說明應用程式中檢視區塊的意義和用途。有了這些標籤,Android 的 TalkBack 功能等螢幕閱讀器就能準確說明每個元素的功能。如果是 ImageView 等靜態檢視區塊,您可以在版面配置檔案中,使用 contentDescription 屬性將內容說明新增至檢視區塊。文字檢視區塊 (TextView 和 EditText) 會自動使用檢視區塊中的文字做為內容說明。
如果是自訂風扇控制檢視畫面,每次點選檢視畫面時,您都需要動態更新內容說明,指出目前的風扇設定。
- 在
DialView類別底部,宣告不含引數或傳回類型的updateContentDescription()函式。
fun updateContentDescription() {
}- 在
updateContentDescription()內,將自訂檢視區塊的contentDescription屬性變更為與目前風扇速度 (關閉、1、2 或 3) 相關聯的字串資源。這些標籤與在畫面上繪製錶盤時使用的標籤相同。onDraw()
fun updateContentDescription() {
contentDescription = resources.getString(fanSpeed.label)
}- 向上捲動至
init()區塊,並在該區塊的結尾新增updateContentDescription()的呼叫。這會在檢視區塊初始化時,初始化內容說明。
init {
isClickable = true
// ...
updateContentDescription()
}- 在
performClick()方法中,於invalidate()之前新增對updateContentDescription()的其他呼叫。
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
updateContentDescription()
invalidate()
return true
}- 編譯並執行應用程式,並確認 TalkBack 已開啟。輕觸即可變更撥號盤檢視畫面的設定,並注意 TalkBack 現在會朗讀目前的標籤 (關閉、1、2、3) 和「輕觸兩下即可啟用」這句話。
步驟 3:為點擊動作新增更多資訊
您可以停在這裡,這樣 TalkBack 就能使用您的檢視區塊。但如果檢視畫面不僅能指出「可」啟動 (「輕觸兩下即可啟動」),還能說明啟動檢視畫面「會」發生什麼事 (「輕觸兩下即可變更」或「輕觸兩下即可重設」),會更有幫助。
如要這麼做,請透過無障礙委派項目,將檢視區塊動作 (這裡是指點按或輕觸動作) 的相關資訊新增至無障礙節點資訊物件。無障礙委派可讓您透過組合 (而非繼承) 自訂應用程式的無障礙相關功能。
在這項工作中,您會使用 Android Jetpack 程式庫 (androidx.*) 中的無障礙功能類別,確保回溯相容性。
- 在
DialView.kt的init區塊中,將檢視區塊的無障礙委派設為新的AccessibilityDelegateCompat物件。依要求匯入androidx.core.view.ViewCompat和androidx.core.view.AccessibilityDelegateCompat。這項策略可讓應用程式達到最大程度的回溯相容性。
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
})- 在
AccessibilityDelegateCompat物件中,使用AccessibilityNodeInfoCompat物件覆寫onInitializeAccessibilityNodeInfo()函式,並呼叫 super 的方法。在系統提示時匯入androidx.core.view.accessibility.AccessibilityNodeInfoCompat。
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
}
})每個檢視區塊都有無障礙節點樹狀結構,可能對應或不對應檢視區塊的實際版面配置元件。Android 無障礙服務會瀏覽這些節點,找出檢視區塊的相關資訊 (例如可朗讀的內容說明,或可對該檢視區塊執行的動作)。建立自訂檢視區塊時,您可能也需要覆寫節點資訊,以便提供無障礙自訂資訊。在這種情況下,您將覆寫節點資訊,指出檢視區塊動作有自訂資訊。
- 在
onInitializeAccessibilityNodeInfo()中,建立新的AccessibilityNodeInfoCompat.AccessibilityActionCompat物件,並指派給customClick變數。將AccessibilityNodeInfo.ACTION_CLICK常數和預留位置字串傳遞至建構函式。視需要匯入AccessibilityNodeInfo。
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
"placeholder"
)
}
})AccessibilityActionCompat 類別代表檢視區塊的動作,適用於無障礙功能。一般動作是點選或輕觸 (如您在此處使用的動作),但其他動作包括取得或失去焦點、剪貼簿作業 (剪下/複製/貼上) 或在檢視區塊中捲動。這個類別的建構函式需要動作常數 (在此為 AccessibilityNodeInfo.ACTION_CLICK),以及 TalkBack 用來指出動作的字串。
- 將
"placeholder"字串替換為對context.getString()的呼叫,以擷取字串資源。針對特定資源,測試目前的風扇轉速。如果目前的速度為FanSpeed.HIGH,則字串為"Reset"。如果風扇速度為其他值,字串為"Change."。您會在後續步驟中建立這些字串資源。
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
context.getString(if (fanSpeed != FanSpeed.HIGH) R.string.change else R.string.reset)
)
}
})- 在
customClick定義的右括號後,使用addAction()方法將新的無障礙動作新增至節點資訊物件。
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
context.getString(if (fanSpeed != FanSpeed.HIGH)
R.string.change else R.string.reset)
)
info.addAction(customClick)
}
})- 在
res/values/strings.xml中,新增「變更」和「重設」的字串資源。
<string name="change">Change</string>
<string name="reset">Reset</string>- 編譯並執行應用程式,然後確認 TalkBack 已開啟。請注意,現在「輕觸兩下即可啟動」這個片語會變成「輕觸兩下即可變更」(如果風扇速度低於高或 3) 或「輕觸兩下即可重設」(如果風扇速度已設為高或 3)。請注意,「輕觸兩下即可...」提示是由 TalkBack 服務本身提供。
下載完成的程式碼研究室程式碼。
$ git clone https://github.com/googlecodelabs/android-kotlin-drawing-custom-views
您也可以將存放區下載為 ZIP 檔案、將其解壓縮,並在 Android Studio 中開啟。
- 如要建立繼承
View子類別 (例如EditText) 外觀和行為的自訂檢視區塊,請新增擴充該子類別的類別,並覆寫部分子類別方法來進行調整。 - 如要建立任何大小和形狀的自訂檢視區塊,請新增擴充
View的類別。 - 覆寫
View方法 (例如onDraw()),定義檢視區塊的形狀和基本外觀。 - 使用
invalidate()強制繪製或重新繪製檢視區塊。 - 為提升效能,請在
onDraw()中使用變數前,先指派變數並為繪圖和繪製作業指派任何必要值,例如在成員變數的初始化作業中。 - 覆寫
performClick(),而非OnClickListener(),為自訂檢視區塊提供檢視區塊的互動行為。這樣一來,您或其他可能使用自訂檢視區塊類別的 Android 開發人員,就能使用onClickListener()提供進一步的行為。 - 將自訂檢視區塊新增至 XML 版面配置檔案,並使用屬性定義其外觀,就像處理其他 UI 元素一樣。
- 在
values資料夾中建立attrs.xml檔案,定義自訂屬性。接著,您可以在 XML 版面配置檔案中使用自訂檢視區塊的自訂屬性。
Udacity 課程:
Android 開發人員說明文件:
- 建立自訂檢視畫面
@JvmOverloads- 自訂元件
- Android 如何繪製檢視區塊
onMeasure()onSizeChanged()onDraw()CanvasPaintdrawText()setTypeface()setColor()drawRect()drawOval()drawArc()drawBitmap()setStyle()invalidate()- 查看
- 輸入事件
- Paint
- Kotlin 擴充功能程式庫 android-ktx
withStyledAttributes- Android KTX 說明文件
- Android KTX 原始公告網誌
- 提高自訂檢視區塊的無障礙程度
AccessibilityDelegateCompatAccessibilityNodeInfoCompatAccessibilityNodeInfoCompat.AccessibilityActionCompat
影片:
本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:
- 視需要指派作業。
- 告知學員如何繳交作業。
- 為作業評分。
講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。
如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。
第 1 題
自訂檢視區塊首次指派大小時,如要計算位置、維度和任何其他值,您會覆寫哪個方法?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ onDraw()
第 2 題
如要指出您希望使用 onDraw() 重新繪製檢視區塊,在屬性值變更後,您會從 UI 執行緒呼叫哪個方法?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ getVisibility()
第 3 題
您應覆寫哪個 View 方法,才能在自訂檢視區塊中加入互動功能?
▢ setOnClickListener()
▢ onSizeChanged()
▢ isClickable()
▢ performClick()
如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 進階功能程式碼研究室登陸頁面。



