เนื้อหาต่อไปนี้มีไว้สำหรับนักพัฒนาแอป
หากต้องการให้แอปรองรับการหมุน คุณต้องดำเนินการต่อไปนี้
- วาง
FocusParkingView
ในเลย์เอาต์กิจกรรมที่เกี่ยวข้อง - ตรวจสอบว่ามุมมองที่โฟกัสได้ (หรือโฟกัสไม่ได้)
- ใช้
FocusArea
เพื่อตัดมุมมองที่โฟกัสได้ทั้งหมด ยกเว้นFocusParkingView
แต่ละงานมีรายละเอียดด้านล่าง หลังจากที่คุณตั้งค่าสภาพแวดล้อมเพื่อพัฒนาแอปที่เปิดใช้การหมุนแล้ว
ตั้งค่าตัวควบคุมแบบหมุน
คุณต้องควบคุมแบบหมุนหรือใช้อุปกรณ์แทนก่อนจึงจะเริ่มพัฒนาแอปที่พร้อมใช้งานตัวควบคุมแบบหมุนได้ คุณมีตัวเลือกดังที่อธิบายไว้ด้านล่าง
โปรแกรมจำลอง
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
หรือจะใช้ aosp_car_x86_64-userdebug
ก็ได้
วิธีเข้าถึงตัวควบคุมแบบหมุนที่จำลอง
- แตะจุด 3 จุดที่ด้านล่างของแถบเครื่องมือ
รูปที่ 1 เข้าถึงตัวควบคุมแบบหมุนจำลอง - เลือกปุ่มหมุนของรถยนต์ในหน้าต่างการควบคุมแบบขยาย
รูปที่ 2 เลือก Rotary สำหรับรถยนต์
แป้นพิมพ์ USB
- เสียบแป้นพิมพ์ USB เข้ากับอุปกรณ์ที่ใช้ระบบปฏิบัติการ Android Automotive (AAOS) ในบางกรณี วิธีนี้อาจทำให้แป้นพิมพ์บนหน้าจอไม่ปรากฏขึ้น
- ใช้บิลด์
userdebug
หรือeng
- เปิดใช้การกรองเหตุการณ์สําคัญ
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- ดูตารางด้านล่างเพื่อหาแป้นที่สอดคล้องกันสำหรับการดำเนินการแต่ละรายการ
คีย์ การดำเนินการแบบหมุน Q หมุนทวนเข็มนาฬิกา E หมุนตามเข็มนาฬิกา ก กดไปทางซ้าย D กดไปทางขวา W กดขึ้น อา กดลง F หรือคอมมา ปุ่มตรงกลาง R หรือ Esc ปุ่มย้อนกลับ
คำสั่ง ADB
คุณสามารถใช้คำสั่ง car_service
เพื่อแทรกเหตุการณ์การป้อนข้อมูลแบบหมุนได้ คำสั่งเหล่านี้สามารถเรียกใช้ได้ในอุปกรณ์ที่ใช้ Android Automotive OS (AAOS) หรือในโปรแกรมจำลอง
คำสั่ง car_service | การป้อนข้อมูลด้วยปุ่มหมุน |
---|---|
adb shell cmd car_service inject-rotary |
หมุนทวนเข็มนาฬิกา |
adb shell cmd car_service inject-rotary -c true |
หมุนตามเข็มนาฬิกา |
adb shell cmd car_service inject-rotary -dt 100 50 |
หมุนทวนเข็มนาฬิกาหลายครั้ง (100 มิลลิวินาทีที่ผ่านมาและ 50 มิลลิวินาทีที่ผ่านมา) |
adb shell cmd car_service inject-key 282 |
กดไปทางซ้าย |
adb shell cmd car_service inject-key 283 |
กดไปทางขวา |
adb shell cmd car_service inject-key 280 |
กดขึ้น |
adb shell cmd car_service inject-key 281 |
กดลง |
adb shell cmd car_service inject-key 23 |
การคลิกปุ่มกลาง |
adb shell input keyevent inject-key 4 |
การคลิกปุ่มย้อนกลับ |
ตัวควบคุมแบบหมุนของ OEM
เมื่อฮาร์ดแวร์ตัวควบคุมแบบหมุนพร้อมใช้งาน ตัวเลือกนี้จะสมจริงที่สุด ซึ่งมีประโยชน์อย่างยิ่งสำหรับการทดสอบการหมุนอย่างรวดเร็ว
FocusParkingView
FocusParkingView
คือมุมมองแบบโปร่งใสในไลบรารี UI ของรถ (car-ui-library)
RotaryService
ใช้เพื่อรองรับการนำทางด้วยตัวควบคุมแบบหมุน
FocusParkingView
ต้องเป็นมุมมองแรกที่โฟกัสได้
ในเลย์เอาต์ โดยต้องอยู่นอก FocusArea
ทั้งหมด แต่ละกรอบต้องมี FocusParkingView
1 รายการ หากคุณใช้เลย์เอาต์พื้นฐาน car-ui-library ซึ่งมี FocusParkingView
อยู่แล้ว ก็ไม่จำเป็นต้องเพิ่ม FocusParkingView
อื่น ตัวอย่าง FocusParkingView
ใน
RotaryPlayground
แสดงอยู่ด้านล่าง
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.android.car.ui.FocusParkingView android:layout_width="wrap_content" android:layout_height="wrap_content"/> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
เหตุผลที่ต้องมี FocusParkingView
มีดังนี้
- Android จะไม่ล้างโฟกัสโดยอัตโนมัติเมื่อตั้งค่าโฟกัสในหน้าต่างอื่น หากคุณพยายามล้างโฟกัสในหน้าต่างก่อนหน้า Android จะโฟกัสมุมมองในหน้าต่างนั้นอีกครั้ง ซึ่งส่งผลให้มีโฟกัส 2 หน้าต่างพร้อมกัน การเพิ่ม
FocusParkingView
ลงในหน้าต่างแต่ละบานจะช่วยแก้ปัญหานี้ได้ มุมมองนี้เป็นแบบโปร่งใสและปิดใช้การไฮไลต์โฟกัสเริ่มต้นไว้ ผู้ใช้จึงมองไม่เห็นไม่ว่าโฟกัสอยู่หรือไม่ก็ตาม รายการนี้สามารถรับโฟกัสเพื่อให้RotaryService
หยุดโฟกัสไว้ได้เพื่อนำไฮไลต์โฟกัสออก - หากมี
FocusArea
เพียงรายการเดียวในหน้าต่างปัจจุบัน การบิดตัวควบคุมในFocusArea
จะทำให้RotaryService
ย้ายโฟกัสจากมุมมองทางด้านขวาไปยังมุมมองทางด้านซ้าย (และในทางกลับกัน) การเพิ่มมุมมองนี้ลงในหน้าต่างแต่ละบานจะช่วยแก้ปัญหาได้ เมื่อRotaryService
ระบุว่าเป้าหมายโฟกัสคือFocusParkingView
ก็จะสามารถระบุได้ว่าการวนรอบกำลังจะเกิดขึ้น และเมื่อถึงจุดนั้นก็จะหลีกเลี่ยงการวนรอบโดยไม่ย้ายโฟกัส - เมื่อตัวควบคุมแบบหมุนเปิดแอป Android จะโฟกัสที่มุมมองแรกที่โฟกัสได้ ซึ่งก็คือ
FocusParkingView
เสมอFocusParkingView
จะกำหนดมุมมองที่ดีที่สุดที่จะโฟกัส จากนั้นจึงใช้โฟกัส
มุมมองที่โฟกัสได้
RotaryService
สร้างขึ้นจากแนวคิดที่มีอยู่ของโฟกัสมุมมองในเฟรมเวิร์ก Android ซึ่งย้อนกลับไปตั้งแต่สมัยที่โทรศัพท์มีแป้นพิมพ์จริงและปุ่ม D-pad
แอตทริบิวต์ android:nextFocusForward
ที่มีอยู่มีไว้สำหรับโรตารี (ดูการปรับแต่ง FocusArea) แต่ android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
และ android:nextFocusDown
ไม่ได้มีไว้สำหรับโรตารี
RotaryService
จะมุ่งเน้นเฉพาะมุมมองที่โฟกัสได้ มุมมองบางอย่าง เช่น Button
มักจะโฟกัสได้ ส่วนรายการอื่นๆ เช่น TextView
และ ViewGroup
มักจะไม่ มุมมองที่คลิกได้จะโฟกัสโดยอัตโนมัติและมุมมองจะคลิกได้โดยอัตโนมัติเมื่อมีตัวรับฟังการคลิก หากตรรกะอัตโนมัตินี้ส่งผลให้เกิดโหมดโฟกัสที่ต้องการ คุณก็ไม่จําเป็นต้องตั้งค่าโหมดโฟกัสของมุมมองอย่างชัดเจน หากตรรกะอัตโนมัติไม่ได้ทําให้โฟกัสได้ตามที่คุณต้องการ ให้ตั้งค่าแอตทริบิวต์ android:focusable
เป็น true
หรือ false
หรือตั้งค่าโฟกัสได้ของมุมมองด้วยโปรแกรมโดยใช้ View.setFocusable(boolean)
มุมมองต้องเป็นไปตามข้อกำหนดต่อไปนี้ RotaryService
จึงจะโฟกัสที่มุมมองนั้นได้
- โฟกัสได้
- เปิดใช้
- แสดง
- มีค่าความกว้างและความสูงที่ไม่ใช่ 0
หากมุมมองไม่เป็นไปตามข้อกำหนดเหล่านี้ทั้งหมด เช่น ปุ่มที่โฟกัสได้แต่ปิดอยู่ ผู้ใช้จะใช้ตัวควบคุมแบบหมุนเพื่อโฟกัสที่มุมมองนั้นไม่ได้ หากต้องการโฟกัสที่มุมมองที่ปิดใช้ ให้พิจารณาใช้สถานะที่กําหนดเองแทน android:state_enabled
เพื่อควบคุมลักษณะที่มุมมองปรากฏโดยไม่ระบุว่า Android ควรปิดใช้มุมมองนั้น แอปสามารถแจ้งให้ผู้ใช้ทราบถึงสาเหตุที่มุมมองถูกปิดใช้เมื่อแตะ ส่วนถัดไปจะอธิบายวิธีดำเนินการนี้
สถานะที่กำหนดเอง
วิธีเพิ่มสถานะที่กําหนดเอง
- วิธีเพิ่มแอตทริบิวต์ที่กําหนดเองลงในมุมมอง เช่น หากต้องการเพิ่มสถานะที่กำหนดเอง
state_rotary_enabled
ให้กับคลาสมุมมองCustomView
ให้ใช้คำสั่งต่อไปนี้<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- หากต้องการติดตามสถานะนี้ ให้เพิ่มตัวแปรอินสแตนซ์ลงในมุมมองพร้อมกับเมธอดการเข้าถึง ดังนี้
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- วิธีอ่านค่าของแอตทริบิวต์เมื่อสร้างมุมมอง
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- ในคลาสมุมมอง ให้ลบล้างเมธอด
onCreateDrawableState()
แล้วเพิ่มสถานะที่กําหนดเองตามความเหมาะสม เช่น@Override protected int[] onCreateDrawableState(int extraSpace) { if (mRotaryEnabled) extraSpace++; int[] drawableState = super.onCreateDrawableState(extraSpace); if (mRotaryEnabled) { mergeDrawableStates(drawableState, { R.attr.state_rotary_enabled }); } return drawableState; }
- ทําให้ตัวแฮนเดิลการคลิกของมุมมองทํางานแตกต่างกันไปตามสถานะ เช่น ตัวแฮนเดิลการคลิกอาจไม่ทําการใดๆ หรืออาจแสดงข้อความแจ้งเมื่อ
mRotaryEnabled
เป็นfalse
- หากต้องการให้ปุ่มปรากฏว่าปิดใช้ ให้ใช้
app:state_rotary_enabled
แทนandroid:state_enabled
ในองค์ประกอบภาพพื้นหลังของมุมมอง หากยังไม่มี คุณจะต้องเพิ่มข้อมูลต่อไปนี้xmlns:app="http://schemas.android.com/apk/res-auto"
- หากมุมมองปิดใช้ในเลย์เอาต์ใด ให้แทนที่
android:enabled="false"
ด้วยapp:state_rotary_enabled="false"
แล้วเพิ่มเนมสเปซapp
ดังที่แสดงด้านบน - หากมีการปิดใช้มุมมองของคุณแบบเป็นโปรแกรม ให้แทนที่การเรียกใช้
setEnabled()
ด้วยการเรียกใช้setRotaryEnabled()
FocusArea
ใช้ FocusAreas
เพื่อแบ่งมุมมองที่โฟกัสได้ออกเป็นบล็อกเพื่อให้ไปยังส่วนต่างๆ ได้ง่ายขึ้นและสอดคล้องกับแอปอื่นๆ ตัวอย่างเช่น หากแอปมีแถบเครื่องมือ แถบเครื่องมือควรอยู่ใน FocusArea
แยกต่างหากจากส่วนอื่นๆ ของแอป แถบแท็บและองค์ประกอบการนําทางอื่นๆ ควรแยกออกจากส่วนอื่นๆ ของแอปด้วย โดยปกติแล้วรายการขนาดใหญ่ควรมี FocusArea
เป็นของตัวเอง หากไม่ได้ตั้งค่าไว้ ผู้ใช้จะต้องเลื่อนดูรายการทั้งหมดเพื่อเข้าถึงมุมมองบางอย่าง
FocusArea
เป็นคลาสย่อยของ LinearLayout
ใน car-ui-library
เมื่อเปิดใช้ฟีเจอร์นี้ FocusArea
จะวาดไฮไลต์เมื่อโฟกัสที่องค์ประกอบย่อย ดูข้อมูลเพิ่มเติมได้ที่การปรับแต่งไฮไลต์โฟกัส
เมื่อสร้างบล็อกการนําทางในไฟล์เลย์เอาต์ หากคุณต้องการใช้ LinearLayout
เป็นคอนเทนเนอร์สําหรับบล็อกนั้น ให้ใช้ FocusArea
แทน
หรือจะตัดบล็อกใน FocusArea
ก็ได้
อย่าฝัง FocusArea
ใน FocusArea
อื่น
เนื่องจากจะทําให้ระบุลักษณะการนําทางไม่ได้ ตรวจสอบว่ามุมมองที่โฟกัสได้ทั้งหมดอยู่ภายใน FocusArea
ตัวอย่าง FocusArea
ใน
RotaryPlayground
แสดงอยู่ด้านล่าง
<com.android.car.ui.FocusArea android:layout_margin="16dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true"> </EditText> </com.android.car.ui.FocusArea>
FocusArea
จะทํางานดังนี้
- เมื่อจัดการกับการหมุนและการขยับ
RotaryService
จะมองหาอินสแตนซ์ของFocusArea
ในลําดับชั้นของมุมมอง - เมื่อได้รับเหตุการณ์การหมุน
RotaryService
จะย้ายโฟกัสไปยังมุมมองอื่นที่รับโฟกัสในFocusArea
เดียวกันได้ - เมื่อได้รับเหตุการณ์การแตะเตือน
RotaryService
จะย้ายโฟกัสไปยังมุมมองอื่นซึ่งสามารถรับโฟกัสในFocusArea
อีกรายการหนึ่ง (โดยปกติจะอยู่ติดกัน)
หากคุณไม่ได้ใส่ FocusAreas
ไว้ในเลย์เอาต์ ระบบจะถือว่ามุมมองรูทเป็นพื้นที่โฟกัสโดยนัย ผู้ใช้จะแตะเพื่อไปยังส่วนต่างๆ ในแอปไม่ได้ แต่ระบบจะสลับไปยังมุมมองที่โฟกัสได้ทั้งหมดแทน ซึ่งอาจเพียงพอสำหรับกล่องโต้ตอบ
การปรับแต่ง FocusArea
คุณใช้แอตทริบิวต์มุมมองมาตรฐาน 2 รายการต่อไปนี้เพื่อปรับแต่งการไปยังส่วนต่างๆ ด้วยปุ่มหมุนได้
android:nextFocusForward
ช่วยให้นักพัฒนาแอประบุลำดับการหมุนในพื้นที่โฟกัสได้ ซึ่งเป็นแอตทริบิวต์เดียวกับที่ใช้ควบคุมลําดับ Tab สําหรับการไปยังส่วนต่างๆ ด้วยแป้นพิมพ์ อย่าใช้แอตทริบิวต์นี้เพื่อสร้างลูป แต่ให้ใช้app:wrapAround
(ดูด้านล่าง) เพื่อสร้างลูปแทนandroid:focusedByDefault
ช่วยให้นักพัฒนาแอประบุมุมมองโฟกัสเริ่มต้นในหน้าต่างได้ อย่าใช้แอตทริบิวต์นี้และapp:defaultFocus
(ดูด้านล่าง) ในFocusArea
เดียวกัน
FocusArea
ยังกำหนดแอตทริบิวต์บางอย่างเพื่อปรับแต่งการไปยังส่วนต่างๆ ด้วยปุ่มหมุนด้วย
คุณปรับแต่งพื้นที่โฟกัสโดยนัยด้วยแอตทริบิวต์เหล่านี้ไม่ได้
- (Android 11 QPR3, Android 11 Car,
Android 12)
app:defaultFocus
ใช้เพื่อระบุรหัสของมุมมองที่รับค่าสืบทอดซึ่งโฟกัสได้ ซึ่งควรโฟกัสเมื่อผู้ใช้แตะFocusArea
นี้ - (Android 11 QPR3, Android 11 Car,
Android 12)
app:defaultFocusOverridesHistory
สามารถตั้งค่าเป็นtrue
เพื่อให้โฟกัสไปที่มุมมองที่ระบุไว้ข้างต้น แม้ว่าจะมีประวัติเพื่อระบุว่าโฟกัสอยู่ที่มุมมองอื่นในFocusArea
นี้ก็ตาม - (Android 12)
ใช้app:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
และapp:nudgeDownShortcut
เพื่อระบุรหัสของมุมมองที่สืบทอดซึ่งโฟกัสได้ ซึ่งควรโฟกัสเมื่อผู้ใช้ปัดไปในทิศทางหนึ่งๆ ดูข้อมูลเพิ่มเติมได้ที่เนื้อหาเกี่ยวกับแป้นพิมพ์ลัดในการแตะเบาๆด้านล่าง(Android 11 QPR3, Android 11 Car, เลิกใช้งานแล้วใน Android 12)
app:nudgeShortcut
และapp:nudgeShortcutDirection
รองรับทางลัดการแตะเบาๆ เพียงรายการเดียว - (Android 11 QPR3, Android 11 Car,
Android 12)
หากต้องการเปิดใช้การหมุนแบบวนซ้ำในFocusArea
นี้ ให้ตั้งค่าapp:wrapAround
เป็นtrue
โดยทั่วไปแล้วจะใช้เมื่อจัดเรียงข้อมูลพร็อพเพอร์ตี้เป็นวงกลมหรือรูปไข่ - (Android 11 QPR3, Android 11 Car,
Android 12)
หากต้องการปรับระยะห่างจากขอบของข้อความไฮไลต์ในFocusArea
นี้ ให้ใช้app:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
และapp:highlightPaddingVertical
- (Android 11 QPR3, Android 11 Car,
Android 12)
หากต้องการปรับขอบเขตที่รับรู้ของFocusArea
นี้เพื่อค้นหาเป้าหมายการกระตุ้น ให้ใช้app:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
และapp:verticalBoundOffset
- (Android 11 QPR3, Android 11 Car,
Android 12)
หากต้องการระบุรหัสของFocusArea
(หรือพื้นที่) ที่อยู่ติดกันในทิศทางที่ระบุ ให้ใช้app:nudgeLeft
,app:nudgeRight
,app:nudgeUp
และapp:nudgeDown
ใช้ตัวเลือกนี้เมื่อการค้นหาเชิงเรขาคณิตที่ใช้โดยค่าเริ่มต้นไม่พบเป้าหมายที่ต้องการ
โดยปกติการนudge จะไปยังส่วนต่างๆ ของ FocusArea แต่การใช้แป้นพิมพ์ลัดสำหรับการกระตุ้นเตือนบางครั้งการกระตุ้นเตือนจะไปยังส่วนต่างๆ ภายใน FocusArea
ก่อน ผู้ใช้จึงอาจต้องกระตุ้นเตือน 2 ครั้งเพื่อไปยัง FocusArea
ถัดไป แป้นพิมพ์ลัดของ "แตะเบาๆ" จะมีประโยชน์เมื่อ FocusArea
มีรายการยาวๆ ตามด้วยปุ่มการดำเนินการแบบลอย ดังตัวอย่างด้านล่าง

หากไม่มีทางลัดการแตะเตือน ผู้ใช้จะต้องเลื่อนดูรายการทั้งหมดเพื่อไปยัง FAB
การปรับแต่งโฟกัสไฮไลต์
ดังที่ระบุไว้ข้างต้น RotaryService
สร้างขึ้นจากแนวคิดโฟกัสการดูที่มีอยู่ของเฟรมเวิร์ก Android เมื่อผู้ใช้หมุนและขยับ RotaryService
จะย้ายโฟกัสไปรอบๆ โดยโฟกัสที่มุมมองหนึ่งและเลิกโฟกัสอีกมุมมองหนึ่ง ใน Android เมื่อโฟกัสที่มุมมองหนึ่งๆ มุมมองนั้นจะมีลักษณะดังนี้
- ระบุไฮไลต์โฟกัสของตนเองไว้ Android จะวาดไฮไลต์โฟกัสของมุมมอง
- ไม่ระบุการไฮไลต์โฟกัส และไม่ได้ปิดใช้การไฮไลต์โฟกัสเริ่มต้น Android จะวาดการไฮไลต์โฟกัสเริ่มต้นสำหรับมุมมอง
แอปที่ออกแบบมาเพื่อการสัมผัสมักจะไม่ได้ระบุไฮไลต์โฟกัสที่เหมาะสม
เฟรมเวิร์ก Android จะเป็นผู้ระบุไฮไลต์โฟกัสเริ่มต้น และ OEM สามารถลบล้างได้ นักพัฒนาแอปจะได้รับเครดิตเมื่อธีมที่ใช้มาจาก
Theme.DeviceDefault
ใช้การไฮไลต์โฟกัสเริ่มต้นทุกครั้งที่เป็นไปได้เพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่สอดคล้องกัน
หากต้องการไฮไลต์โฟกัสที่มีรูปร่างที่กำหนดเอง (เช่น กลมหรือรูปเม็ดยา) หรือหากใช้ธีมที่ไม่ได้มาจาก Theme.DeviceDefault
ให้ใช้ทรัพยากร car-ui-library เพื่อระบุไฮไลต์โฟกัสของคุณเองสำหรับแต่ละมุมมอง
หากต้องการระบุไฮไลต์โฟกัสที่กำหนดเองสำหรับมุมมอง ให้เปลี่ยนภาพวาดพื้นหลังหรือพื้นหน้าของมุมมองเป็นภาพวาดที่แตกต่างออกไปเมื่อโฟกัสที่มุมมอง โดยทั่วไปแล้ว คุณจะต้องเปลี่ยนพื้นหลัง รูปภาพที่วาดได้ต่อไปนี้จะสร้างไฮไลต์โฟกัสทรงกลมหากใช้เป็นพื้นหลังสำหรับมุมมองสี่เหลี่ยมจัตุรัส
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true" android:state_pressed="true"> <shape android:shape="oval"> <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/> <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width" android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/> </shape> </item> <item android:state_focused="true"> <shape android:shape="oval"> <solid android:color="@color/car_ui_rotary_focus_fill_color"/> <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width" android:color="@color/car_ui_rotary_focus_stroke_color"/> </shape> </item> <item> <ripple...> ... </ripple> </item> </selector>
(Android 11 QPR3, Android 11 Car, Android 12) การอ้างอิงทรัพยากรที่ตัวหนาในตัวอย่างข้างต้นระบุทรัพยากรที่ car-ui-library กำหนด OEM จะลบล้างค่าเหล่านี้เพื่อให้สอดคล้องกับการไฮไลต์โฟกัสเริ่มต้นที่ระบุ วิธีนี้ช่วยให้สีไฮไลต์โฟกัส ความหนาของเส้นขีด และอื่นๆ ไม่เปลี่ยนแปลงเมื่อผู้ใช้ไปยังส่วนต่างๆ ระหว่างมุมมองที่มีไฮไลต์โฟกัสที่กำหนดเองกับมุมมองที่มีไฮไลต์โฟกัสเริ่มต้น รายการสุดท้ายคือภาพกระเพื่อมที่ใช้ในการสัมผัส ค่าเริ่มต้นที่ใช้สำหรับทรัพยากรแบบหนาจะปรากฏดังนี้

นอกจากนี้ ระบบจะเรียกใช้ไฮไลต์โฟกัสที่กำหนดเองเมื่อปุ่มมีสีพื้นหลังที่เป็นสีเดียวกันเพื่อดึงดูดความสนใจของผู้ใช้ ดังตัวอย่างด้านล่าง ซึ่งอาจทำให้เห็นไฮไลต์โฟกัสได้ยาก ในกรณีนี้ ให้ระบุไฮไลต์โฟกัสที่กำหนดเองโดยใช้สีรอง ดังนี้
![]() |
- (Android 11 QPR3, Android 11 Car,
Android 12)
car_ui_rotary_focus_fill_secondary_color
car_ui_rotary_focus_stroke_secondary_color
- (Android 12)
car_ui_rotary_focus_pressed_fill_secondary_color
car_ui_rotary_focus_pressed_stroke_secondary_color
เช่น
![]() |
![]() |
|
โฟกัสอยู่ ไม่ได้กด | โฟกัส กด |
การเลื่อนด้วยปุ่มหมุน
หากแอปใช้ RecyclerView
คุณควรใช้ CarUiRecyclerView
แทน วิธีนี้ช่วยให้มั่นใจได้ว่า UI ของคุณจะสอดคล้องกับ UI ของผู้อื่น เนื่องจากการปรับแต่งของ OEM จะมีผลกับ CarUiRecyclerView
ทั้งหมด
หากองค์ประกอบในรายการโฟกัสได้ทั้งหมด คุณก็ไม่ต้องดำเนินการใดๆ เพิ่มเติม การไปยังส่วนต่างๆ ด้วยปุ่มหมุนจะย้ายโฟกัสไปยังองค์ประกอบต่างๆ ในรายการ และรายการจะเลื่อนขึ้นเพื่อแสดงองค์ประกอบที่โฟกัสใหม่
(Android 11 QPR3, Android 11 Car, Android 12)
หากมีองค์ประกอบที่โฟกัสได้และโฟกัสไม่ได้ผสมกัน หรือหากองค์ประกอบทั้งหมดโฟกัสไม่ได้ คุณสามารถเปิดใช้การเลื่อนด้วยปุ่มหมุน ซึ่งจะช่วยให้ผู้ใช้ใช้ตัวควบคุมแบบหมุนเพื่อเลื่อนดูรายการทีละรายการได้โดยไม่ต้องข้ามรายการที่โฟกัสไม่ได้ หากต้องการเปิดใช้การเลื่อนแบบหมุน ให้ตั้งค่าแอตทริบิวต์ app:rotaryScrollEnabled
เป็น true
(Android 11 QPR3, Android 11 Car,
Android 12)
คุณสามารถเปิดใช้การเลื่อนแบบหมุนในมุมมองที่เลื่อนได้ทั้งหมด ซึ่งรวมถึง avCarUiRecyclerView
ด้วยวิธี setRotaryScrollEnabled()
ใน CarUiUtils
หากดำเนินการดังกล่าว คุณจะต้องดำเนินการดังนี้
- ทำให้มุมมองที่เลื่อนได้โฟกัสได้เพื่อให้โฟกัสได้เมื่อไม่มีมุมมองที่โฟกัสได้ซึ่งสืบทอดมาจากมุมมองนั้นแสดงอยู่
- ปิดใช้การไฮไลต์โฟกัสเริ่มต้นในมุมมองที่เลื่อนได้โดยการเรียกใช้
setDefaultFocusHighlightEnabled(false)
เพื่อให้มุมมองที่เลื่อนได้ดูเหมือนว่าไม่ได้โฟกัส - ตรวจสอบว่าได้โฟกัสที่มุมมองที่เลื่อนได้ก่อนองค์ประกอบที่สืบทอดโดยเรียกใช้
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
- ฟัง MotionEvents ด้วย
SOURCE_ROTARY_ENCODER
และAXIS_VSCROLL
หรือAXIS_HSCROLL
เพื่อระบุระยะทางที่จะเลื่อนและทิศทาง (ผ่านเครื่องหมาย)
เมื่อเปิดใช้การเลื่อนแบบหมุนใน CarUiRecyclerView
และผู้ใช้หมุนไปยังพื้นที่ที่ไม่มีมุมมองที่โฟกัสได้ แถบเลื่อนจะเปลี่ยนจากสีเทาเป็นสีน้ำเงิน ราวกับว่าเป็นการระบุว่าโฟกัสอยู่ที่แถบเลื่อน คุณใช้เอฟเฟกต์ที่คล้ายกันได้หากต้องการ
MotionEvents จะเหมือนกับเหตุการณ์ที่เกิดจากล้อเลื่อนบนเมาส์ ยกเว้นแหล่งที่มา
โหมดการจัดการโดยตรง
โดยปกติแล้ว การแตะเบาๆ และการหมุนจะเป็นการไปยังส่วนต่างๆ ของอินเทอร์เฟซผู้ใช้ ส่วนการกดปุ่มกลางจะเป็นการดําเนินการ แต่ก็ไม่ได้เป็นเช่นนั้นเสมอไป เช่น หากผู้ใช้ต้องการปรับระดับเสียงปลุก ผู้ใช้อาจใช้ตัวควบคุมแบบหมุนเพื่อไปยังแถบเลื่อนระดับเสียง กดปุ่มกลาง หมุนตัวควบคุมเพื่อปรับระดับเสียงปลุก แล้วกดปุ่มย้อนกลับเพื่อกลับไปที่การนําทาง ซึ่งเรียกว่าโหมดการจัดการโดยตรง (DM) ในโหมดนี้ ตัวควบคุมแบบหมุนจะใช้เพื่อโต้ตอบกับมุมมองโดยตรงแทนการไปยังส่วนต่างๆ
ติดตั้งใช้งาน DM ได้ 2 วิธีดังนี้ หากต้องการจัดการเฉพาะการหมุนและมุมมองที่ต้องการควบคุมให้ตอบสนองต่อ ACTION_SCROLL_FORWARD
และ ACTION_SCROLL_BACKWARD
AccessibilityEvent
อย่างเหมาะสม ให้ใช้กลไกแบบง่าย หรือใช้กลไกที่ขั้นสูง
กลไกแบบง่ายเป็นตัวเลือกเดียวในหน้าต่างระบบ ส่วนแอปจะใช้กลไกใดก็ได้
กลไกที่เรียบง่าย
(Android 11 QPR3, Android 11 Car,
Android 12)
แอปของคุณควรเรียกใช้
DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
RotaryService
จะรับรู้เมื่อผู้ใช้อยู่ในโหมด DM และเข้าสู่โหมด DM เมื่อผู้ใช้กดปุ่มกลางขณะที่โฟกัสอยู่ที่มุมมอง เมื่ออยู่ในโหมด DM ระบบจะหมุนด้วยแป้น ACTION_SCROLL_FORWARD
หรือ ACTION_SCROLL_BACKWARD
และออกจากโหมด DM เมื่อผู้ใช้กดปุ่มย้อนกลับ กลไกง่ายๆ นี้จะสลับสถานะที่เลือกของมุมมองเมื่อเข้าสู่และออกจากโหมด DM
หากต้องการให้ผู้ใช้ทราบว่าอยู่ในโหมด DM ให้ทำให้มุมมองของคุณดูแตกต่างออกไปเมื่อเลือก เช่น เปลี่ยนพื้นหลังเมื่อ android:state_selected
เป็น true
กลไกขั้นสูง
แอปจะกำหนดว่า RotaryService
เข้าสู่และออกจากโหมด DM เมื่อใด กดปุ่มกลางขณะที่โฟกัสอยู่ที่มุมมอง DM ควรเข้าสู่โหมด DM และปุ่มย้อนกลับควรออกจากโหมด DM เพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่สอดคล้องกัน หากไม่ได้ใช้ปุ่ม "ศูนย์" และ/หรือการแตะเบาๆ ปุ่มเหล่านี้จะเป็นวิธีอื่นในการออกจากโหมด DM สำหรับแอปอย่าง Maps คุณสามารถใช้ปุ่มเพื่อแสดง DM เพื่อเข้าสู่โหมด DM
มุมมองต้องมีคุณสมบัติต่อไปนี้จึงจะรองรับโหมด DM ขั้นสูง
- (Android 11 QPR3, Android 11 Car,
Android 12) ต้องคอยฟังเหตุการณ์
KEYCODE_DPAD_CENTER
เพื่อเข้าสู่โหมด DM และคอยฟังเหตุการณ์KEYCODE_BACK
เพื่อออกจากโหมด DM โดยเรียกใช้DirectManipulationHelper.enableDirectManipulationMode()
ในแต่ละกรณี หากต้องการฟังเหตุการณ์เหล่านี้ ให้ทําอย่างใดอย่างหนึ่งต่อไปนี้- ลงทะเบียน
OnKeyListener
หรือ
- ขยายมุมมอง แล้วลบล้างเมธอด
dispatchKeyEvent()
ของมุมมองนั้น
- ลงทะเบียน
- ควรฟังเหตุการณ์ Nudge (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
หรือKEYCODE_DPAD_RIGHT
) หากมุมมองควรจัดการ Nudge - ควรฟัง
MotionEvent
และรับจํานวนการหมุนในAXIS_SCROLL
หากมุมมองต้องการจัดการการหมุน ซึ่งทำได้หลายวิธีดังนี้- ลงทะเบียน
OnGenericMotionListener
- ขยายมุมมองและลบล้างเมธอด
dispatchTouchEvent()
ของมุมมอง
- ลงทะเบียน
- คุณต้องออกจากโหมด DM เมื่อข้อมูลโค้ดหรือกิจกรรมที่มุมมองนั้นอยู่ไม่ใช่แบบอินเทอร์แอกทีฟเพื่อไม่ให้ระบบค้างอยู่ในโหมด DM
- ควรระบุตัวช่วยในการมองเห็นเพื่อระบุว่ามุมมองอยู่ในโหมด DM
ตัวอย่างมุมมองที่กำหนดเองซึ่งใช้โหมด DM เพื่อเลื่อนและซูมแผนที่มีดังนี้
/** Whether this view is in DM mode. */ private boolean mInDirectManipulationMode;
/** Initializes the view. Called by the constructors. */ private void init() { setOnKeyListener((view, keyCode, keyEvent) -> { boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP; switch (keyCode) { // Always consume KEYCODE_DPAD_CENTER and KEYCODE_BACK events. case KeyEvent.KEYCODE_DPAD_CENTER: if (!mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = true; DirectManipulationHelper.enableDirectManipulationMode(this, true); setSelected(true); // visually indicate DM mode } return true; case KeyEvent.KEYCODE_BACK: if (mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); setSelected(false); } return true; // Consume controller nudge events only when in DM mode. // When in DM mode, nudges pan the map. case KeyEvent.KEYCODE_DPAD_UP: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, -10f); return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, 10f); return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(-10f, 0f); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(10f, 0f); return true; // Don't consume other key events. default: return false; } });
// When in DM mode, rotation zooms the map. setOnGenericMotionListener(((view, motionEvent) -> { if (!mInDirectManipulationMode) return false; float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL); zoom(10 * scroll); return true; })); }
@Override public void onPause() { if (mInDirectManipulationMode) { // To ensure that the user doesn't get stuck in DM mode, disable DM mode // when the fragment is not interactive (e.g., a dialog shows up). mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); } super.onPause(); }
ดูตัวอย่างเพิ่มเติมได้ในโปรเจ็กต์ RotaryPlayground
ActivityView
เมื่อใช้ ActivityView
ActivityView
ไม่ควรโฟกัสได้- (Android 11 QPR3, Android 11 Car,
เลิกใช้งานแล้วใน Android 11)
เนื้อหาของActivityView
ต้องมีFocusParkingView
เป็นมุมมองที่โฟกัสได้รายการแรก และapp:shouldRestoreFocus
แอตทริบิวต์ต้องเป็นfalse
- เนื้อหาของ
ActivityView
ไม่ควรมีandroid:focusByDefault
ยอดดู
สำหรับผู้ใช้แล้ว ActivityView ไม่ควรส่งผลต่อการนำทาง ยกเว้นว่าพื้นที่โฟกัสจะครอบคลุม ActivityView ไม่ได้ กล่าวคือ คุณไม่สามารถมีจุดสนใจเดียวที่มีเนื้อหาทั้งภายในและภายนอก ActivityView
หากคุณไม่ได้เพิ่ม FocusArea ใดๆ ลงใน ActivityView
ระบบจะถือว่ารูทของลําดับชั้นมุมมองใน ActivityView
เป็นพื้นที่โฟกัสโดยนัย
ปุ่มที่ทำงานเมื่อกดค้างไว้
ปุ่มส่วนใหญ่จะทําให้เกิดการดำเนินการบางอย่างเมื่อคลิก ปุ่มบางปุ่มจะทำงานเมื่อกดค้างไว้แทน
เช่น ปุ่มกรอไปข้างหน้าและกรอกลับมักจะทำงานเมื่อกดค้างไว้ หากต้องการให้ปุ่มดังกล่าวรองรับปุ่มหมุน ให้ฟังหา KEYCODE_DPAD_CENTER
KeyEvents
ดังนี้
mButton.setOnKeyListener((v, keyCode, event) -> { if (keyCode != KEYCODE_DPAD_CENTER) { return false; } if (event.getAction() == ACTION_DOWN) { mButton.setPressed(true); mHandler.post(mRunnable); } else { mButton.setPressed(false); mHandler.removeCallbacks(mRunnable); } return true; });
ซึ่ง mRunnable
จะดำเนินการ (เช่น กรอกลับ) และกำหนดเวลาให้ทำงานหลังจากรอเวลา
โหมดสัมผัส
ผู้ใช้สามารถใช้ตัวควบคุมแบบหมุนเพื่อโต้ตอบกับจอภาพหลักในรถได้ 2 วิธี นั่นคือ โดยใช้ตัวควบคุมแบบหมุนหรือโดยการสัมผัสหน้าจอ เมื่อใช้ตัวควบคุมแบบหมุน ระบบจะไฮไลต์มุมมองที่โฟกัสได้มุมมองใดมุมมองหนึ่ง เมื่อแตะหน้าจอ จะไม่มีไฮไลต์โฟกัสปรากฏขึ้น ผู้ใช้สามารถสลับระหว่างโหมดการป้อนข้อมูลต่อไปนี้ได้ทุกเมื่อ
- ปุ่มหมุน → แตะ เมื่อผู้ใช้แตะหน้าจอ ข้อความไฮไลต์โฟกัสจะหายไป
- แตะ → โรตารี่ เมื่อผู้ใช้เขย่ง หมุน หรือกดปุ่ม "ตรงกลาง" ข้อความไฮไลต์โฟกัสจะปรากฏขึ้น
ปุ่มย้อนกลับและปุ่มหน้าแรกจะไม่มีผลกับโหมดอินพุต
โรตารีใช้แนวคิดโหมดสัมผัสที่มีอยู่ของ Android
คุณสามารถใช้ View.isInTouchMode()
เพื่อระบุโหมดการป้อนข้อมูลของผู้ใช้ คุณใช้ OnTouchModeChangeListener
เพื่อฟังการเปลี่ยนแปลงได้ แม้ว่าจะใช้เพื่อปรับแต่งอินเทอร์เฟซผู้ใช้สำหรับโหมดการป้อนข้อมูลปัจจุบันได้ แต่ให้หลีกเลี่ยงการเปลี่ยนแปลงที่สำคัญเนื่องจากอาจทำให้สับสน
การแก้ปัญหา
ในแอปที่ออกแบบมาเพื่อสัมผัส การมีมุมมองที่โฟกัสได้แบบซ้อนกันเป็นเรื่องปกติ
เช่น อาจมี FrameLayout
รอบๆ ImageButton
ซึ่งทั้ง 2 รายการโฟกัสได้ การดำเนินการนี้ไม่ส่งผลเสียต่อการควบคุมด้วยการสัมผัส แต่อาจส่งผลให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ไม่ดีสำหรับการควบคุมแบบหมุน เนื่องจากผู้ใช้ต้องหมุนตัวควบคุม 2 ครั้งเพื่อไปยังมุมมองแบบอินเทอร์แอกทีฟถัดไป Google ขอแนะนำให้คุณทำให้ผู้ใช้โฟกัสได้ทั้งมุมมองด้านนอกหรือมุมมองด้านใน แต่ไม่ใช่ทั้ง 2 อย่าง เพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ดี
หากปุ่มหรือสวิตช์เสียโฟกัสเมื่อกดผ่านตัวควบคุมแบบหมุน แสดงว่าอาจเกิดจากเงื่อนไขข้อใดข้อหนึ่งต่อไปนี้
- ปุ่มหรือสวิตช์ปิดอยู่ (ชั่วคราวหรือถาวร) เนื่องจากมีการกดปุ่ม ไม่ว่าจะในกรณีใด คุณสามารถแก้ไขปัญหานี้ได้ 2 วิธีดังนี้
- ปล่อยสถานะ
android:enabled
เป็นtrue
และใช้สถานะที่กําหนดเองเพื่อทําให้ปุ่มหรือสวิตช์เป็นสีเทาตามที่อธิบายไว้ในสถานะที่กําหนดเอง - ใช้คอนเทนเนอร์ล้อมรอบปุ่มหรือสวิตช์และทำให้โฟกัสคอนเทนเนอร์ได้แทนปุ่มหรือสวิตช์ (ตัวรับฟังการคลิกต้องอยู่ในคอนเทนเนอร์)
- ปล่อยสถานะ
- กำลังเปลี่ยนปุ่มหรือสวิตช์ ตัวอย่างเช่น การดำเนินการที่เกิดขึ้นเมื่อกดปุ่มหรือสลับสวิตช์อาจทริกเกอร์การรีเฟรชการดำเนินการที่ใช้ได้ ซึ่งจะทำให้ปุ่มใหม่มาแทนที่ปุ่มที่มีอยู่ ปัญหานี้แก้ไขได้ 2 วิธี ดังนี้
- ตั้งค่าไอคอนและ/หรือข้อความของปุ่มหรือสวิตช์ที่มีอยู่แทนการสร้างปุ่มหรือสวิตช์ใหม่
- เพิ่มคอนเทนเนอร์ที่โฟกัสได้รอบๆ ปุ่มหรือสวิตช์ ดังที่อธิบายไว้ข้างต้น
RotaryPlayground
RotaryPlayground
เป็นแอปอ้างอิงสำหรับโรตารี ใช้เพื่อดูวิธีผสานรวมฟีเจอร์การหมุนเข้ากับแอป RotaryPlayground
รวมอยู่ในบิลด์โปรแกรมจำลองและบิลด์สำหรับอุปกรณ์ที่ใช้ Android Automotive OS (AAOS)
RotaryPlayground
repository:packages/apps/Car/tests/RotaryPlayground/
- เวอร์ชัน: Android 11 QPR3, Android 11 Car และ Android 12
แอป RotaryPlayground
จะแสดงแท็บต่อไปนี้ทางด้านซ้าย
- การ์ด ทดสอบการไปยังส่วนต่างๆ ของโฟกัส การข้ามองค์ประกอบที่โฟกัสไม่ได้ และการป้อนข้อความ
- Direct Manipulation ทดสอบวิดเจ็ตที่รองรับโหมดการจัดการโดยตรงแบบง่ายและขั้นสูง แท็บนี้มีไว้สำหรับการดําเนินการโดยตรงภายในหน้าต่างแอปโดยเฉพาะ
- การดําเนินการกับ UI ของระบบ ทดสอบวิดเจ็ตที่รองรับการจัดการโดยตรงในหน้าต่างของระบบที่รองรับเฉพาะโหมดการจัดการโดยตรงแบบง่ายเท่านั้น
- ตารางกริด ทดสอบการไปยังส่วนต่างๆ ของเมนูด้วยปุ่มหมุนตามรูปแบบ Z โดยการเลื่อน
- การแจ้งเตือน ทดสอบการแตะเพื่อแสดงและซ่อนการแจ้งเตือนล่วงหน้า
- เลื่อน ทดสอบการเลื่อนดูเนื้อหาที่โฟกัสได้และโฟกัสไม่ได้ผสมกัน
- WebView ทดสอบการไปยังส่วนต่างๆ ของลิงก์ใน
WebView
- กำหนดเอง
FocusArea
ทดสอบการปรับแต่งFocusArea
ดังนี้- ยิงได้จากลูกอ้อมประตู
android:focusedByDefault
และapp:defaultFocus
.
- เป้าหมายการกระตุ้นให้ดำเนินการที่ชัดเจน
- แป้นพิมพ์ลัดสำหรับขยับ
FocusArea
ที่ไม่มีมุมมองที่โฟกัสได้