กำลังพัฒนาแอพ

เอกสารต่อไปนี้มีไว้สำหรับนักพัฒนาแอป

เพื่อให้แอปของคุณรองรับแบบหมุน คุณต้อง:

  1. วาง FocusParkingView ในรูปแบบกิจกรรมที่เกี่ยวข้อง
  2. ตรวจสอบให้แน่ใจว่ามุมมองที่สามารถโฟกัสได้ (หรือไม่ใช่)
  3. ใช้ 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. แตะที่จุดสามจุดที่ด้านล่างของแถบเครื่องมือ:

    เข้าถึงตัวควบคุมโรตารี่จำลอง
    รูปที่ 1. เข้าถึงตัวควบคุมโรตารี่จำลอง
  2. เลือก 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 :

  1. Android ไม่ล้างโฟกัสโดยอัตโนมัติเมื่อตั้งค่าโฟกัสในหน้าต่างอื่น หากคุณพยายามล้างโฟกัสในหน้าต่างก่อนหน้า Android จะปรับมุมมองใหม่ในหน้าต่างนั้น ซึ่งทำให้หน้าต่างสองบานถูกโฟกัสพร้อมกัน การเพิ่ม FocusParkingView ลงในแต่ละหน้าต่างสามารถแก้ไขปัญหานี้ได้ มุมมองนี้โปร่งใสและไฮไลต์โฟกัสเริ่มต้นถูกปิดใช้งาน เพื่อให้ผู้ใช้มองไม่เห็นไม่ว่าจะโฟกัสหรือไม่ก็ตาม มันสามารถโฟกัสได้เพื่อให้ RotaryService สามารถโฟกัส ที่ จุดนั้นเพื่อเอาไฮไลท์โฟกัสออก
  2. หากมี FocusArea เพียงอันเดียวในหน้าต่างปัจจุบัน การหมุนตัวควบคุมใน FocusArea จะทำให้ RotaryService ย้ายโฟกัสจากมุมมองทางด้านขวาไปยังมุมมองทางด้านซ้าย (และในทางกลับกัน) การเพิ่มมุมมองนี้ในแต่ละหน้าต่างสามารถแก้ไขปัญหาได้ เมื่อ RotaryService กำหนดเป้าหมายโฟกัสคือ FocusParkingView จะสามารถระบุได้ว่าจุดตัดรอบที่กำลังจะเกิดขึ้น ณ จุดที่หลีกเลี่ยงจุดโฟกัสโดยไม่ให้โฟกัสเคลื่อนที่
  3. เมื่อตัวควบคุมแบบหมุนเปิดแอปขึ้นมา 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 ควรพิจารณาให้ปิดใช้งาน แอปของคุณสามารถแจ้งให้ผู้ใช้ทราบว่าเหตุใดจึงปิดใช้งานมุมมองเมื่อแตะ ส่วนถัดไปจะอธิบายวิธีการดำเนินการนี้

สถานะที่กำหนดเอง

ในการเพิ่มสถานะที่กำหนดเอง:

  1. เพื่อเพิ่ม แอตทริบิวต์ที่กำหนดเอง ให้กับมุมมองของคุณ ตัวอย่างเช่น ในการเพิ่มสถานะแบบกำหนดเอง state_rotary_enabled ให้กับคลาสมุมมอง CustomView ให้ใช้:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. หากต้องการติดตามสถานะนี้ ให้เพิ่มตัวแปรอินสแตนซ์ในมุมมองของคุณพร้อมกับเมธอด accessor:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. วิธีอ่านค่าแอตทริบิวต์ของคุณเมื่อสร้างมุมมองของคุณ:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. ในคลาสมุมมองของคุณ ให้แทนที่ 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;
    }
    
  5. ทำให้ตัวจัดการการคลิกของข้อมูลพร็อพเพอร์ตี้ทำงานแตกต่างกันไปตามสถานะ ตัวอย่างเช่น ตัวจัดการการคลิกอาจไม่ทำอะไรเลย หรืออาจแสดงข้อความเตือนเมื่อ mRotaryEnabled เป็น false
  6. หากต้องการให้ปุ่มปรากฏว่าปิดใช้งาน ในพื้นหลังของมุมมองที่วาดได้ ให้ใช้ app:state_rotary_enabled แทน android:state_enabled หากคุณยังไม่มี คุณจะต้องเพิ่ม:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. หากมุมมองของคุณถูกปิดใช้งานในเลย์เอาต์ใดๆ ให้แทนที่ android:enabled="false" ด้วย app:state_rotary_enabled="false" แล้วเพิ่มเนมสเปซของ app ดังด้านบน
  8. หากมุมมองของคุณถูกปิดใช้งานโดยทางโปรแกรม ให้แทนที่การเรียก 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 ทำงานดังนี้:

  1. เมื่อจัดการกับการหมุนเวียนและสะกิด RotaryService จะค้นหาอินสแตนซ์ของ FocusArea ในลำดับชั้นของมุมมอง
  2. เมื่อได้รับเหตุการณ์การหมุนเวียน RotaryService จะย้ายโฟกัสไปยัง View อื่นที่สามารถโฟกัสใน FocusArea เดียวกันได้
  3. เมื่อได้รับเหตุการณ์สะกิด RotaryService จะย้ายโฟกัสไปที่มุมมองอื่นที่สามารถโฟกัสในอีก FocusArea อื่น (โดยทั่วไปจะอยู่ติดกัน)

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

การปรับแต่งพื้นที่โฟกัส

สามารถใช้แอตทริบิวต์ View มาตรฐานสองรายการเพื่อปรับแต่งการนำทางแบบหมุนได้:

  • android:nextFocusForward อนุญาตให้นักพัฒนาแอประบุลำดับการหมุนในพื้นที่โฟกัส นี่เป็นแอตทริบิวต์เดียวกับที่ใช้ควบคุมลำดับแท็บสำหรับการนำทางด้วยแป้นพิมพ์ อย่า ใช้แอตทริบิวต์นี้เพื่อสร้างการวนซ้ำ ให้ใช้ app:wrapAround (ดูด้านล่าง) เพื่อสร้างลูปแทน
  • android:focusedByDefault ช่วยให้นักพัฒนาแอประบุมุมมองโฟกัสเริ่มต้นในหน้าต่าง อย่า ใช้แอตทริบิวต์นี้และ app:defaultFocus (ดูด้านล่าง) ใน FocusArea เดียวกัน

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

  1. ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
    app:defaultFocus สามารถใช้เพื่อระบุ ID ของมุมมองลูกหลานที่โฟกัสได้ ซึ่งควรเน้นเมื่อผู้ใช้สะกิดไปยัง FocusArea นี้
  2. ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
    app:defaultFocusOverridesHistory สามารถตั้งค่า true เพื่อให้มุมมองที่ระบุด้านบนมีโฟกัส แม้ว่าจะมีการเน้นไปที่ประวัติเพื่อระบุมุมมองอื่นใน FocusArea นี้ก็ตาม
  3. ( แอนดรอยด์ 12 )
    ใช้ app:nudgeLeftShortcut , app:nudgeRightShortcut , app:nudgeUpShortcut และ app:nudgeDownShortcut เพื่อระบุ ID ของมุมมองลูกหลานที่โฟกัสได้ ซึ่งควรเน้นเมื่อผู้ใช้เขยิบไปในทิศทางที่กำหนด หากต้องการเรียนรู้เพิ่มเติม โปรดดูเนื้อหาสำหรับ ปุ่มลัดเขยิบ ด้านล่าง

    ( Android 11 QPR3, Android 11 Car, เลิกใช้แล้วใน Android 12 ) app:nudgeShortcut และ app:nudgeShortcutDirection รองรับปุ่มลัดเขยิบเพียงอันเดียวเท่านั้น

  4. ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
    หากต้องการเปิดใช้งานการหมุนเพื่อล้อมรอบใน FocusArea นี้ app:wrapAround สามารถตั้งค่า true โดยทั่วไปมักใช้เมื่อมีการจัดเรียงมุมมองเป็นวงกลมหรือวงรี
  5. ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
    หากต้องการปรับ padding ของไฮไลท์ใน FocusArea นี้ ให้ใช้ app:highlightPaddingStart , app:highlightPaddingEnd , app:highlightPaddingTop , app:highlightPaddingBottom , app:highlightPaddingHorizontal และ app:highlightPaddingVertical
  6. ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
    หากต้องการปรับขอบเขตที่รับรู้ของ FocusArea นี้เพื่อค้นหาเป้าหมายที่สะกิด ให้ใช้ app:startBoundOffset , app:endBoundOffset , app:topBoundOffset , app:bottomBoundOffset , app:horizontalBoundOffset และ app:verticalBoundOffset
  7. ( Android 11 QPR3, Android 11 รถยนต์, Android 12 )
    หากต้องการระบุ ID ของ FocusArea (หรือพื้นที่) ที่อยู่ติดกันอย่างชัดเจนในทิศทางที่กำหนด ให้ใช้ app:nudgeLeft , app:nudgeRight , app:nudgeUp และ app:nudgeDown ใช้สิ่งนี้เมื่อการค้นหาทางเรขาคณิตที่ใช้โดยค่าเริ่มต้นไม่พบเป้าหมายที่ต้องการ

การสะกิดมักจะนำทางระหว่าง FocusAreas แต่ด้วยปุ่มลัดเขยิบ บางครั้งการสะกิดจะนำทางภายใน FocusArea ดังนั้นผู้ใช้อาจต้องสะกิดสองครั้งเพื่อไปยัง FocusArea ถัดไป ปุ่มลัดเขยิบมีประโยชน์เมื่อ FocusArea มีรายการยาวตามด้วย ปุ่มการทำงานแบบลอย ดังตัวอย่างด้านล่าง:

ทางลัดเขยิบ
รูปที่ 3 ปุ่มลัดเขยิบ

หากไม่มีปุ่มลัดเขยิบ ผู้ใช้จะต้องหมุนดูรายการทั้งหมดเพื่อเข้าถึง 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 จะแทนที่สิ่งเหล่านี้เพื่อให้สอดคล้องกับไฮไลท์โฟกัสเริ่มต้นที่พวกเขาระบุ เพื่อให้แน่ใจว่าสีที่เน้นโฟกัส ความกว้างของเส้นขีด และอื่นๆ จะไม่เปลี่ยนแปลงเมื่อผู้ใช้นำทางระหว่างมุมมองที่มีการเน้นโฟกัสแบบกำหนดเองและมุมมองที่มีการเน้นโฟกัสเริ่มต้น รายการสุดท้ายเป็นระลอกคลื่นที่ใช้สำหรับการสัมผัส ค่าเริ่มต้นที่ใช้สำหรับทรัพยากรตัวหนาปรากฏดังนี้:

ค่าเริ่มต้นสำหรับทรัพยากรตัวหนา
รูปที่ 4. ค่าดีฟอลต์สำหรับรีซอร์สตัวหนา

นอกจากนี้ จะมีการเรียกไฮไลต์โฟกัสแบบกำหนดเองเมื่อปุ่มได้รับสีพื้นหลังแบบทึบเพื่อให้ผู้ใช้สนใจ ดังตัวอย่างด้านล่าง ซึ่งจะทำให้มองเห็นการเน้นโฟกัสได้ยาก ในสถานการณ์นี้ ระบุการเน้นโฟกัสแบบกำหนดเองโดยใช้สี รอง :

สีพื้นหลังทึบ
  • ( 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 ขั้นสูง มุมมอง:

  1. ( Android 11 QPR3, Android 11 Car, Android 12 ) ต้องฟังเหตุการณ์ KEYCODE_DPAD_CENTER เพื่อเข้าสู่โหมด DM และฟังเหตุการณ์ KEYCODE_BACK เพื่อออกจากโหมด DM โดยเรียก DirectManipulationHelper.enableDirectManipulationMode() ในแต่ละกรณี หากต้องการฟังเหตุการณ์เหล่านี้ ให้เลือกทำอย่างใดอย่างหนึ่งต่อไปนี้:
    • ลงทะเบียน OnKeyListener
    • หรือ,
    • ขยายมุมมองแล้วแทนที่เมธอด dispatchKeyEvent()
  2. ควรฟังเหตุการณ์เขยิบ ( KEYCODE_DPAD_UP , KEYCODE_DPAD_DOWN , KEYCODE_DPAD_LEFT หรือ KEYCODE_DPAD_RIGHT ) หากมุมมองควรจัดการกับการกระตุ้นเตือน
  3. ควรฟัง MotionEvent และรับจำนวนการหมุนใน AXIS_SCROLL หากมุมมองต้องการจัดการการหมุน มีหลายวิธีในการทำเช่นนี้:
    1. ลงทะเบียน OnGenericMotionListener
    2. ขยายมุมมองและแทนที่เมธอด dispatchTouchEvent()
  4. เพื่อหลีกเลี่ยงไม่ให้ติดอยู่ในโหมด DM ต้องออกจากโหมด DM เมื่อ Fragment หรือ Activity ที่เป็นของมุมมองไม่โต้ตอบ
  5. ควรจัดให้มีสัญญาณภาพเพื่อระบุว่ามุมมองอยู่ในโหมด 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 ที่ไม่มีมุมมองที่สามารถโฟกัสได้
,

เอกสารต่อไปนี้มีไว้สำหรับนักพัฒนาแอป

เพื่อให้แอปของคุณรองรับแบบหมุน คุณต้อง:

  1. วาง FocusParkingView ในรูปแบบกิจกรรมที่เกี่ยวข้อง
  2. ตรวจสอบให้แน่ใจว่ามุมมองที่สามารถโฟกัสได้ (หรือไม่ใช่)
  3. ใช้ 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. แตะที่จุดสามจุดที่ด้านล่างของแถบเครื่องมือ:

    เข้าถึงตัวควบคุมโรตารี่จำลอง
    รูปที่ 1. เข้าถึงตัวควบคุมโรตารี่จำลอง
  2. เลือก 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 :

  1. Android ไม่ล้างโฟกัสโดยอัตโนมัติเมื่อตั้งค่าโฟกัสในหน้าต่างอื่น หากคุณพยายามล้างโฟกัสในหน้าต่างก่อนหน้า Android จะปรับมุมมองใหม่ในหน้าต่างนั้น ซึ่งทำให้หน้าต่างสองบานถูกโฟกัสพร้อมกัน การเพิ่ม FocusParkingView ลงในแต่ละหน้าต่างสามารถแก้ไขปัญหานี้ได้ มุมมองนี้โปร่งใสและไฮไลต์โฟกัสเริ่มต้นถูกปิดใช้งาน เพื่อให้ผู้ใช้มองไม่เห็นไม่ว่าจะโฟกัสหรือไม่ก็ตาม มันสามารถโฟกัสได้เพื่อให้ RotaryService สามารถโฟกัส ที่ จุดนั้นเพื่อเอาไฮไลท์โฟกัสออก
  2. หากมี FocusArea เพียงอันเดียวในหน้าต่างปัจจุบัน การหมุนตัวควบคุมใน FocusArea จะทำให้ RotaryService ย้ายโฟกัสจากมุมมองทางด้านขวาไปยังมุมมองทางด้านซ้าย (และในทางกลับกัน) การเพิ่มมุมมองนี้ในแต่ละหน้าต่างสามารถแก้ไขปัญหาได้ เมื่อ RotaryService กำหนดเป้าหมายโฟกัสคือ FocusParkingView จะสามารถระบุได้ว่าจุดตัดรอบที่กำลังจะเกิดขึ้น ณ จุดที่หลีกเลี่ยงจุดโฟกัสโดยไม่ให้โฟกัสเคลื่อนที่
  3. เมื่อตัวควบคุมแบบหมุนเปิดแอปขึ้นมา 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 ควรพิจารณาให้ปิดใช้งาน แอปของคุณสามารถแจ้งให้ผู้ใช้ทราบว่าเหตุใดจึงปิดใช้งานมุมมองเมื่อแตะ ส่วนถัดไปจะอธิบายวิธีการดำเนินการนี้

สถานะที่กำหนดเอง

ในการเพิ่มสถานะที่กำหนดเอง:

  1. เพื่อเพิ่ม แอตทริบิวต์ที่กำหนดเอง ให้กับมุมมองของคุณ ตัวอย่างเช่น ในการเพิ่มสถานะแบบกำหนดเอง state_rotary_enabled ให้กับคลาสมุมมอง CustomView ให้ใช้:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. หากต้องการติดตามสถานะนี้ ให้เพิ่มตัวแปรอินสแตนซ์ในมุมมองของคุณพร้อมกับเมธอด accessor:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. วิธีอ่านค่าแอตทริบิวต์ของคุณเมื่อสร้างมุมมองของคุณ:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. ในคลาสมุมมองของคุณ ให้แทนที่ 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;
    }
    
  5. ทำให้ตัวจัดการการคลิกของข้อมูลพร็อพเพอร์ตี้ทำงานแตกต่างกันไปตามสถานะ ตัวอย่างเช่น ตัวจัดการการคลิกอาจไม่ทำอะไรเลย หรืออาจแสดงข้อความเตือนเมื่อ mRotaryEnabled เป็น false
  6. หากต้องการให้ปุ่มปรากฏว่าปิดใช้งาน ในพื้นหลังของมุมมองที่วาดได้ ให้ใช้ app:state_rotary_enabled แทน android:state_enabled หากคุณยังไม่มี คุณจะต้องเพิ่ม:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. หากมุมมองของคุณถูกปิดใช้งานในเลย์เอาต์ใดๆ ให้แทนที่ android:enabled="false" ด้วย app:state_rotary_enabled="false" แล้วเพิ่มเนมสเปซของ app ดังด้านบน
  8. หากมุมมองของคุณถูกปิดใช้งานโดยทางโปรแกรม ให้แทนที่การเรียก 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 ทำงานดังนี้:

  1. When handling rotate and nudge actions, RotaryService looks for instances of FocusArea in the view hierarchy.
  2. When receiving a rotation event, RotaryService moves focus to another View that can take focus in the same FocusArea .
  3. 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, use app: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 and app:defaultFocus (see below) in the same FocusArea .

FocusArea also defines some attributes to customize rotary navigation. Implicit focus areas can't be customized with these attributes.

  1. ( 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 this FocusArea .
  2. ( Android 11 QPR3, Android 11 Car, Android 12 )
    app:defaultFocusOverridesHistory can be set to true to make the view specified above take focus even if with history to indicate another view in this FocusArea had been focused on.
  3. ( Android 12 )
    Use app:nudgeLeftShortcut , app:nudgeRightShortcut , app:nudgeUpShortcut , and app: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 and app:nudgeShortcutDirection supported only one nudge shortcut.

  4. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To enable rotation to wrap around in this FocusArea , app:wrapAround can be set to true . This is most typically used when views are arranged in a circle or oval.
  5. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To adjust the padding of the highlight in this FocusArea , use app:highlightPaddingStart , app:highlightPaddingEnd , app:highlightPaddingTop , app:highlightPaddingBottom , app:highlightPaddingHorizontal , and app:highlightPaddingVertical .
  6. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To adjust the perceived bounds of this FocusArea to find a nudge target, use app:startBoundOffset , app:endBoundOffset , app:topBoundOffset , app:bottomBoundOffset , app:horizontalBoundOffset , and app:verticalBoundOffset .
  7. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To explicitly specify the ID of an adjacent FocusArea (or areas) in the given directions, use app:nudgeLeft , app:nudgeRight , app:nudgeUp , and app: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:

Nudge shortcut
Figure 3. Nudge shortcut

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:

Default values for bold resources
Figure 4. Default values for bold resources

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:

Solid background color
  • ( 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 pressedFocused, pressed
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 either AXIS_VSCROLL or AXIS_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:

  1. ( Android 11 QPR3, Android 11 Car, Android 12 ) MUST listen for a KEYCODE_DPAD_CENTER event to enter DM mode and listen for a KEYCODE_BACK event to exit DM mode, calling DirectManipulationHelper.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.
  2. SHOULD listen for nudge events ( KEYCODE_DPAD_UP , KEYCODE_DPAD_DOWN , KEYCODE_DPAD_LEFT , or KEYCODE_DPAD_RIGHT ) if the view should handle nudges.
  3. SHOULD listen to MotionEvent s and get rotation count in AXIS_SCROLL if the view wants to handle rotation. There are several ways to do this:
    1. Register an OnGenericMotionListener .
    2. Extend the view and override its dispatchTouchEvent() method.
  4. To avoid being stuck in DM mode, MUST exit DM mode when the Fragment or Activity the view belongs to is not interactive.
  5. 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 the ActivityView MUST contain a FocusParkingView as the first focusable view, and its app:shouldRestoreFocus attribute MUST be false .
  • The contents of the ActivityView should have no android: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 as true 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.)
  • 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 . Test FocusArea customization:
    • Wrap-around.
    • android:focusedByDefault and app:defaultFocus
    • .
    • Explicit nudge targets.
    • Nudge shortcuts.
    • FocusArea with no focusable views.