本程式碼研究室是 Kotlin 進階課程的一部分。只要您按部就班完成程式碼研究室,就能發揮本課程的最大效益。不過,您不一定要這麼做。所有課程程式碼研究室清單均列於進階 Android 版的 Kotlin 程式碼研究室到達網頁中。
引言
Android 提供大量的 View
子類別,例如 Button
、TextView
、EditText
、ImageView
、CheckBox
或 RadioButton
。您可以使用這些子類別來建立使用者介面,讓使用者互動並在應用程式中顯示資訊。如果沒有任何 View
子類別均符合您的需求,您可以建立 View
子類別,也就是所謂的自訂視圖。
如要建立自訂檢視模式,您可以擴充現有的 View
子類別 (例如 Button
或 EditText
),也可以自行建立 View
的子類別。直接擴充 View
可讓您建立 View
的互動式 UI 元素,只要覆寫 onDraw()
方法即可讓 View
繪圖。
建立自訂檢視模式後,您可以將其新增到活動版面配置中,方法與新增 TextView
或 Button
時相同。
本課程說明如何藉由延長 View
的方式從頭開始建立自訂檢視畫面。
須知事項
- 如何使用「活動」建立應用程式,並使用 Android Studio 執行應用程式。
課程內容
- 如何擴充
View
以建立自訂檢視畫面。 - 如何繪製圓形的自訂檢視。
- 如何使用事件監聽器來處理自訂檢視的使用者互動。
- 如何在版面配置中使用自訂檢視。
執行步驟
- 展開「
View
」即可建立自訂檢視畫面。 - 使用繪圖和繪製值來初始化自訂檢視。
- 如要覆寫檢視畫面,請覆寫
onDraw()
。 - 使用事件監聽器提供自訂檢視行為。
- 將自訂檢視加入版面配置。
CustomFanController 應用程式示範如何透過擴充 View
類別來建立自訂檢視子類別。新的子類別稱為 DialView
。
App 顯示了一個形似物理機扇控制的圓形 UI 元素,具有設置為 (0)、低 (1)、中 (2) 和高 (3)。對於用戶觸摸視圖,選擇指示器移動到下一個位置:0-1-2-3,然後返回到 0。此外,如果選擇為 1 以上,則視圖中圓形部分的背景顏色會從灰色變成綠色 (表示風扇電源已開啟)。
資料檢視是應用程式 UI 的基本構成要素。View
類別提供許多子類別,也稱為 UI 小工具,包含一般 Android 應用程式使用者介面的許多需求。
UI 建構區塊 (例如 Button
和 TextView
) 是擴充 View
類別的子類別。為了節省時間和開發作業,您可以延長其中一種 View
子類別。自訂檢視會繼承上層機構單位的外觀和行為,而您可以覆寫特定外觀的行為或畫面。舉例來說,如果您擴展了 EditText
來建立自訂檢視,該視圖的作用就像是 EditText
檢視一樣,但是也可以自訂用來顯示 X 按鈕,這些文字會清除文字輸入欄位中的文字。
您可以擴充任何 View
子類別 (例如 EditText
),以便取得自訂檢視,請從中挑選出您最想達成的子類別。接著,您可以在一個以上的版面配置中,使用自訂檢視畫面和其他任何 View
子類別,當做含有屬性的 XML 元素。
如果要從頭開始建立自訂檢視模式,請擴充 View
類別本身。您的程式碼會覆寫 View
方法來定義檢視的外觀和功能。建立自訂畫面的關鍵在於,您有責任將各種大小和形狀的 UI 元素繪製到畫面上。如果您將現有資料檢視 (例如 Button
) 設為子類別,該類別會為您處理繪圖。(您稍後可以在這個程式碼研究室中進一步瞭解繪圖功能)。
如要建立自訂檢視,請按照下列一般步驟操作:
- 建立延伸
View
的自訂檢視類別,或擴充View
子類別 (例如Button
或EditText
)。 - 如果您延長現有
View
子類別,即可只覆寫您要變更的外觀行為或面向。 - 如果您延長
View
類別的形狀,就會繪製自訂視圖的形狀,並藉由覆寫新類別中的View
方法 (例如onDraw()
和onMeasure()
) 來控制其外觀。 - 加入程式碼以回應使用者互動,並視需要重新繪製自訂檢視。
- 使用自訂檢視類別做為活動 XML 版面配置的 UI 小工具。您也可以為視圖定義自訂屬性,為不同的版面配置提供自訂檢視。
在這項工作中,您會執行下列作業:
步驟 1:使用 ImageView 預留位置建立應用程式
- 使用空白活動範本建立標題為
CustomFanController
的 Kotlin 應用程式。確認套件名稱為com.example.android.customfancontroller
。 - 開啟「文字」分頁中的
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 Builders using '@JvmOverloads']。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()
方法。 - 使用
Paint
物件的樣式設定Canvas
物件,覆寫onDraw()
方法來繪製自訂檢視。 - 回應使用者點選的
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
類別上的這項擴充功能會計算螢幕標籤和目前指標 (0、1、2 或 3) 的 X、Y 座標 (假設該表目前的FanSpeed
位置和半徑)。您將在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
}
- 使用
Canvas
和Paint
類別覆寫onDraw()
方法來在螢幕上顯示視圖。在要求時匯入android.graphics.Canvas
。這是骨骼覆寫:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}
- 根據
onDraw()
的風扇速度 (OFF
) 或其他值,將這一行加到onDraw()
中,將繪製顏色設為灰色 (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" />
- 執行應用程式。活動控制畫面會顯示在活動中。
最終工作就是啟用自訂檢視,以便在使用者輕觸檢視時執行動作。每次觸摸都必須將選擇指示器移動到下一個位置:off-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 移至 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
的屬性,並將值設為下方顯示的顏色。使用自訂屬性屬於schemas.android.com/apk/res/
your_app_package_name
命名空間而非android
命名空間時,請使用app:
做為自訂屬性的介面 (位於app:fanColor1
),而不是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.
GRAY
else
Color.
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
。但是在建立自訂檢視時,您需要考慮自訂檢視如何提供其他功能,例如螢幕上內容的語音說明。
在這項工作中,您將瞭解 TalkBack 和 Android 螢幕閱讀器,並修改應用程式,加入適用於 DialView
自訂檢視畫面的語音提示和說明。
步驟 1. 探索 TalkBack
TalkBack 是 Android 的內建螢幕閱讀器。由於 Android 會朗讀螢幕元素,因此啟用 TalkBack 後,使用者不需看到螢幕就能與 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()
}
- 請在
invalidate()
之前透過performClick()
方法,再次加入updateContentDescription()
的呼叫。
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
updateContentDescription()
invalidate()
return true
}
- 編譯並執行應用程式,並確認 TalkBack 已開啟。輕按以變更撥號畫面的設定
步驟 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's 方法。當系統提示時匯入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."
You' 您會在後續步驟中建立這些字串資源。
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」)請注意,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()
Canvas
Paint
drawText()
setTypeface()
setColor()
drawRect()
drawOval()
drawArc()
drawBitmap()
setStyle()
invalidate()
- 查看
- 輸入事件
- 疼痛
- Kotlin 擴充功能程式庫 android-ktx
withStyledAttributes
- Android KTX 說明文件
- Android KTX 原始公告網誌
- 讓使用者更容易存取自訂檢視畫面
AccessibilityDelegateCompat
AccessibilityNodeInfoCompat
AccessibilityNodeInfoCompat.AccessibilityActionCompat
影片:
這個部分會列出在代碼研究室中,受老師主導的課程作業的可能學生作業。由老師自行決定要執行下列動作:
- 視需要指派家庭作業。
- 告知學生如何提交家庭作業。
- 批改家庭作業。
老師可視需要使用這些建議,並視情況指派其他合適的家庭作業。
如果您是自行操作本程式碼研究室,歡迎透過這些家庭作業來測試自己的知識。
第 1 題
若要計算自訂檢視在首次指派大小時的位置、維度以及任何其他值,您覆寫哪個方法?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ onDraw()
第 2 題
為表示您是否想利用 onDraw()
來重新檢視視圖?在屬性值變更後,您透過 UI 執行緒呼叫哪種方法?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ getVisibility()
第 3 題
如要針對互動功能添加互動功能,你應該覆寫哪一種 View
方法?
▢ setOnClickListener()
▢ onSizeChanged()
▢ isClickable()
▢ performClick()
如要瞭解本課程中其他程式碼研究室的連結,請參閱 Kotlin 的進階 Android 程式碼研究室到達網頁。