เอกสารต่อไปนี้มีไว้สำหรับนักพัฒนาแอป
เพื่อให้แอปของคุณรองรับแบบหมุน คุณต้อง:
- วาง
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
ในการเข้าถึงตัวควบคุมโรตารีจำลอง:
- แตะที่จุดสามจุดที่ด้านล่างของแถบเครื่องมือ:
รูปที่ 1. เข้าถึงตัวควบคุมโรตารี่จำลอง - เลือก Carrotation ในหน้าต่างการควบคุมแบบขยาย:
รูปที่ 2 เลือกรถแบบหมุน
แป้นพิมพ์ USB
- เสียบแป้นพิมพ์ USB เข้ากับอุปกรณ์ของคุณที่ใช้ Android Automotive OS (AAOS) ในบางกรณี การทำเช่นนี้อาจทำให้แป้นพิมพ์บนหน้าจอไม่ปรากฏขึ้น
- ใช้
userdebug
หรือeng
build - เปิดใช้งานการกรองเหตุการณ์สำคัญ:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- ดูตารางด้านล่างเพื่อค้นหาคีย์ที่เกี่ยวข้องสำหรับแต่ละการกระทำ:
สำคัญ การกระทำของโรตารี คิว หมุนทวนเข็มนาฬิกา อี หมุนตามเข็มนาฬิกา อา เขยิบไปทางซ้าย ดี เขยิบขวา 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 ms ที่แล้วและ 50 ms ที่แล้ว) |
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
เป็นมุมมองที่โปร่งใสใน Car UI Library (car-ui-library) RotaryService
ใช้เพื่อสนับสนุนการนำทางตัวควบคุมแบบโรตารี่ FocusParkingView
ต้องเป็นมุมมองที่โฟกัสได้อันแรกในเลย์เอาต์ ต้องวางไว้นอก FocusArea
ทั้งหมด แต่ละหน้าต่างต้องมี FocusParkingView
หนึ่งรายการ หากคุณใช้เลย์เอาต์พื้นฐานของ 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 จะปรับมุมมองใหม่ในหน้าต่างนั้น ซึ่งทำให้หน้าต่างสองบานถูกโฟกัสพร้อมกัน การเพิ่ม
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
มักจะไม่เป็นเช่นนั้น มุมมองที่คลิกได้จะโฟกัสได้โดยอัตโนมัติและมุมมองจะคลิกได้โดยอัตโนมัติเมื่อมี Listener การคลิก หากตรรกะอัตโนมัตินี้ส่งผลให้เกิดความสามารถในการโฟกัสที่ต้องการ คุณไม่จำเป็นต้องตั้งค่าความสามารถในการโฟกัสของมุมมองอย่างชัดเจน หากตรรกะอัตโนมัติไม่ส่งผลให้มีความสามารถในการโฟกัสที่ต้องการ ให้ตั้งค่าแอตทริบิวต์ android:focusable
true
หรือ false
หรือตั้งค่าความสามารถในการโฟกัสของมุมมองโดยทางโปรแกรมด้วย View.setFocusable(boolean)
เพื่อให้ RotaryService
มุ่งเน้น มุมมองต้องเป็นไปตามข้อกำหนดต่อไปนี้:
- โฟกัสได้
- เปิดใช้งาน
- มองเห็นได้
- มีค่าความกว้างและความสูงที่ไม่ใช่ศูนย์
ถ้ามุมมองไม่ตรงตามข้อกำหนดเหล่านี้ทั้งหมด เช่น ปุ่มที่โฟกัสได้ แต่ปิดใช้งาน ผู้ใช้จะไม่สามารถใช้ตัวควบคุมแบบหมุนเพื่อโฟกัสได้ หากคุณต้องการเน้นที่มุมมองที่ปิดใช้งาน ให้พิจารณาใช้สถานะที่กำหนดเองแทน android:state_enabled
เพื่อควบคุมลักษณะที่ปรากฏโดยไม่ระบุว่า Android ควรพิจารณาให้ปิดใช้งาน แอปของคุณสามารถแจ้งให้ผู้ใช้ทราบว่าเหตุใดจึงปิดใช้งานมุมมองเมื่อแตะ ส่วนถัดไปจะอธิบายวิธีการดำเนินการนี้
สถานะที่กำหนดเอง
ในการเพิ่มสถานะที่กำหนดเอง:
- เพื่อเพิ่ม แอตทริบิวต์ที่กำหนดเอง ให้กับมุมมองของคุณ ตัวอย่างเช่น ในการเพิ่มสถานะแบบกำหนดเอง
state_rotary_enabled
ให้กับคลาสมุมมองCustomView
ให้ใช้:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- หากต้องการติดตามสถานะนี้ ให้เพิ่มตัวแปรอินสแตนซ์ในมุมมองของคุณพร้อมกับเมธอด accessor:
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()
โฟกัสพื้นที่
ใช้ 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
จะย้ายโฟกัสไปยัง View อื่นที่สามารถโฟกัสในFocusArea
เดียวกันได้ - เมื่อได้รับเหตุการณ์สะกิด
RotaryService
จะย้ายโฟกัสไปที่มุมมองอื่นที่สามารถโฟกัสในอีกFocusArea
อื่น (โดยทั่วไปจะอยู่ติดกัน)
หากคุณไม่ได้รวม FocusAreas
ใดๆ ไว้ในเลย์เอาต์ของคุณ มุมมองรูทจะถือเป็นพื้นที่โฟกัสโดยปริยาย ผู้ใช้ไม่สามารถสะกิดเพื่อไปยังส่วนต่างๆ ในแอปได้ แต่จะหมุนเวียนไปตามมุมมองที่โฟกัสได้ทั้งหมดแทน ซึ่งอาจเพียงพอสำหรับกล่องโต้ตอบ
การปรับแต่งพื้นที่โฟกัส
สามารถใช้แอตทริบิวต์ View มาตรฐานสองรายการเพื่อปรับแต่งการนำทางแบบหมุนได้:
-
android:nextFocusForward
อนุญาตให้นักพัฒนาแอประบุลำดับการหมุนในพื้นที่โฟกัส นี่เป็นแอตทริบิวต์เดียวกับที่ใช้ควบคุมลำดับแท็บสำหรับการนำทางด้วยแป้นพิมพ์ อย่า ใช้แอตทริบิวต์นี้เพื่อสร้างการวนซ้ำ ให้ใช้app:wrapAround
(ดูด้านล่าง) เพื่อสร้างลูปแทน -
android:focusedByDefault
ช่วยให้นักพัฒนาแอประบุมุมมองโฟกัสเริ่มต้นในหน้าต่าง อย่า ใช้แอตทริบิวต์นี้และapp:defaultFocus
(ดูด้านล่าง) ในFocusArea
เดียวกัน
FocusArea
ยังกำหนดคุณลักษณะบางอย่างเพื่อปรับแต่งการนำทางแบบหมุน ไม่สามารถกำหนดพื้นที่โฟกัสโดยนัยด้วยแอตทริบิวต์เหล่านี้ได้
- ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
app:defaultFocus
สามารถใช้เพื่อระบุ ID ของมุมมองลูกหลานที่โฟกัสได้ ซึ่งควรเน้นเมื่อผู้ใช้สะกิดไปยังFocusArea
นี้ - ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
app:defaultFocusOverridesHistory
สามารถตั้งค่าtrue
เพื่อให้มุมมองที่ระบุด้านบนมีโฟกัส แม้ว่าจะมีการเน้นไปที่ประวัติเพื่อระบุมุมมองอื่นในFocusArea
นี้ก็ตาม - ( แอนดรอยด์ 12 )
ใช้app:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
และapp:nudgeDownShortcut
เพื่อระบุ ID ของมุมมองลูกหลานที่โฟกัสได้ ซึ่งควรเน้นเมื่อผู้ใช้เขยิบไปในทิศทางที่กำหนด หากต้องการเรียนรู้เพิ่มเติม โปรดดูเนื้อหาสำหรับ ปุ่มลัดเขยิบ ด้านล่าง( Android 11 QPR3, Android 11 Car, เลิกใช้แล้วใน Android 12 )
app:nudgeShortcut
และapp:nudgeShortcutDirection
รองรับปุ่มลัดเขยิบเพียงอันเดียวเท่านั้น - ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
หากต้องการเปิดใช้งานการหมุนเพื่อล้อมรอบในFocusArea
นี้app:wrapAround
สามารถตั้งค่าtrue
โดยทั่วไปมักใช้เมื่อมีการจัดเรียงมุมมองเป็นวงกลมหรือวงรี - ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
หากต้องการปรับ padding ของไฮไลท์ในFocusArea
นี้ ให้ใช้app:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
และapp:highlightPaddingVertical
- ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
หากต้องการปรับขอบเขตที่รับรู้ของFocusArea
นี้เพื่อค้นหาเป้าหมายที่สะกิด ให้ใช้app:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
และapp:verticalBoundOffset
- ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
หากต้องการระบุ ID ของFocusArea
(หรือพื้นที่) ที่อยู่ติดกันอย่างชัดเจนในทิศทางที่กำหนด ให้ใช้app:nudgeLeft
,app:nudgeRight
,app:nudgeUp
และapp:nudgeDown
ใช้สิ่งนี้เมื่อการค้นหาทางเรขาคณิตที่ใช้โดยค่าเริ่มต้นไม่พบเป้าหมายที่ต้องการ
การสะกิดมักจะนำทางระหว่าง FocusAreas แต่ด้วยปุ่มลัดเขยิบ บางครั้งการสะกิดจะนำทางภายใน FocusArea
ดังนั้นผู้ใช้อาจต้องสะกิดสองครั้งเพื่อไปยัง FocusArea
ถัดไป ปุ่มลัดเขยิบมีประโยชน์เมื่อ FocusArea
มีรายการยาวตามด้วย ปุ่มการทำงานแบบลอย ดังตัวอย่างด้านล่าง:

หากไม่มีปุ่มลัดเขยิบ ผู้ใช้จะต้องหมุนดูรายการทั้งหมดเพื่อเข้าถึง FAB
ปรับแต่งเน้นไฮไลท์
ดังที่กล่าวไว้ข้างต้น RotaryService
สร้างขึ้นจากแนวคิดที่มีอยู่ของกรอบงาน Android ในการมุ่งเน้นมุมมอง เมื่อผู้ใช้หมุนและสะกิด RotaryService
จะย้ายจุดสนใจไปรอบๆ โดยเพ่งความสนใจไปที่มุมมองหนึ่งและไม่สนใจอีกมุมมองหนึ่ง ใน Android เมื่อมุมมองถูกโฟกัส หากมุมมอง:
- ได้ระบุไฮไลท์โฟกัสของตัวเอง Android ดึงไฮไลท์โฟกัสของมุมมอง
- ไม่ได้ระบุการเน้นโฟกัส และการเน้นโฟกัสเริ่มต้นจะไม่ถูกปิดใช้งาน Android จะดึงการเน้นโฟกัสเริ่มต้นสำหรับมุมมอง
แอปที่ออกแบบมาสำหรับการสัมผัสมักจะไม่ระบุไฮไลท์โฟกัสที่เหมาะสม
ไฮไลต์โฟกัสเริ่มต้นมีให้ในกรอบงาน Android และ OEM สามารถแทนที่ได้ นักพัฒนาแอปจะได้รับเมื่อธีมที่พวกเขาใช้นั้นมาจาก Theme.DeviceDefault
เพื่อประสบการณ์ของผู้ใช้ที่สอดคล้องกัน ให้พึ่งพาการเน้นโฟกัสเริ่มต้นทุกครั้งที่ทำได้ หากคุณต้องการไฮไลต์โฟกัสที่มีรูปร่างกำหนดเอง (เช่น ทรงกลมหรือเม็ดยา) หรือหากคุณใช้ธีมที่ไม่ได้มาจาก Theme.DeviceDefault
ให้ใช้ทรัพยากร car-ui-library เพื่อระบุไฮไลท์โฟกัสของคุณเอง แต่ละมุมมอง
เมื่อต้องการระบุการเน้นโฟกัสแบบกำหนดเองสำหรับมุมมอง ให้เปลี่ยนพื้นหลังหรือพื้นหน้าที่สามารถวาดได้ของมุมมองเป็นแบบวาดได้ ซึ่งจะแตกต่างออกไปเมื่อโฟกัสที่มุมมอง โดยปกติ คุณจะต้องเปลี่ยนพื้นหลัง Drawable ต่อไปนี้ หากใช้เป็นพื้นหลังสำหรับมุมมองสี่เหลี่ยม ให้ไฮไลต์โฟกัสแบบกลม:
<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 รถยนต์, Android 12 )
car_ui_rotary_focus_fill_secondary_color
car_ui_rotary_focus_stroke_secondary_color
- ( แอนดรอยด์ 12 )
car_ui_rotary_focus_pressed_fill_secondary_color
car_ui_rotary_focus_pressed_stroke_secondary_color
ตัวอย่างเช่น:
![]() | ![]() | |
เน้นไม่กด | โฟกัสกด |
การเลื่อนแบบหมุน
หากแอปของคุณใช้ RecyclerView
คุณควรใช้ CarUiRecyclerView
แทน เพื่อให้แน่ใจว่า UI ของคุณสอดคล้องกับส่วนอื่นๆ เนื่องจากการปรับแต่งของ OEM มีผลกับ CarUiRecyclerView
ทั้งหมด
หากองค์ประกอบในรายการของคุณสามารถโฟกัสได้ทั้งหมด คุณไม่จำเป็นต้องดำเนินการใดๆ การนำทางแบบหมุนจะย้ายโฟกัสผ่านองค์ประกอบในรายการ และรายการจะเลื่อนเพื่อทำให้องค์ประกอบที่โฟกัสใหม่มองเห็นได้
( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
หากมีองค์ประกอบที่สามารถโฟกัสได้และไม่สามารถโฟกัสได้ผสมกัน หรือหากองค์ประกอบทั้งหมดไม่สามารถโฟกัสได้ คุณสามารถเปิดใช้งานการเลื่อนแบบหมุนได้ ซึ่งช่วยให้ผู้ใช้สามารถใช้ตัวควบคุมแบบโรตารี่เพื่อค่อยๆ เลื่อนดูรายการโดยไม่ข้ามรายการที่โฟกัสไม่ได้ หากต้องการเปิดใช้งานการเลื่อนแบบหมุน ให้ตั้งค่าแอตทริบิวต์ app:rotaryScrollEnabled
true
( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
คุณสามารถเปิดใช้งานการเลื่อนแบบหมุนในมุมมองที่เลื่อนได้ รวมถึง av CarUiRecyclerView
ด้วย setRotaryScrollEnabled()
ใน CarUiUtils
หากคุณทำเช่นนั้น คุณต้อง:
- ทำให้มุมมองที่เลื่อนได้โฟกัสได้เพื่อให้สามารถโฟกัสได้เมื่อไม่มีมุมมองที่สืบทอดที่โฟกัสได้ปรากฏให้เห็น
- ปิดใช้งานการเน้นโฟกัสเริ่มต้นในมุมมองแบบเลื่อนได้โดยการเรียก
setDefaultFocusHighlightEnabled(false)
เพื่อให้มุมมองที่เลื่อนได้ไม่ปรากฏว่าโฟกัส - ตรวจสอบให้แน่ใจว่ามุมมองที่เลื่อนได้นั้นโฟกัสไปที่ส่วนต่อจากนี้ด้วยการเรียก
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
- ฟัง MotionEvents ด้วย
SOURCE_ROTARY_ENCODER
และAXIS_VSCROLL
หรือAXIS_HSCROLL
เพื่อระบุระยะทางที่จะเลื่อนและทิศทาง (ผ่านป้าย)
เมื่อเปิดใช้งานการเลื่อนแบบหมุนบน CarUiRecyclerView
และผู้ใช้หมุนไปยังพื้นที่ที่ไม่มีมุมมองที่สามารถโฟกัสได้ แถบเลื่อนจะเปลี่ยนจากสีเทาเป็นสีน้ำเงิน ราวกับว่าเพื่อระบุว่าแถบเลื่อนนั้นโฟกัสอยู่ คุณสามารถใช้เอฟเฟกต์ที่คล้ายกันได้หากต้องการ
MotionEvents เหมือนกับที่สร้างโดยล้อเลื่อนบนเมาส์ ยกเว้นแหล่งที่มา
โหมดการจัดการโดยตรง
โดยปกติ การสะกิดและการหมุนจะนำทางผ่านอินเทอร์เฟซผู้ใช้ ในขณะที่การกดปุ่มตรงกลางจะดำเนินการ แม้ว่าจะไม่ได้เป็นเช่นนั้นเสมอไป ตัวอย่างเช่น หากผู้ใช้ต้องการปรับระดับเสียงการเตือน พวกเขาอาจใช้ตัวควบคุมแบบหมุนเพื่อไปยังแถบเลื่อนระดับเสียง กดปุ่มกลาง หมุนตัวควบคุมเพื่อปรับระดับเสียงของการเตือน จากนั้นกดปุ่มย้อนกลับเพื่อกลับสู่การนำทาง . นี่เรียกว่าโหมด การจัดการโดยตรง (DM) ในโหมดนี้ ตัวควบคุมแบบโรตารี่ใช้เพื่อโต้ตอบกับมุมมองโดยตรง แทนที่จะใช้เพื่อนำทาง
ใช้ DM ด้วยวิธีใดวิธีหนึ่งจากสองวิธี หากคุณต้องการจัดการการหมุนเท่านั้น และมุมมองที่คุณต้องการจัดการตอบสนองต่อ ACTION_SCROLL_FORWARD
และ ACTION_SCROLL_BACKWARD
AccessibilityEvent
อย่างเหมาะสม ให้ใช้กลไก ง่ายๆ มิฉะนั้น ให้ใช้กลไก ขั้นสูง
กลไกที่เรียบง่ายเป็นตัวเลือกเดียวในหน้าต่างระบบ แอพสามารถใช้กลไกใดก็ได้
กลไกง่ายๆ
( Android 11 QPR3, Android 11 รถยนต์, 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()
- ลงทะเบียน
- ควรฟังเหตุการณ์เขยิบ (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
หรือKEYCODE_DPAD_RIGHT
) หากมุมมองควรจัดการกับการกระตุ้นเตือน - ควรฟัง
MotionEvent
และรับจำนวนการหมุนในAXIS_SCROLL
หากมุมมองต้องการจัดการการหมุน มีหลายวิธีในการทำเช่นนี้:- ลงทะเบียน
OnGenericMotionListener
- ขยายมุมมองและแทนที่เมธอด
dispatchTouchEvent()
- ลงทะเบียน
- เพื่อหลีกเลี่ยงไม่ให้ติดอยู่ในโหมด DM ต้องออกจากโหมด DM เมื่อ Fragment หรือ Activity ที่เป็นของมุมมองไม่โต้ตอบ
- ควรจัดให้มีสัญญาณภาพเพื่อระบุว่ามุมมองอยู่ในโหมด 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
ไม่ควรโฟกัสได้ - ( Android 11 QPR3, Android 11 Car, เลิกใช้งานใน Android 11 )
เนื้อหาของActivityView
ต้องมีFocusParkingView
เป็นมุมมองแรกที่โฟกัสได้ และแอตทริบิวต์app:shouldRestoreFocus
ต้องเป็นfalse
- เนื้อหาของ
ActivityView
ไม่ควรมีมุมมองandroid:focusByDefault
สำหรับผู้ใช้ ActivityViews ไม่ควรมีผลกับการนำทาง ยกเว้นว่าพื้นที่โฟกัสไม่สามารถขยาย ActivityViews กล่าวคือ คุณไม่สามารถมีพื้นที่โฟกัสเดียวที่มีเนื้อหาภายใน และ ภายนอก ActivityView
หากคุณไม่ได้เพิ่ม FocusAreaas ใดๆ ให้กับ 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
ดำเนินการ (เช่น การกรอกลับ) และกำหนดเวลาให้ทำงานเองหลังจากเกิดความล่าช้า
โหมดสัมผัส
ผู้ใช้สามารถใช้ตัวควบคุมแบบโรตารี่เพื่อโต้ตอบกับเฮดยูนิตในรถยนต์ได้สองวิธี ทั้งโดยใช้ตัวควบคุมแบบโรตารี่หรือโดยการสัมผัสหน้าจอ เมื่อใช้ตัวควบคุมแบบหมุน หนึ่งในมุมมองที่สามารถโฟกัสได้จะถูกเน้นไว้ เมื่อสัมผัสหน้าจอ จะไม่มีไฮไลท์โฟกัสปรากฏขึ้น ผู้ใช้สามารถสลับระหว่างโหมดอินพุตเหล่านี้ได้ตลอดเวลา:
- โรตารี → สัมผัส เมื่อผู้ใช้สัมผัสหน้าจอ ไฮไลท์โฟกัสจะหายไป
- สัมผัส → หมุน เมื่อผู้ใช้สะกิด หมุน หรือกดปุ่มตรงกลาง ไฮไลท์โฟกัสจะปรากฏขึ้น
ปุ่ม Back และ Home จะไม่มีผลกับโหมดป้อนข้อมูล
หมุน piggybacks บนแนวคิดที่มีอยู่ของ Android ใน โหมดสัมผัส คุณสามารถใช้ View.isInTouchMode()
เพื่อกำหนดโหมดอินพุตที่ผู้ใช้ใช้ คุณสามารถใช้ OnTouchModeChangeListener
เพื่อฟังการเปลี่ยนแปลงได้ แม้ว่าจะใช้เพื่อปรับแต่งอินเทอร์เฟซผู้ใช้ของคุณสำหรับโหมดอินพุตปัจจุบัน ให้หลีกเลี่ยงการเปลี่ยนแปลงที่สำคัญเนื่องจากอาจทำให้สับสนได้
การแก้ไขปัญหา
ในแอปที่ออกแบบมาเพื่อการสัมผัส ไม่ใช่เรื่องแปลกที่จะมีมุมมองที่โฟกัสได้ซ้อนกันอยู่ ตัวอย่างเช่น อาจมี FrameLayout
รอบ ImageButton
ซึ่งทั้งคู่สามารถโฟกัสได้ การดำเนินการนี้ไม่เป็นอันตรายต่อการสัมผัส แต่อาจส่งผลให้ผู้ใช้โรตารีได้รับประสบการณ์ที่ไม่ดี เนื่องจากผู้ใช้ต้องหมุนตัวควบคุมสองครั้งเพื่อย้ายไปยังมุมมองแบบโต้ตอบถัดไป เพื่อประสบการณ์การใช้งานที่ดี Google แนะนำให้คุณทำให้มุมมองภายนอกหรือมุมมองภายในโฟกัสได้ แต่ไม่ใช่ทั้งสองอย่าง
หากปุ่มหรือสวิตช์สูญเสียโฟกัสเมื่อกดผ่านตัวควบคุมแบบโรตารี่ อาจใช้เงื่อนไขใดเงื่อนไขหนึ่งต่อไปนี้:
- ปุ่มหรือสวิตช์ถูกปิดใช้งาน (สั้น ๆ หรือไม่มีกำหนด) เนื่องจากการกดปุ่ม ไม่ว่าในกรณีใด มีสองวิธีในการแก้ไขปัญหานี้:
- ปล่อยให้สถานะ
android:enabled
เป็นtrue
และใช้สถานะที่กำหนดเองเพื่อทำให้ปุ่มหรือสวิตช์เป็นสีเทาตามที่อธิบายไว้ใน Custom State - ใช้คอนเทนเนอร์ล้อมรอบปุ่มหรือสวิตช์และทำให้คอนเทนเนอร์โฟกัสได้แทนปุ่มหรือสวิตช์ (ตัวฟังการคลิกต้องอยู่บนคอนเทนเนอร์)
- ปล่อยให้สถานะ
- กำลังเปลี่ยนปุ่มหรือสวิตช์ ตัวอย่างเช่น การดำเนินการที่ดำเนินการเมื่อกดปุ่มหรือสวิตช์ถูกสลับอาจทำให้การรีเฟรชของการทำงานที่มีอยู่ทำให้ปุ่มใหม่เข้ามาแทนที่ปุ่มที่มีอยู่ มีสองวิธีในการแก้ไขปัญหานี้:
- แทนที่จะสร้างปุ่มหรือสวิตช์ใหม่ ให้ตั้งค่าไอคอนและ/หรือข้อความของปุ่มหรือสวิตช์ที่มีอยู่
- ด้านบน ให้เพิ่มคอนเทนเนอร์ที่โฟกัสได้รอบๆ ปุ่มหรือสวิตช์
สนามเด็กเล่นโรตารี
RotaryPlayground
เป็นแอพอ้างอิงสำหรับการหมุน ใช้เพื่อเรียนรู้วิธีผสานรวมคุณสมบัติแบบโรตารี่เข้ากับแอพของคุณ RotaryPlayground
รวมอยู่ในอีมูเลเตอร์บิลด์และบิลด์สำหรับอุปกรณ์ที่ใช้ Android Automotive OS (AAOS)
- พื้นที่เก็บข้อมูล
RotaryPlayground
:packages/apps/Car/tests/RotaryPlayground/
- รุ่น: Android 11 QPR3, Android 11 Car และ Android 12
แอป RotaryPlayground
แสดงแท็บต่อไปนี้ทางด้านซ้าย:
- การ์ด. ทดสอบการนำทางรอบๆ พื้นที่โฟกัส ข้ามองค์ประกอบที่ไม่สามารถโฟกัสได้และการป้อนข้อความ
- การจัดการโดยตรง วิดเจ็ตทดสอบที่รองรับโหมดการจัดการโดยตรงขั้นสูงที่เรียบง่ายและล้ำสมัย แท็บนี้มีไว้สำหรับการจัดการโดยตรงภายในหน้าต่างแอปโดยเฉพาะ
- การจัดการ UI ของ Sys วิดเจ็ตทดสอบที่รองรับการจัดการโดยตรงในหน้าต่างระบบที่รองรับเฉพาะโหมดการจัดการโดยตรงอย่างง่ายเท่านั้น
- กริด. ทดสอบการนำทางแบบหมุนรูปแบบ z ด้วยการเลื่อน
- การแจ้งเตือน ทดสอบการเขยิบเข้าและออกจากการแจ้งเตือนล่วงหน้า
- เลื่อน. ทดสอบการเลื่อนผ่านเนื้อหาที่โฟกัสได้และไม่สามารถโฟกัสได้ผสมกัน
- เว็บวิว. ทดสอบการนำทางผ่านลิงก์ใน
WebView
-
FocusArea
กำหนดเอง ทดสอบการปรับแต่งFocusArea
:- ห่อรอบ ๆ.
-
android:focusedByDefault
และapp:defaultFocus
. - เขยิบเป้าหมายที่ชัดเจน
- ปุ่มลัดเขยิบ
-
FocusArea
ที่ไม่มีมุมมองที่สามารถโฟกัสได้
เอกสารต่อไปนี้มีไว้สำหรับนักพัฒนาแอป
เพื่อให้แอปของคุณรองรับแบบหมุน คุณต้อง:
- วาง
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
ในการเข้าถึงตัวควบคุมโรตารีจำลอง:
- แตะที่จุดสามจุดที่ด้านล่างของแถบเครื่องมือ:
รูปที่ 1. เข้าถึงตัวควบคุมโรตารี่จำลอง - เลือก Carrotation ในหน้าต่างการควบคุมแบบขยาย:
รูปที่ 2 เลือกรถแบบหมุน
แป้นพิมพ์ USB
- เสียบแป้นพิมพ์ USB เข้ากับอุปกรณ์ของคุณที่ใช้ Android Automotive OS (AAOS) ในบางกรณี การทำเช่นนี้อาจทำให้แป้นพิมพ์บนหน้าจอไม่ปรากฏขึ้น
- ใช้
userdebug
หรือeng
build - เปิดใช้งานการกรองเหตุการณ์สำคัญ:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- ดูตารางด้านล่างเพื่อค้นหาคีย์ที่เกี่ยวข้องสำหรับแต่ละการกระทำ:
สำคัญ การกระทำของโรตารี คิว หมุนทวนเข็มนาฬิกา อี หมุนตามเข็มนาฬิกา อา เขยิบไปทางซ้าย ดี เขยิบขวา 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 ms ที่แล้วและ 50 ms ที่แล้ว) |
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
เป็นมุมมองที่โปร่งใสใน Car UI Library (car-ui-library) RotaryService
ใช้เพื่อสนับสนุนการนำทางตัวควบคุมแบบโรตารี่ FocusParkingView
ต้องเป็นมุมมองที่โฟกัสได้อันแรกในเลย์เอาต์ ต้องวางไว้นอก FocusArea
ทั้งหมด แต่ละหน้าต่างต้องมี FocusParkingView
หนึ่งรายการ หากคุณใช้เลย์เอาต์พื้นฐานของ 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 จะปรับมุมมองใหม่ในหน้าต่างนั้น ซึ่งทำให้หน้าต่างสองบานถูกโฟกัสพร้อมกัน การเพิ่ม
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
มักจะไม่เป็นเช่นนั้น มุมมองที่คลิกได้จะโฟกัสได้โดยอัตโนมัติและมุมมองจะคลิกได้โดยอัตโนมัติเมื่อมี Listener การคลิก หากตรรกะอัตโนมัตินี้ส่งผลให้เกิดความสามารถในการโฟกัสที่ต้องการ คุณไม่จำเป็นต้องตั้งค่าความสามารถในการโฟกัสของมุมมองอย่างชัดเจน หากตรรกะอัตโนมัติไม่ส่งผลให้มีความสามารถในการโฟกัสที่ต้องการ ให้ตั้งค่าแอตทริบิวต์ android:focusable
true
หรือ false
หรือตั้งค่าความสามารถในการโฟกัสของมุมมองโดยทางโปรแกรมด้วย View.setFocusable(boolean)
เพื่อให้ RotaryService
มุ่งเน้น มุมมองต้องเป็นไปตามข้อกำหนดต่อไปนี้:
- โฟกัสได้
- เปิดใช้งาน
- มองเห็นได้
- มีค่าความกว้างและความสูงที่ไม่ใช่ศูนย์
ถ้ามุมมองไม่ตรงตามข้อกำหนดเหล่านี้ทั้งหมด เช่น ปุ่มที่โฟกัสได้ แต่ปิดใช้งาน ผู้ใช้จะไม่สามารถใช้ตัวควบคุมแบบหมุนเพื่อโฟกัสได้ หากคุณต้องการเน้นที่มุมมองที่ปิดใช้งาน ให้พิจารณาใช้สถานะที่กำหนดเองแทน android:state_enabled
เพื่อควบคุมลักษณะที่ปรากฏโดยไม่ระบุว่า Android ควรพิจารณาให้ปิดใช้งาน แอปของคุณสามารถแจ้งให้ผู้ใช้ทราบว่าเหตุใดจึงปิดใช้งานมุมมองเมื่อแตะ ส่วนถัดไปจะอธิบายวิธีการดำเนินการนี้
สถานะที่กำหนดเอง
ในการเพิ่มสถานะที่กำหนดเอง:
- เพื่อเพิ่ม แอตทริบิวต์ที่กำหนดเอง ให้กับมุมมองของคุณ ตัวอย่างเช่น ในการเพิ่มสถานะแบบกำหนดเอง
state_rotary_enabled
ให้กับคลาสมุมมองCustomView
ให้ใช้:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- หากต้องการติดตามสถานะนี้ ให้เพิ่มตัวแปรอินสแตนซ์ในมุมมองของคุณพร้อมกับเมธอด accessor:
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()
โฟกัสพื้นที่
ใช้ 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
ทำงานดังนี้:
- When handling rotate and nudge actions,
RotaryService
looks for instances ofFocusArea
in the view hierarchy. - When receiving a rotation event,
RotaryService
moves focus to another View that can take focus in the sameFocusArea
. - When receiving a nudge event,
RotaryService
move focus to another view that can take focus in another (typically adjacent)FocusArea
.
If you don't include any FocusAreas
in your layout, the root view is treated as an implicit focus area. The user can't nudge to navigate in the app. Instead, they'll rotate through all focusable views, which may be adequate for dialogs.
FocusArea customization
Two standard View attributes can be used to customize rotary navigation:
-
android:nextFocusForward
allows app developers to specify the rotation order in a focus area. This is the same attribute used to control the Tab order for keyboard navigation. Do NOT use this attribute to create a loop. Instead, useapp:wrapAround
(see below) to create a loop. -
android:focusedByDefault
allows app developers to specify the default focus view in the window. Do NOT use this attribute andapp:defaultFocus
(see below) in the sameFocusArea
.
FocusArea
also defines some attributes to customize rotary navigation. Implicit focus areas can't be customized with these attributes.
- ( Android 11 QPR3, Android 11 Car, Android 12 )
app:defaultFocus
can be used to specify the ID of a focusable descendant view, which should be focused on when the user nudges to thisFocusArea
. - ( Android 11 QPR3, Android 11 Car, Android 12 )
app:defaultFocusOverridesHistory
can be set totrue
to make the view specified above take focus even if with history to indicate another view in thisFocusArea
had been focused on. - ( Android 12 )
Useapp:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
, andapp:nudgeDownShortcut
to specify the ID of a focusable descendant view, which should be focused on when the user nudges in a given direction. To learn more, see the content for nudge shortcuts below.( Android 11 QPR3, Android 11 Car, deprecated in Android 12 )
app:nudgeShortcut
andapp:nudgeShortcutDirection
supported only one nudge shortcut. - ( Android 11 QPR3, Android 11 Car, Android 12 )
To enable rotation to wrap around in thisFocusArea
,app:wrapAround
can be set totrue
. This is most typically used when views are arranged in a circle or oval. - ( Android 11 QPR3, Android 11 Car, Android 12 )
To adjust the padding of the highlight in thisFocusArea
, useapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
, andapp:highlightPaddingVertical
. - ( Android 11 QPR3, Android 11 Car, Android 12 )
To adjust the perceived bounds of thisFocusArea
to find a nudge target, useapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
, andapp:verticalBoundOffset
. - ( Android 11 QPR3, Android 11 Car, Android 12 )
To explicitly specify the ID of an adjacentFocusArea
(or areas) in the given directions, useapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
, andapp:nudgeDown
. Use this when the geometric search used by default doesn't find the desired target.
Nudging usually navigates between FocusAreas. But with nudge shortcuts, nudging sometimes first navigates within a FocusArea
so that the user may need to nudge twice to navigate to the next FocusArea
. Nudge shortcuts are useful when a FocusArea
contains a long list followed by a Floating Action Button , as in the example below:

Without the nudge shortcut, the user would have to rotate through the entire list to reach the FAB.
Focus highlight customization
As noted above, RotaryService
builds upon the Android framework's existing concept of view focus. When the user rotates and nudges, RotaryService
moves the focus around, focusing one view and unfocusing another. In Android, when a view is focused, if the view:
- has specified its own focus highlight, Android draws the view's focus highlight.
- doesn't specify a focus highlight, and the default focus highlight is not disabled, Android draws the default focus highlight for the view.
Apps designed for touch usually don't specify the appropriate focus highlights.
The default focus highlight is provided by the Android framework and can be overridden by the OEM. App developers receive it when the theme they're using is derived from Theme.DeviceDefault
.
For a consistent user experience, rely on the default focus highlight whenever possible. If you need a custom-shaped (for example, round or pill-shaped) focus highlight, or if you're using a theme not derived from Theme.DeviceDefault
, use the car-ui-library resources to specify your own focus highlight for each view.
To specify a custom focus highlight for a view, change the background or foreground drawable of the view to a drawable that differs when the view is focused on. Typically, you'd change the background. The following drawable, if used as the background for a square view, produces a round focus highlight:
<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 ) Bold resource references in the sample above identify resources defined by the car-ui-library. The OEM overrides these to be consistent with the default focus highlight they specify. This ensures that the focus highlight color, stroke width, and so on don't change when the user navigates between a view with a custom focus highlight and a view with the default focus highlight. The last item is a ripple used for touch. Default values used for the bold resources appear as follows:

In addition, a custom focus highlight is called for when a button is given a solid background color to bring it to the user's attention, as in the example below. This can make the focus highlight difficult to see. In this situation, specify a custom focus highlight using secondary colors:
![]() |
- ( 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
ตัวอย่างเช่น:
![]() | ![]() | |
Focused, not pressed | Focused, pressed |
Rotary scrolling
If your app uses RecyclerView
s, you SHOULD use CarUiRecyclerView
s instead. This ensures that your UI is consistent with others because an OEM's customization applies to all CarUiRecyclerView
s.
If the elements in your list are all focusable, you needn't do anything else. Rotary navigation moves the focus through the elements in the list and the list scrolls to make the newly focused element visible.
( Android 11 QPR3, Android 11 Car, Android 12 )
If there is a mix of focusable and unfocusable elements, or if all the elements are unfocusable, you can enable rotary scrolling, which allows the user to use the rotary controller to gradually scroll through the list without skipping unfocusable items. To enable rotary scrolling, set the app:rotaryScrollEnabled
attribute to true
.
( Android 11 QPR3, Android 11 Car, Android 12 )
You can enable rotary scrolling in any scrollable view, including av CarUiRecyclerView
, with the setRotaryScrollEnabled()
method in CarUiUtils
. If you do so, you need to:
- Make the scrollable view focusable so that it can be focused on when none of its focusable descendant views are visible,
- Disable the default focus highlight on the scrollable view by calling
setDefaultFocusHighlightEnabled(false)
so that the scrollable view doesn't appear to be focused, - Ensure that the scrollable view is focused on before its descendants by calling
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Listen for MotionEvents with
SOURCE_ROTARY_ENCODER
and eitherAXIS_VSCROLL
orAXIS_HSCROLL
to indicate the distance to scroll and the direction (through the sign).
When rotary scrolling is enabled on a CarUiRecyclerView
and the user rotates to an area where no focusable views are present, the scrollbar changes from gray to blue, as if to indicate the scrollbar is focused. You can implement a similar effect if you like.
The MotionEvents are the same as those generated by a scroll wheel on a mouse, except for the source.
Direct manipulation mode
Normally, nudges and rotation navigate through the user interface, while Center button presses take action, though this isn't always the case. For example, if a user wants to adjust the alarm volume, they might use the rotary controller to navigate to the volume slider, press the Center button, rotate the controller to adjust the alarm volume, and then press the Back button to return to navigation. This is referred to as direct manipulation (DM) mode. In this mode, the rotary controller is used to interact with the view directly rather than to navigate.
Implement DM in one of two ways. If you only need to handle rotation and the view you want to manipulate responds to ACTION_SCROLL_FORWARD
and ACTION_SCROLL_BACKWARD
AccessibilityEvent
s appropriately, use the simple mechanism. Otherwise, use the advanced mechanism.
The simple mechanism is the only option in system windows; apps can use either mechanism.
Simple mechanism
( Android 11 QPR3, Android 11 Car, Android 12 )
Your app should call DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
. RotaryService
recognizes when the user is in DM mode and enters DM mode when the user presses the Center button while a view is focused. When in DM mode, rotations perform ACTION_SCROLL_FORWARD
or ACTION_SCROLL_BACKWARD
and exits DM mode when the user presses the Back button. The simple mechanism toggles the selected state of the view when entering and exiting DM mode.
To provide a visual cue that the user is in DM mode, make your view appear different when selected. For example, change the background when android:state_selected
is true
.
Advanced mechanism
The app determines when RotaryService
enters and exits DM mode. For a consistent user experience, pressing the Center button with a DM view focused should enter DM mode and the Back button should exit DM mode. If the Center button and/or nudge aren't used, they can be alternative ways to exit DM mode. For apps such as Maps, a button to represent DM can be used to enter DM mode.
To support advanced DM mode, a view:
- ( Android 11 QPR3, Android 11 Car, Android 12 ) MUST listen for a
KEYCODE_DPAD_CENTER
event to enter DM mode and listen for aKEYCODE_BACK
event to exit DM mode, callingDirectManipulationHelper.enableDirectManipulationMode()
in each case. To listen for these events, do one of the following:- Register an
OnKeyListener
. or, - Extend the view and then override its
dispatchKeyEvent()
method.
- Register an
- SHOULD listen for nudge events (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
, orKEYCODE_DPAD_RIGHT
) if the view should handle nudges. - SHOULD listen to
MotionEvent
s and get rotation count inAXIS_SCROLL
if the view wants to handle rotation. There are several ways to do this:- Register an
OnGenericMotionListener
. - Extend the view and override its
dispatchTouchEvent()
method.
- Register an
- To avoid being stuck in DM mode, MUST exit DM mode when the Fragment or Activity the view belongs to is not interactive.
- SHOULD provide a visual cue to indicate that the view is in DM mode.
A sample of a custom view that uses DM mode to pan and zoom a map is provided below:
/** 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(); }
More examples can be found in the RotaryPlayground
project.
ActivityView
When using an ActivityView:
- The
ActivityView
should not be focusable. - ( Android 11 QPR3, Android 11 Car, deprecated in Android 11 )
The contents of theActivityView
MUST contain aFocusParkingView
as the first focusable view, and itsapp:shouldRestoreFocus
attribute MUST befalse
. - The contents of the
ActivityView
should have noandroid:focusByDefault
views.
For the user, ActivityViews should have no effect on navigation except that focus areas can't span ActivityViews. In other words, you can't have a single focus area that has content inside and outside an ActivityView
. If you don't add any FocusAreas to your ActivityView
, the root of the view hierarchy in the ActivityView
is considered an implicit focus area.
Buttons that operate when held down
Most buttons cause some action when clicked. Some buttons operate when held down instead. For example, the Fast Forward and Rewind buttons typically operate when held down. To make such buttons support rotary, listen for KEYCODE_DPAD_CENTER
KeyEvents
as follows:
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; });
In which mRunnable
takes an action (such as rewinding) and schedules itself to be run after a delay.
Touch mode
Users can use a rotary controller to interact with the head unit in a car in two ways, either by using the rotary controller or by touching the screen. When using the rotary controller, one of the focusable views will be highlighted. When touching the screen, no focus highlight appears. The user can switch between these input modes at any time:
- Rotary → touch. When the user touches the screen, the focus highlight disappears.
- Touch → rotary. When the user nudges, rotates, or presses the Center button, the focus highlight appears.
The Back and Home buttons have no effect on the input mode.
Rotary piggybacks on Android's existing concept of touch mode . You can use View.isInTouchMode()
to determine which input mode the user is using. You can use OnTouchModeChangeListener
to listen for changes. While this can be used to customize your user interface for the current input mode, avoid any major changes as they can be disconcerting.
การแก้ไขปัญหา
In an app designed for touch, it's not uncommon to have nested focusable views. For example, there may be a FrameLayout
around an ImageButton
, both of which are focusable. This does no harm for touch but it can result in a poor user experience for rotary because the user must rotate the controller twice to move to the next interactive view. For a good user experience, Google recommends you make either the outer view or the inner view focusable, but not both.
If a button or switch loses focus when pressed through the rotary controller, one of these conditions may apply:
- The button or switch is being disabled (briefly or indefinitely) due to the button being pressed. In either case, there are two ways to address this:
- Leave the
android:enabled
state astrue
and use a custom state to gray out the button or switch as described in Custom State . - Use a container to surround the button or switch and make the container focusable instead of the button or switch. (The click listener must be on the container.)
- Leave the
- The button or switch is being replaced. For example, the action taken when the button is pressed or the switch is toggled may trigger a refresh of the available actions causing new buttons to replace existing buttons. There are two ways to address this:
- Instead of creating a new button or switch, set the icon and/or text of the existing button or switch.
- As above, add a focusable container around the button or switch.
RotaryPlayground
RotaryPlayground
is a reference app for rotary. Use it to learn how to integrate rotary features into your apps. RotaryPlayground
is included in emulator builds and in builds for devices that run Android Automotive OS (AAOS).
-
RotaryPlayground
repository:packages/apps/Car/tests/RotaryPlayground/
- Versions: Android 11 QPR3, Android 11 Car, and Android 12
The RotaryPlayground
app shows the following tabs on the left:
- Cards. Test navigating around focus areas, skipping unfocusable elements and text input.
- Direct Manipulation. Test widgets that support simple and advanced direct manipulation mode. This tab is specifically for direct manipulation within the app window.
- Sys UI Manipulation. Test widgets that support direct manipulation in system windows where only simple direct manipulation mode is supported.
- Grid. Test z-pattern rotary navigation with scrolling.
- Notification. Test nudging in and out of heads-up notifications.
- Scroll. Test scrolling through a mix of focusable and unfocusable content.
- WebView. Test navigating through links in a
WebView
. - Custom
FocusArea
. TestFocusArea
customization:- Wrap-around.
-
android:focusedByDefault
andapp:defaultFocus
. - Explicit nudge targets.
- Nudge shortcuts.
-
FocusArea
with no focusable views.