การสร้างมุมมองที่กำหนดเอง

Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Android ขั้นสูงใน Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ แต่ไม่จำเป็นต้องทำ Codelab ของหลักสูตรทั้งหมดแสดงอยู่ในหน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin

บทนำ

Android มีคลาสย่อยของ View มากมาย เช่น Button, TextView, EditText, ImageView, CheckBox หรือ RadioButton คุณสามารถใช้คลาสย่อยเหล่านี้เพื่อสร้าง UI ที่ช่วยให้ผู้ใช้โต้ตอบและแสดงข้อมูลในแอปได้ หากไม่มีViewคลาสย่อยใดที่ตรงกับความต้องการ คุณสามารถสร้างคลาสย่อยViewที่เรียกว่ามุมมอง ที่กำหนดเองได้

หากต้องการสร้างมุมมองที่กำหนดเอง คุณจะขยายViewคลาสย่อยที่มีอยู่ (เช่น Button หรือ EditText) หรือสร้างคลาสย่อยของ View เองก็ได้ การขยาย View โดยตรงช่วยให้คุณสร้างองค์ประกอบ UI แบบอินเทอร์แอกทีฟที่มีขนาดและรูปร่างใดก็ได้โดยการลบล้างเมธอด onDraw() สำหรับ View เพื่อวาด

หลังจากสร้างมุมมองที่กำหนดเองแล้ว คุณจะเพิ่มมุมมองดังกล่าวลงในเลย์เอาต์กิจกรรมได้ในลักษณะเดียวกับการเพิ่ม TextView หรือ Button

บทเรียนนี้จะแสดงวิธีสร้างมุมมองที่กำหนดเองตั้งแต่ต้นโดยการขยาย View

สิ่งที่คุณควรทราบอยู่แล้ว

  • วิธีสร้างแอปที่มีกิจกรรมและเรียกใช้โดยใช้ Android Studio

สิ่งที่คุณจะได้เรียนรู้

  • วิธีขยาย View เพื่อสร้างมุมมองที่กำหนดเอง
  • วิธีการวาดมุมมองที่กำหนดเองซึ่งมีรูปร่างเป็นวงกลม
  • วิธีใช้ Listener เพื่อจัดการการโต้ตอบของผู้ใช้กับมุมมองที่กำหนดเอง
  • วิธีใช้มุมมองที่กำหนดเองในเลย์เอาต์

สิ่งที่คุณต้องดำเนินการ

  • ขยาย View เพื่อสร้างมุมมองที่กำหนดเอง
  • เริ่มต้นมุมมองที่กำหนดเองด้วยค่าการวาดและการระบายสี
  • แทนที่ onDraw() เพื่อวาดมุมมอง
  • ใช้ Listener เพื่อกำหนดลักษณะการทำงานของมุมมองที่กำหนดเอง
  • เพิ่มมุมมองที่กำหนดเองลงในเลย์เอาต์

แอป CustomFanController แสดงวิธีสร้างคลาสย่อยของมุมมองที่กำหนดเองโดยการขยายคลาส View โดยคลาสย่อยใหม่นี้เรียกว่า DialView

แอปจะแสดงองค์ประกอบ UI แบบวงกลมที่คล้ายกับการควบคุมพัดลมจริง โดยมีการตั้งค่าสำหรับปิด (0), ต่ำ (1), ปานกลาง (2) และสูง (3) เมื่อผู้ใช้แตะมุมมอง ตัวบ่งชี้การเลือกจะย้ายไปยังตำแหน่งถัดไป: 0-1-2-3 และกลับไปที่ 0 นอกจากนี้ หากเลือก 1 ขึ้นไป สีพื้นหลังของส่วนวงกลมของมุมมองจะเปลี่ยนจากสีเทาเป็นสีเขียว (แสดงว่าพัดลมเปิดอยู่)

View คือองค์ประกอบพื้นฐานของ UI ของแอป คลาส View มีคลาสย่อยจำนวนมากที่เรียกว่า UI Widget ซึ่งครอบคลุมความต้องการส่วนใหญ่ของอินเทอร์เฟซผู้ใช้ของแอป Android ทั่วไป

องค์ประกอบ UI เช่น Button และ TextView เป็นคลาสย่อยที่ขยายคลาส View คุณสามารถขยายคลาสย่อยViewใดคลาสย่อยหนึ่งต่อไปนี้เพื่อประหยัดเวลาและลดความยุ่งยากในการพัฒนาได้ มุมมองที่กำหนดเองจะรับลักษณะและลักษณะการทำงานของมุมมองหลัก และคุณสามารถลบล้างลักษณะการทำงานหรือลักษณะของลักษณะที่ต้องการเปลี่ยนได้ ตัวอย่างเช่น หากคุณขยาย EditText เพื่อสร้างมุมมองที่กำหนดเอง มุมมองจะทํางานเหมือนกับมุมมอง EditText แต่ก็ปรับแต่งให้แสดงปุ่ม X ที่ล้างข้อความจากช่องป้อนข้อความได้ด้วย

คุณสามารถขยายViewคลาสย่อยใดก็ได้ เช่น EditText เพื่อรับมุมมองที่กำหนดเอง เพียงเลือกมุมมองที่ใกล้เคียงกับสิ่งที่คุณต้องการทำให้มากที่สุด จากนั้นคุณจะใช้มุมมองที่กำหนดเองได้เหมือนกับViewคลาสย่อยอื่นๆ ในเลย์เอาต์อย่างน้อย 1 รายการเป็นองค์ประกอบ XML ที่มีแอตทริบิวต์

หากต้องการสร้างมุมมองที่กำหนดเองใหม่ตั้งแต่ต้น ให้ขยายคลาส View เอง โค้ดของคุณจะลบล้างเมธอด View เพื่อกำหนดลักษณะที่ปรากฏและฟังก์ชันการทำงานของมุมมอง สิ่งสำคัญในการสร้างมุมมองที่กำหนดเองคือคุณมีหน้าที่รับผิดชอบในการวาดองค์ประกอบ UI ทั้งหมดที่มีขนาดและรูปร่างใดก็ได้ลงบนหน้าจอ หากคุณสร้างคลาสย่อยของ View ที่มีอยู่ เช่น Button คลาสนั้นจะจัดการการวาดให้คุณ (คุณจะได้ดูข้อมูลเพิ่มเติมเกี่ยวกับการวาดใน Codelab นี้ในภายหลัง)

หากต้องการสร้างมุมมองที่กําหนดเอง ให้ทําตามขั้นตอนทั่วไปต่อไปนี้

  • สร้างคลาสมุมมองที่กำหนดเองซึ่งขยาย View หรือขยายคลาสย่อย View (เช่น Button หรือ EditText)
  • หากขยายViewคลาสย่อยที่มีอยู่ ให้ลบล้างเฉพาะลักษณะการทำงานหรือลักษณะที่ปรากฏที่คุณต้องการเปลี่ยนแปลง
  • หากขยายView คลาส ให้วาดรูปร่างของมุมมองที่กำหนดเองและควบคุมลักษณะที่ปรากฏโดยการลบล้างเมธอด View เช่น onDraw() และ onMeasure() ในคลาสใหม่
  • เพิ่มโค้ดเพื่อตอบสนองต่อการโต้ตอบของผู้ใช้ และวาดมุมมองที่กำหนดเองใหม่หากจำเป็น
  • ใช้คลาสมุมมองที่กำหนดเองเป็นวิดเจ็ต UI ในเลย์เอาต์ XML ของกิจกรรม นอกจากนี้ คุณยังกำหนดแอตทริบิวต์ที่กำหนดเองสำหรับมุมมองได้ด้วย เพื่อให้ปรับแต่งมุมมองในเลย์เอาต์ต่างๆ ได้

ในงานนี้ คุณจะได้ทำสิ่งต่อไปนี้

  • สร้างแอปที่มี ImageView เป็นตัวยึดตำแหน่งชั่วคราวสำหรับมุมมองที่กำหนดเอง
  • ขยาย View เพื่อสร้างมุมมองที่กำหนดเอง
  • เริ่มต้นมุมมองที่กำหนดเองด้วยค่าการวาดและการระบายสี

ขั้นตอนที่ 1: สร้างแอปที่มีตัวยึดตำแหน่ง ImageView

  1. สร้างแอป Kotlin ที่มีชื่อ CustomFanController โดยใช้เทมเพลตกิจกรรมเปล่า ตรวจสอบว่าชื่อแพ็กเกจเป็น com.example.android.customfancontroller
  2. เปิด activity_main.xml ในแท็บข้อความเพื่อแก้ไขโค้ด XML
  3. แทนที่ 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"/>
  1. เพิ่มองค์ประกอบ 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"/>
  1. แยกทรัพยากรสตริงและมิติข้อมูลในองค์ประกอบ UI ทั้ง 2 รายการ
  2. คลิกแท็บออกแบบ เลย์เอาต์ควรมีลักษณะดังนี้

ขั้นตอนที่ 2 สร้างคลาสมุมมองที่กำหนดเอง

  1. สร้างคลาส Kotlin ใหม่ชื่อ DialView
  2. แก้ไขคำจำกัดความของคลาสเพื่อขยาย View นำเข้า android.view.View เมื่อได้รับข้อความแจ้ง
  3. คลิก View แล้วคลิกหลอดไฟสีแดง เลือกเพิ่มตัวสร้าง Android View โดยใช้ '@JvmOverloads' Android Studio จะเพิ่มตัวสร้างจากคลาส View คำอธิบายประกอบ @JvmOverloads จะสั่งให้คอมไพเลอร์ Kotlin สร้างการโอเวอร์โหลดสำหรับฟังก์ชันนี้ซึ่งจะแทนที่ค่าพารามิเตอร์เริ่มต้น
class DialView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
  1. เหนือ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);
}
  1. เพิ่มค่าคงที่ต่อไปนี้ใต้ enum คุณจะใช้ค่าเหล่านี้เป็นส่วนหนึ่งของการวาดตัวบ่งชี้และป้ายกำกับของมาตรวัด
private const val RADIUS_OFFSET_LABEL = 30      
private const val RADIUS_OFFSET_INDICATOR = -35
  1. ภายในคลาส 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 ที่จะใช้ในการวาดองค์ประกอบต่างๆ ของมุมมองบนหน้าจอ

ระบบจะสร้างและเริ่มต้นค่าเหล่านี้ที่นี่แทนที่จะเป็นตอนที่วาดวิวจริงๆ เพื่อให้มั่นใจว่าขั้นตอนการวาดจริงจะทำงานได้เร็วที่สุด

  1. นอกจากนี้ ภายในคำจำกัดความของคลาส DialView ให้เริ่มต้นออบเจ็กต์ Paint ด้วยสไตล์พื้นฐาน 2-3 แบบ นำเข้า 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)
}
  1. เปิด 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() เปลี่ยนว่าจะให้รูปทรงมีสี เค้าโครง หรือทั้ง 2 อย่างโดยเรียกใช้ setStyle()
  • วาดบิตแมปโดยใช้ drawBitmap()

คุณจะได้ดูข้อมูลเพิ่มเติมเกี่ยวกับ Canvas และ Paint ใน Codelab ในภายหลัง ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่ Android วาด View ได้ที่วิธีที่ Android วาด View

ในงานนี้ คุณจะวาดมุมมองที่กำหนดเองของตัวควบคุมพัดลมลงบนหน้าจอ ซึ่งก็คือหน้าปัด ตัวบ่งชี้ตำแหน่งปัจจุบัน และป้ายกำกับตัวบ่งชี้ โดยใช้เมธอด onSizeChanged() และ onDraw() นอกจากนี้ คุณยังจะได้สร้างเมธอดตัวช่วย computeXYForSpeed(), เพื่อคำนวณตำแหน่ง X,Y ปัจจุบันของป้ายกำกับตัวบ่งชี้บนหน้าปัดด้วย

ขั้นตอนที่ 1 คำนวณตำแหน่งและวาดมุมมอง

  1. ในคลาส DialView ให้ลบล้างเมธอด onSizeChanged() จากคลาส View เพื่อคำนวณขนาดของแป้นหมุนของมุมมองที่กำหนดเอง นำเข้า kotlinmath.min เมื่อมีการร้องขอ

    ระบบจะเรียกใช้เมธอด onSizeChanged() ทุกครั้งที่ขนาดของมุมมองเปลี่ยนแปลง รวมถึงครั้งแรกที่วาดเมื่อมีการขยายเลย์เอาต์ แทนที่ onSizeChanged() เพื่อคำนวณตำแหน่ง ขนาด และค่าอื่นๆ ที่เกี่ยวข้องกับขนาดของมุมมองที่กำหนดเอง แทนที่จะคำนวณใหม่ทุกครั้งที่วาด ในกรณีนี้ คุณใช้ onSizeChanged() เพื่อคำนวณรัศมีปัจจุบันขององค์ประกอบวงกลมของหน้าปัด
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
   radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
  1. เพิ่มโค้ดนี้ใต้ onSizeChanged() เพื่อกำหนดฟังก์ชันส่วนขยาย computeXYForSpeed() สำหรับคลาส PointF นำเข้า kotlin.math.cos และ kotlin.math.sin เมื่อมีการร้องขอ ฟังก์ชันส่วนขยายนี้ในคลาส PointF จะคำนวณพิกัด X, Y บนหน้าจอสำหรับป้ายกำกับข้อความและตัวบ่งชี้ปัจจุบัน (0, 1, 2 หรือ 3) โดยอิงตามตำแหน่ง 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
}
  1. แทนที่เมธอด onDraw() เพื่อแสดงผลมุมมองบนหน้าจอด้วยคลาส Canvas และ Paint นำเข้า android.graphics.Canvas เมื่อได้รับคำขอ นี่คือการลบล้างโครงกระดูก
override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   
}
  1. ภายใน onDraw() ให้เพิ่มบรรทัดนี้เพื่อตั้งค่าสีการวาดเป็นสีเทา (Color.GRAY) หรือสีเขียว (Color.GREEN) ขึ้นอยู่กับว่าความเร็วพัดลมเป็น OFF หรือค่าอื่นๆ นำเข้า android.graphics.Color เมื่อได้รับคำขอ
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
  1. เพิ่มโค้ดนี้เพื่อวาดวงกลมสำหรับหน้าปัดด้วยเมธอด drawCircle() เมธอดนี้ใช้ความกว้างและความสูงของมุมมองปัจจุบันเพื่อหารัศมีของวงกลม จุดศูนย์กลางของวงกลม และสีที่ใช้ระบายสีในปัจจุบัน พร็อพเพอร์ตี้ width และ height เป็นสมาชิกของคลาสซูเปอร์ View และระบุขนาดปัจจุบันของมุมมอง
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
  1. เพิ่มโค้ดต่อไปนี้เพื่อวาดวงกลมขนาดเล็กสำหรับเครื่องหมายแสดงความเร็วพัดลมด้วยdrawCircle()วิธีนี้ ส่วนนี้ใช้PointFcomputeXYforSpeed() วิธีการขยายเพื่อคำนวณพิกัด 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)
  1. สุดท้าย ให้วาดป้ายกำกับความเร็วพัดลม (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 ของกิจกรรม ควบคุมลักษณะที่ปรากฏและลักษณะการทำงานของวิดเจ็ตด้วยแอตทริบิวต์ขององค์ประกอบ XML เช่นเดียวกับองค์ประกอบ UI อื่นๆ

  1. ใน activity_main.xml ให้เปลี่ยนแท็ก ImageView สำหรับ dialView เป็น 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. เรียกใช้แอป มุมมองการควบคุมพัดลมจะปรากฏในกิจกรรม

งานสุดท้ายคือการเปิดใช้มุมมองที่กำหนดเองเพื่อดำเนินการเมื่อผู้ใช้แตะมุมมอง การแตะแต่ละครั้งควรย้ายตัวบ่งชี้การเลือกไปยังตำแหน่งถัดไป ได้แก่ ปิด-1-2-3 และกลับไปที่ปิด นอกจากนี้ หากการเลือกเป็น 1 ขึ้นไป ให้เปลี่ยนพื้นหลังจากสีเทาเป็นสีเขียวเพื่อระบุว่าพัดลมเปิดอยู่

หากต้องการเปิดใช้มุมมองที่กําหนดเองเพื่อให้คลิกได้ คุณต้องทําดังนี้

  • ตั้งค่าพร็อพเพอร์ตี้ isClickable ของข้อมูลพร็อพเพอร์ตี้เป็น true ซึ่งจะช่วยให้มุมมองที่กำหนดเองตอบสนองต่อการคลิกได้
  • ใช้ performClick() ของคลาส View เพื่อดำเนินการเมื่อมีการคลิกมุมมอง
  • เรียกใช้เมธอด invalidate() ซึ่งจะบอกให้ระบบ Android เรียกใช้เมธอด onDraw() เพื่อวาดมุมมองใหม่

โดยปกติแล้ว เมื่อใช้มุมมอง Android มาตรฐาน คุณจะใช้ OnClickListener() เพื่อดำเนินการเมื่อผู้ใช้คลิกมุมมองนั้น สําหรับมุมมองที่กําหนดเอง ให้ใช้เมธอด performClick() ของคลาส View แทน แล้วเรียกใช้ superperformClick(). เมธอด performClick() เริ่มต้นจะเรียกใช้ onClickListener() ด้วย คุณจึงเพิ่มการดำเนินการลงใน performClick() และปล่อยให้ onClickListener() พร้อมใช้งานสำหรับการปรับแต่งเพิ่มเติมโดยคุณหรือนักพัฒนาแอปคนอื่นๆ ที่อาจใช้มุมมองที่กำหนดเองของคุณ

  1. ใน 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
   }
}
  1. ในDialView คลาส ให้เพิ่มบล็อก init() ก่อนเมธอด onSizeChanged() การตั้งค่าพร็อพเพอร์ตี้ isClickable ของมุมมองเป็น "จริง" จะทําให้มุมมองนั้นยอมรับอินพุตของผู้ใช้ได้
init {
   isClickable = true
}
  1. ด้านล่าง init(), ให้ลบล้างเมธอด performClick() ด้วยโค้ดด้านล่าง
override fun performClick(): Boolean {
   if (super.performClick()) return true

   fanSpeed = fanSpeed.next()
   contentDescription = resources.getString(fanSpeed.label)
  
   invalidate()
   return true
}

โทรหา superperformClick() ต้องเกิดขึ้นก่อน ซึ่งจะเปิดใช้กิจกรรมการช่วยเหลือพิเศษรวมถึงการโทร onClickListener()

2 บรรทัดถัดไปจะเพิ่มความเร็วของพัดลมด้วยเมธอด next() และตั้งค่าคำอธิบายเนื้อหาของมุมมองเป็นทรัพยากรสตริงที่แสดงความเร็วปัจจุบัน (ปิด 1, 2 หรือ 3)

สุดท้ายนี้ วิธี invalidate() จะทำให้มุมมองทั้งหมดไม่ถูกต้อง และบังคับให้เรียกใช้ onDraw() เพื่อวาดมุมมองใหม่ หากมีการเปลี่ยนแปลงในมุมมองที่กำหนดเองไม่ว่าด้วยเหตุผลใดก็ตาม รวมถึงการโต้ตอบของผู้ใช้ และจำเป็นต้องแสดงการเปลี่ยนแปลง ให้เรียกใช้ invalidate().

  1. เรียกใช้แอป แตะองค์ประกอบ DialView เพื่อย้ายตัวบ่งชี้จากปิดเป็น 1 แป้นหมุนควรเปลี่ยนเป็นสีเขียว เมื่อแตะแต่ละครั้ง ตัวบ่งชี้ควรย้ายไปยังตำแหน่งถัดไป เมื่อสัญญาณกลับมาเป็นปิดอีกครั้ง แป้นหมุนควรเปลี่ยนเป็นสีเทาอีกครั้ง

ตัวอย่างนี้แสดงกลไกพื้นฐานของการใช้แอตทริบิวต์ที่กำหนดเองกับมุมมองที่กำหนดเอง คุณกำหนดแอตทริบิวต์ที่กำหนดเองสำหรับคลาส DialView โดยใช้สีที่แตกต่างกันสำหรับตำแหน่งการหมุนพัดลมแต่ละตำแหน่ง

  1. สร้างและเปิด res/values/attrs.xml
  2. เพิ่มองค์ประกอบทรัพยากร <declare-styleable> ภายใน <resources>
  3. ภายในองค์ประกอบทรัพยากร <declare-styleable> ให้เพิ่มองค์ประกอบ attr 3 รายการ โดยแต่ละรายการจะมี 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>
  1. เปิดไฟล์เลย์เอาต์ activity_main.xml
  2. ใน 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 และกำหนดค่าแอตทริบิวต์ให้กับตัวแปรภายในสำหรับการแคช

  1. เปิดDialView.ktไฟล์ชั้นเรียน
  2. ภายใน DialView ให้ประกาศตัวแปรเพื่อแคชค่าแอตทริบิวต์
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0
  1. ในบล็อก 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)
}
  1. ใช้ตัวแปรภายในใน 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
  1. เรียกใช้แอป คลิกที่แป้นหมุน และการตั้งค่าสีควรแตกต่างกันสำหรับแต่ละตำแหน่ง ดังที่แสดงด้านล่าง

ดูข้อมูลเพิ่มเติมเกี่ยวกับแอตทริบิวต์มุมมองที่กำหนดเองได้ที่การสร้างคลาสมุมมอง

การช่วยเหลือพิเศษคือชุดเทคนิคการออกแบบ การติดตั้งใช้งาน และการทดสอบที่ช่วยให้ทุกคน รวมถึงผู้พิการสามารถใช้แอปของคุณได้

ความพิการที่พบบ่อยซึ่งอาจส่งผลต่อการใช้อุปกรณ์ Android ของบุคคล ได้แก่ ภาวะตาบอด สายตาเลือนราง ตาบอดสี ภาวะหูหนวกหรือสูญเสียการได้ยิน และทักษะการเคลื่อนไหวที่จำกัด เมื่อพัฒนาแอปโดยคำนึงถึงความสามารถในการเข้าถึง คุณจะปรับปรุงประสบการณ์ของผู้ใช้ให้ดียิ่งขึ้น ไม่เพียงแต่สำหรับผู้ใช้ที่มีความพิการเท่านั้น แต่ยังรวมถึงผู้ใช้คนอื่นๆ ทั้งหมดด้วย

Android มีฟีเจอร์การช่วยเหลือพิเศษหลายอย่างโดยค่าเริ่มต้นในมุมมอง UI มาตรฐาน เช่น TextView และ Button อย่างไรก็ตาม เมื่อสร้างมุมมองที่กำหนดเอง คุณต้องพิจารณาว่ามุมมองที่กำหนดเองนั้นจะให้ฟีเจอร์ที่เข้าถึงได้ เช่น คำอธิบายที่พูดของเนื้อหาบนหน้าจอ ได้อย่างไร

ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับ TalkBack ซึ่งเป็นโปรแกรมอ่านหน้าจอของ Android และแก้ไขแอปให้มีคำใบ้และคำอธิบายที่อ่านออกเสียงได้สำหรับDialViewมุมมองที่กำหนดเอง

ขั้นตอนที่ 1 สำรวจ TalkBack

TalkBack เป็นโปรแกรมอ่านหน้าจอในตัวของ Android เมื่อเปิดใช้ TalkBack ผู้ใช้จะโต้ตอบกับอุปกรณ์ Android ได้โดยไม่ต้องมองหน้าจอ เนื่องจาก Android จะอธิบายองค์ประกอบบนหน้าจอด้วยการอ่านออกเสียง ผู้ใช้ที่มีความบกพร่องทางสายตาอาจต้องพึ่งพา TalkBack ในการใช้แอปของคุณ

ในงานนี้ คุณจะเปิดใช้ TalkBack เพื่อทำความเข้าใจวิธีการทำงานของโปรแกรมอ่านหน้าจอและวิธีไปยังส่วนต่างๆ ของแอป

  1. ในอุปกรณ์หรือโปรแกรมจำลอง Android ให้ไปที่การตั้งค่า > การช่วยเหลือพิเศษ > TalkBack
  2. แตะปุ่มเปิด/ปิดเพื่อเปิด TalkBack
  3. แตะตกลงเพื่อยืนยันสิทธิ์
  4. ยืนยันรหัสผ่านของอุปกรณ์ หากระบบขอ หากคุณเรียกใช้ TalkBack เป็นครั้งแรก บทแนะนำจะเปิดขึ้น (บทแนะนำอาจไม่พร้อมใช้งานในอุปกรณ์รุ่นเก่า)
  5. การไปยังส่วนต่างๆ ของบทแนะนำโดยหลับตาอาจเป็นประโยชน์ หากต้องการเปิดบทแนะนำอีกครั้งในอนาคต ให้ไปที่การตั้งค่า > การช่วยเหลือพิเศษ > TalkBack > การตั้งค่า > เปิดบทแนะนำ TalkBack
  6. คอมไพล์และเรียกใช้CustomFanControllerแอป หรือเปิดด้วยปุ่มภาพรวมหรือล่าสุดในอุปกรณ์ เมื่อเปิด TalkBack คุณจะเห็นว่าระบบจะอ่านชื่อแอป รวมถึงข้อความของป้ายกำกับ TextView ("การควบคุมพัดลม") อย่างไรก็ตาม หากคุณแตะที่DialViewมุมมองเอง ระบบจะไม่พูดข้อมูลเกี่ยวกับสถานะของมุมมอง (การตั้งค่าปัจจุบันสำหรับแป้นหมุน) หรือการดำเนินการที่จะเกิดขึ้นเมื่อคุณแตะมุมมองเพื่อเปิดใช้งาน

ขั้นตอนที่ 2 เพิ่มคำอธิบายเนื้อหาสำหรับป้ายกำกับแบบหมุน

คำอธิบายเนื้อหาจะอธิบายความหมายและวัตถุประสงค์ของมุมมองในแอป ป้ายกำกับเหล่านี้ช่วยให้โปรแกรมอ่านหน้าจอ เช่น ฟีเจอร์ TalkBack ของ Android อธิบายฟังก์ชันของแต่ละองค์ประกอบได้อย่างถูกต้อง สำหรับมุมมองแบบคงที่ เช่น ImageView คุณสามารถเพิ่มคำอธิบายเนื้อหาลงในมุมมองในไฟล์เลย์เอาต์ด้วยแอตทริบิวต์ contentDescription มุมมองข้อความ (TextView และ EditText) จะใช้ข้อความในมุมมองเป็นคำอธิบายเนื้อหาโดยอัตโนมัติ

สำหรับมุมมองการควบคุมพัดลมที่กำหนดเอง คุณต้องอัปเดตคำอธิบายเนื้อหาแบบไดนามิกทุกครั้งที่มีการคลิกมุมมองเพื่อระบุการตั้งค่าพัดลมปัจจุบัน

  1. ที่ด้านล่างของคลาส DialView ให้ประกาศฟังก์ชัน updateContentDescription() ที่ไม่มีอาร์กิวเมนต์หรือประเภทการคืนค่า
fun updateContentDescription() {
}
  1. ใน updateContentDescription() ให้เปลี่ยนพร็อพเพอร์ตี้ contentDescription สำหรับมุมมองที่กำหนดเองเป็นทรัพยากรสตริงที่เชื่อมโยงกับความเร็วพัดลมปัจจุบัน (ปิด, 1, 2 หรือ 3) ป้ายกำกับเหล่านี้เป็นป้ายกำกับเดียวกันกับที่ใช้ใน onDraw() เมื่อวาดแป้นหมุนบนหน้าจอ
fun updateContentDescription() {
   contentDescription = resources.getString(fanSpeed.label)
}
  1. เลื่อนขึ้นไปที่บล็อก init() แล้วเพิ่มการเรียกไปยัง updateContentDescription() ที่ท้ายบล็อกนั้น ซึ่งจะเริ่มต้นคำอธิบายเนื้อหาเมื่อเริ่มต้นมุมมอง
init {
   isClickable = true
   // ...

   updateContentDescription()
}
  1. เพิ่มการเรียกอีกครั้งในเมธอด updateContentDescription() ในเมธอด performClick() ก่อน invalidate()
override fun performClick(): Boolean {
   if (super.performClick()) return true
   fanSpeed = fanSpeed.next()
   updateContentDescription()
   invalidate()
   return true
}
  1. คอมไพล์และเรียกใช้แอป และตรวจสอบว่าได้เปิด TalkBack แล้ว แตะเพื่อเปลี่ยนการตั้งค่าสำหรับมุมมองแป้นหมุน แล้วสังเกตว่าตอนนี้ TalkBack จะประกาศป้ายกำกับปัจจุบัน (ปิด, 1, 2, 3) รวมถึงวลี "แตะสองครั้งเพื่อเปิดใช้งาน"

ขั้นตอนที่ 3 เพิ่มข้อมูลเพิ่มเติมสำหรับการดำเนินการคลิก

คุณหยุดที่ตรงนี้ได้และมุมมองของคุณจะใช้ได้ใน TalkBack แต่จะดีกว่าหากมุมมองของคุณระบุได้ไม่เพียงแค่ว่าเปิดใช้งานได้ ("แตะสองครั้งเพื่อเปิดใช้งาน") แต่ยังอธิบายด้วยว่าจะเกิดอะไรขึ้นเมื่อเปิดใช้งานมุมมอง ("แตะสองครั้งเพื่อเปลี่ยน" หรือ "แตะสองครั้งเพื่อรีเซ็ต")

โดยคุณจะเพิ่มข้อมูลเกี่ยวกับการดำเนินการของมุมมอง (ในที่นี้คือการคลิกหรือแตะ) ลงในออบเจ็กต์ข้อมูลโหนดการช่วยเหลือพิเศษได้โดยใช้ตัวแทนการช่วยเหลือพิเศษ Accessibility Delegate ช่วยให้คุณปรับแต่งฟีเจอร์ที่เกี่ยวข้องกับการช่วยเหลือพิเศษของแอปผ่านการคอมโพสิชัน (แทนที่จะเป็นการรับค่า)

สำหรับงานนี้ คุณจะใช้คลาสการช่วยเหลือพิเศษในไลบรารี Android Jetpack (androidx.*) เพื่อให้มั่นใจถึงความเข้ากันได้แบบย้อนหลัง

  1. ใน DialView.kt ในบล็อก init ให้ตั้งค่าผู้มอบสิทธิ์การช่วยเหลือพิเศษในมุมมองเป็นออบเจ็กต์ AccessibilityDelegateCompat ใหม่ นำเข้า androidx.core.view.ViewCompat และ androidx.core.view.AccessibilityDelegateCompat เมื่อมีการร้องขอ กลยุทธ์นี้ช่วยให้แอปของคุณเข้ากันได้แบบย้อนหลังมากที่สุด
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   
})
  1. ในออบเจ็กต์ AccessibilityDelegateCompat ให้ลบล้างฟังก์ชัน onInitializeAccessibilityNodeInfo() ด้วยออบเจ็กต์ AccessibilityNodeInfoCompat แล้วเรียกใช้เมธอดของคลาสแม่ นำเข้า androidx.core.view.accessibility.AccessibilityNodeInfoCompat เมื่อได้รับข้อความแจ้ง
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)

   }  
})

ทุกมุมมองจะมีโครงสร้างแบบต้นไม้ของโหนดการช่วยเหลือพิเศษ ซึ่งอาจตรงหรือไม่ตรงกับคอมโพเนนต์เลย์เอาต์จริงของมุมมองก็ได้ บริการการช่วยเหลือพิเศษของ Android จะไปยังโหนดเหล่านั้นเพื่อค้นหาข้อมูลเกี่ยวกับมุมมอง (เช่น คำอธิบายเนื้อหาที่พูดได้ หรือการดำเนินการที่เป็นไปได้ซึ่งทำได้ในมุมมองนั้น) เมื่อสร้างมุมมองที่กำหนดเอง คุณอาจต้องลบล้างข้อมูลโหนดเพื่อระบุข้อมูลที่กำหนดเองสำหรับการช่วยเหลือพิเศษด้วย ในกรณีนี้ คุณจะลบล้างข้อมูลโหนดเพื่อระบุว่ามีการดำเนินการที่กำหนดเองสำหรับมุมมอง

  1. ภายใน 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 ใช้เพื่อระบุว่าการดำเนินการคืออะไร

  1. แทนที่สตริง "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)
      )
   }  
})
  1. หลังจากวงเล็บปิดสำหรับ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)
   }
})
  1. ใน res/values/strings.xml ให้เพิ่มทรัพยากรสตริงสำหรับ "เปลี่ยน" และ "รีเซ็ต"
<string name="change">Change</string>
<string name="reset">Reset</string>
  1. คอมไพล์และเรียกใช้แอป แล้วตรวจสอบว่า TalkBack เปิดอยู่ ตอนนี้คุณจะเห็นว่าวลี "แตะสองครั้งเพื่อเปิดใช้งาน" เปลี่ยนเป็น "แตะสองครั้งเพื่อเปลี่ยน" (หากความเร็วพัดลมต่ำกว่าสูงหรือ 3) หรือ "แตะสองครั้งเพื่อรีเซ็ต" (หากความเร็วพัดลมสูงหรือ 3 อยู่แล้ว) โปรดทราบว่าข้อความแจ้ง "แตะสองครั้งเพื่อ..." มาจากบริการ TalkBack เอง

ดาวน์โหลดโค้ดสำหรับ Codelab ที่เสร็จสมบูรณ์

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-custom-views


หรือคุณจะดาวน์โหลดที่เก็บเป็นไฟล์ Zip, แตกไฟล์ และเปิดใน Android Studio ก็ได้

ดาวน์โหลด Zip

  • หากต้องการสร้างมุมมองที่กำหนดเองซึ่งรับช่วงลักษณะและลักษณะการทำงานของคลาสย่อย View เช่น EditText ให้เพิ่มคลาสใหม่ที่ขยายคลาสย่อยนั้น และทำการปรับเปลี่ยนโดยการลบล้างเมธอดบางอย่างของคลาสย่อย
  • หากต้องการสร้างมุมมองที่กำหนดเองที่มีขนาดและรูปร่างใดก็ได้ ให้เพิ่มคลาสใหม่ที่ขยาย View
  • แทนที่เมธอด View เช่น onDraw() เพื่อกำหนดรูปร่างและลักษณะพื้นฐานของมุมมอง
  • ใช้ invalidate() เพื่อบังคับให้วาดหรือวาดมุมมองใหม่
  • หากต้องการเพิ่มประสิทธิภาพ ให้จัดสรรตัวแปรและกําหนดค่าที่จําเป็นสําหรับการวาดและระบายสีก่อนที่จะใช้ใน onDraw() เช่น ในการเริ่มต้นตัวแปรสมาชิก
  • แทนที่จะใช้ OnClickListener() ให้ใช้การลบล้าง performClick() กับมุมมองที่กำหนดเองเพื่อให้ลักษณะการทำงานแบบอินเทอร์แอกทีฟของมุมมอง ซึ่งจะช่วยให้คุณหรือนักพัฒนาแอป Android คนอื่นๆ ที่อาจใช้คลาส View ที่กำหนดเองของคุณใช้ onClickListener() เพื่อให้ลักษณะการทำงานเพิ่มเติมได้
  • เพิ่มมุมมองที่กำหนดเองลงในไฟล์เลย์เอาต์ XML พร้อมแอตทริบิวต์เพื่อกำหนดลักษณะที่ปรากฏ เช่นเดียวกับองค์ประกอบ UI อื่นๆ
  • สร้างไฟล์ attrs.xml ในโฟลเดอร์ values เพื่อกำหนดแอตทริบิวต์ที่กำหนดเอง จากนั้นคุณจะใช้แอตทริบิวต์ที่กำหนดเองสำหรับมุมมองที่กำหนดเองในไฟล์เลย์เอาต์ XML ได้

หลักสูตร Udacity:

เอกสารประกอบสำหรับนักพัฒนาแอป Android

วิดีโอ:

ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้

  • มอบหมายการบ้านหากจำเป็น
  • สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
  • ให้คะแนนงานการบ้าน

ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม

หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ

คำถามที่ 1

หากต้องการคำนวณตำแหน่ง มิติข้อมูล และค่าอื่นๆ เมื่อมีการกำหนดขนาดให้กับมุมมองที่กำหนดเองเป็นครั้งแรก คุณจะลบล้างเมธอดใด

onMeasure()

onSizeChanged()

invalidate()

onDraw()

คำถามที่ 2

หากต้องการระบุว่าคุณต้องการให้วาดมุมมองใหม่ด้วย onDraw() คุณจะเรียกเมธอดใดจากเทรด UI หลังจากที่ค่าแอตทริบิวต์เปลี่ยนแปลง

▢ onMeasure()

▢ onSizeChanged()

▢ invalidate()

▢ getVisibility()

คำถามที่ 3

คุณควรแทนที่Viewเมธอดใดเพื่อเพิ่มการโต้ตอบในมุมมองที่กำหนดเอง

▢ setOnClickListener()

▢ onSizeChanged()

▢ isClickable()

▢ performClick()

ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin