พัฒนาแอพ

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

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

  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. เลือก Car Rotary ในหน้าต่างควบคุมแบบขยาย:

    เลือกรถหมุน
    รูปที่ 2 เลือก รถหมุน

แป้นพิมพ์ยูเอสบี

  • เสียบแป้นพิมพ์ USB เข้ากับอุปกรณ์ของคุณที่ใช้ Android Automotive OS (AAOS) ในบางกรณี การดำเนินการนี้จะป้องกันไม่ให้แป้นพิมพ์บนหน้าจอปรากฏขึ้น
  • ใช้ userdebug หรือ eng build
  • เปิดใช้งานการกรองเหตุการณ์สำคัญ:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • ดูตารางด้านล่างเพื่อค้นหาคีย์ที่เกี่ยวข้องสำหรับแต่ละการกระทำ:
    สำคัญ การกระทำแบบโรตารี
    ถาม หมุนทวนเข็มนาฬิกา
    อี หมุนตามเข็มนาฬิกา
    เขยิบไปทางซ้าย
    ดี เขยิบไปทางขวา
    เขยิบขึ้น
    เขยิบลง
    F หรือจุลภาค ปุ่มตรงกลาง
    R หรือ Esc ปุ่มย้อนกลับ

คำสั่งเอดีบี

คุณสามารถใช้คำสั่ง 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 เป็นมุมมองแบบโปร่งใสใน ไลบรารี Car UI (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 s และ ViewGroup s มักไม่เป็นเช่นนั้น มุมมองที่คลิกได้นั้นสามารถโฟกัสได้โดยอัตโนมัติ และมุมมองนั้นสามารถคลิกได้โดยอัตโนมัติเมื่อมีตัวฟังการคลิก หากตรรกะอัตโนมัตินี้ส่งผลให้ได้ความสามารถในการโฟกัสที่ต้องการ คุณไม่จำเป็นต้องตั้งค่าความสามารถในการโฟกัสของมุมมองอย่างชัดเจน หากตรรกะอัตโนมัติไม่ส่งผลให้สามารถโฟกัสได้ตามต้องการ ให้ตั้งค่าแอตทริบิวต์ 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. หากต้องการติดตามสถานะนี้ ให้เพิ่มตัวแปรอินสแตนซ์ลงในมุมมองของคุณพร้อมกับวิธีการเข้าถึง:
    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 จะย้ายโฟกัสไปยังมุมมองอื่นที่สามารถโฟกัสใน 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 )
    หากต้องการปรับช่องว่างภายในของไฮไลต์ใน 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 เพื่อระบุไฮไลต์โฟกัสของคุณเองสำหรับ แต่ละมุมมอง

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

<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 เมื่อแฟรกเมนต์หรือกิจกรรมที่อยู่ในมุมมองนั้นไม่โต้ตอบ
  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 หากคุณไม่เพิ่ม FocusAreas ใดๆ ให้กับ 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 จะดำเนินการ (เช่น การกรอกลับ) และกำหนดเวลาให้ตัวเองทำงานหลังจากการหน่วงเวลา

โหมดสัมผัส

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

  • โรตารี → สัมผัส เมื่อผู้ใช้สัมผัสหน้าจอ ไฮไลท์โฟกัสจะหายไป
  • สัมผัส → หมุน เมื่อผู้ใช้ขยับ หมุน หรือกดปุ่มกลาง ไฮไลท์โฟกัสจะปรากฏขึ้น

ปุ่มย้อนกลับและปุ่มโฮมไม่มีผลกับโหมดอินพุต

หมูหลังแบบหมุนบนแนวคิด โหมดสัมผัส ที่มีอยู่ของ 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. เลือก Car Rotary ในหน้าต่างควบคุมแบบขยาย:

    เลือกรถหมุน
    รูปที่ 2 เลือก รถหมุน

แป้นพิมพ์ยูเอสบี

  • เสียบแป้นพิมพ์ USB เข้ากับอุปกรณ์ของคุณที่ใช้ Android Automotive OS (AAOS) ในบางกรณี การดำเนินการนี้จะป้องกันไม่ให้แป้นพิมพ์บนหน้าจอปรากฏขึ้น
  • ใช้ userdebug หรือ eng build
  • เปิดใช้งานการกรองเหตุการณ์สำคัญ:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • ดูตารางด้านล่างเพื่อค้นหาคีย์ที่เกี่ยวข้องสำหรับแต่ละการกระทำ:
    สำคัญ การกระทำแบบโรตารี
    ถาม หมุนทวนเข็มนาฬิกา
    อี หมุนตามเข็มนาฬิกา
    เขยิบไปทางซ้าย
    ดี เขยิบไปทางขวา
    เขยิบขึ้น
    เขยิบลง
    F หรือจุลภาค ปุ่มตรงกลาง
    R หรือ Esc ปุ่มย้อนกลับ

คำสั่งเอดีบี

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

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

การปรับแต่งโฟกัส

แอตทริบิวต์มุมมองมาตรฐานสองตัวสามารถใช้ในการปรับแต่งการนำทางแบบโรตารี่:

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

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

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

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

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

หากไม่มีทางลัดเขยิบผู้ใช้จะต้องหมุนผ่านรายการทั้งหมดเพื่อไปถึง Fab

โฟกัสเน้นการปรับแต่ง

ดังที่ได้กล่าวไว้ข้างต้น RotaryService สร้างขึ้นบนแนวคิดที่มีอยู่ของ Android Framework ของมุมมองโฟกัส เมื่อผู้ใช้หมุนและสะบัด RotaryService จะย้ายโฟกัสไปรอบ ๆ โดยมุ่งเน้นที่มุมมองหนึ่งและแยกออกจากกัน ใน Android เมื่อมุมมองมุ่งเน้นถ้ามุมมอง:

  • ได้ระบุไฮไลต์โฟกัสของตัวเอง Android ดึงไฮไลต์โฟกัสของมุมมอง
  • ไม่ได้ระบุไฮไลต์โฟกัสและไฮไลท์โฟกัสเริ่มต้นจะไม่ปิดใช้งาน Android ดึงไฮไลต์โฟกัสเริ่มต้นสำหรับมุมมอง

แอพที่ออกแบบมาสำหรับการสัมผัสมักจะไม่ได้ระบุไฮไลท์โฟกัสที่เหมาะสม

ไฮไลต์โฟกัสเริ่มต้นนั้นจัดทำโดย Android Framework และสามารถแทนที่ได้โดย OEM นักพัฒนาแอปจะได้รับเมื่อธีมที่ใช้มาจาก Theme.DeviceDefault

สำหรับประสบการณ์การใช้งานที่สอดคล้องกันให้พึ่งพาโฟกัสเริ่มต้นเริ่มต้นเมื่อใดก็ตามที่เป็นไปได้ หากคุณต้องการไฮไลต์โฟกัสรูปทรงกลมหรือรูปทรงกลม) หรือหากคุณใช้ธีมที่ไม่ได้มาจาก Theme.DeviceDefault ให้ใช้ทรัพยากร Car-Ui-Library เพื่อระบุจุดสนใจโฟกัสของคุณเอง แต่ละมุมมอง

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

<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:state_pressed="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
            android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
      </shape>
   </item>
   <item android:state_focused="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_stroke_width"
            android:color="@color/car_ui_rotary_focus_stroke_color"/>
      </shape>
   </item>
   <item>
      <ripple...>
         ...
      </ripple>
   </item>
</selector>

( Android 11 QPR3, Android 11 Car, Android 12 ) การอ้างอิงทรัพยากร ตัวหนา ในตัวอย่างด้านบนระบุทรัพยากรที่กำหนดโดย Car-UI-Library OEM แทนที่สิ่งเหล่านี้ให้สอดคล้องกับไฮไลต์โฟกัสเริ่มต้นที่ระบุไว้ สิ่งนี้ทำให้มั่นใจได้ว่าสีไฮไลต์โฟกัสความกว้างจังหวะและอื่น ๆ จะไม่เปลี่ยนแปลงเมื่อผู้ใช้นำทางระหว่างมุมมองด้วยไฮไลต์โฟกัสแบบกำหนดเองและมุมมองที่มีไฮไลต์โฟกัสเริ่มต้น รายการสุดท้ายคือระลอกคลื่นที่ใช้สำหรับสัมผัส ค่าเริ่มต้นที่ใช้สำหรับทรัพยากรตัวหนาจะปรากฏดังนี้:

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

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

สีพื้นหลังทึบ
  • ( Android 11 QPR3, Android 11 Car, Android 12 )
    car_ui_rotary_focus_fill_secondary_color
    car_ui_rotary_focus_stroke_secondary_color
  • ( Android 12 )
    car_ui_rotary_focus_pressed_fill_secondary_color
    car_ui_rotary_focus_pressed_stroke_secondary_color

ตัวอย่างเช่น:

โฟกัสไม่กดโฟกัสกด
โฟกัสไม่กด โฟกัสกด

การเลื่อนแบบหมุน

หากแอปของคุณใช้ RecyclerView S คุณควรใช้ CarUiRecyclerView S แทน สิ่งนี้ทำให้มั่นใจได้ว่า UI ของคุณสอดคล้องกับผู้อื่นเพราะการปรับแต่งของ OEM ใช้กับ CarUiRecyclerView s ทั้งหมด

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

( Android 11 QPR3, Android 11 Car, Android 12 )
หากมีการผสมผสานระหว่างองค์ประกอบที่โฟกัสและไม่สามารถโฟกัสได้หรือหากองค์ประกอบทั้งหมดไม่สามารถโฟกัสได้คุณสามารถเปิดใช้งานการเลื่อนแบบโรตารี่ซึ่งช่วยให้ผู้ใช้สามารถใช้คอนโทรลเลอร์โรตารี่เพื่อค่อยๆเลื่อนดูรายการโดยไม่ต้องข้ามรายการที่ไม่สามารถโฟกัสได้ ในการเปิดใช้งานการเลื่อนแบบโรตารี่ให้ตั้ง app:rotaryScrollEnabled Attribute เป็น true

( Android 11 QPR3, Android 11 Car, Android 12 )
คุณสามารถเปิดใช้งานการเลื่อนแบบโรตารี่ในมุมมองที่เลื่อนได้ใด ๆ รวมถึง AV CarUiRecyclerView ด้วยวิธี setRotaryScrollEnabled() ใน CarUiUtils ถ้าคุณทำเช่นนั้นคุณต้อง:

  • ทำให้มุมมองแบบเลื่อนได้โฟกัสได้เพื่อที่จะสามารถมุ่งเน้นไปที่เมื่อไม่มีมุมมองลูกหลานที่สามารถโฟกัสได้
  • ปิดการใช้งานไฮไลต์โฟกัสเริ่มต้นบนมุมมองที่เลื่อนได้โดยเรียก setDefaultFocusHighlightEnabled(false) เพื่อให้มุมมองที่เลื่อนได้ไม่ปรากฏขึ้น
  • ตรวจสอบให้แน่ใจว่ามุมมองที่เลื่อนได้นั้นมุ่งเน้นไปที่ก่อนที่ลูกหลานของมันจะเรียก setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
  • ฟัง MotionEvents ด้วย SOURCE_ROTARY_ENCODER และ AXIS_VSCROLL หรือ AXIS_HSCROLL เพื่อระบุระยะทางในการเลื่อนและทิศทาง (ผ่านเครื่องหมาย)

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

MotionEvents นั้นเหมือนกับที่สร้างขึ้นโดยล้อเลื่อนบนเมาส์ยกเว้นแหล่งที่มา

โหมดการจัดการโดยตรง

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

ใช้ DM ในหนึ่งในสองวิธี หากคุณต้องการจัดการกับการหมุนและมุมมองที่คุณต้องการจัดการตอบสนองต่อ ACTION_SCROLL_FORWARD และ ACTION_SCROLL_BACKWARD AccessibilityEvent S อย่างเหมาะสมให้ใช้กลไก ง่ายๆ มิฉะนั้นให้ใช้กลไก ขั้นสูง

กลไกง่ายๆเป็นตัวเลือกเดียวใน Windows ระบบ แอพสามารถใช้กลไกอย่างใดอย่างหนึ่ง

กลไกง่ายๆ

( Android 11 QPR3, Android 11 Car, Android 12 )
แอพของคุณควรโทรหา DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable) RotaryService รับรู้เมื่อผู้ใช้อยู่ในโหมด DM และเข้าสู่โหมด DM เมื่อผู้ใช้กดปุ่มกลางในขณะที่มุมมองโฟกัส เมื่ออยู่ในโหมด DM การหมุนจะดำเนิน ACTION_SCROLL_FORWARD หรือ ACTION_SCROLL_BACKWARD และออกจากโหมด DM เมื่อผู้ใช้กดปุ่มย้อนกลับ กลไกง่ายๆสลับสถานะของมุมมองที่เลือกเมื่อเข้าและออกจากโหมด DM

เพื่อให้คิวภาพที่ผู้ใช้อยู่ในโหมด DM ทำให้มุมมองของคุณแตกต่างกันเมื่อเลือก ตัวอย่างเช่นเปลี่ยนพื้นหลังเมื่อ android:state_selected เป็น true

กลไกขั้นสูง

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

เพื่อรองรับโหมด DM ขั้นสูงมุมมอง:

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

ตัวอย่างของมุมมองที่กำหนดเองที่ใช้โหมด DM เพื่อ PAN และซูมแผนที่มีให้ด้านล่าง:

/** Whether this view is in DM mode. */
private boolean mInDirectManipulationMode;

/** Initializes the view. Called by the constructors. */ private void init() { setOnKeyListener((view, keyCode, keyEvent) -> { boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP; switch (keyCode) { // Always consume KEYCODE_DPAD_CENTER and KEYCODE_BACK events. case KeyEvent.KEYCODE_DPAD_CENTER: if (!mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = true; DirectManipulationHelper.enableDirectManipulationMode(this, true); setSelected(true); // visually indicate DM mode } return true; case KeyEvent.KEYCODE_BACK: if (mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); setSelected(false); } return true; // Consume controller nudge events only when in DM mode. // When in DM mode, nudges pan the map. case KeyEvent.KEYCODE_DPAD_UP: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, -10f); return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, 10f); return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(-10f, 0f); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(10f, 0f); return true; // Don't consume other key events. default: return false; } });
// When in DM mode, rotation zooms the map. setOnGenericMotionListener(((view, motionEvent) -> { if (!mInDirectManipulationMode) return false; float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL); zoom(10 * scroll); return true; })); }
@Override public void onPause() { if (mInDirectManipulationMode) { // To ensure that the user doesn't get stuck in DM mode, disable DM mode // when the fragment is not interactive (e.g., a dialog shows up). mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); } super.onPause(); }

ตัวอย่างเพิ่มเติมสามารถพบได้ในโครงการ RotaryPlayground

ActivityView

เมื่อใช้ ActivityView:

  • ActivityView ไม่ควรมุ่งเน้น
  • ( Android 11 QPR3, Android 11 Car, เลิกใช้ใน Android 11 )
    เนื้อหาของ ActivityView จะต้องมี FocusParkingView เป็นมุมมองแรกที่สามารถโฟกัสได้และ app:shouldRestoreFocus ควรจะเป็น false
  • เนื้อหาของ ActivityView ควรไม่มี android:focusByDefault Views

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

โหมดสัมผัส

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

  • โรตารี่→สัมผัส เมื่อผู้ใช้สัมผัสหน้าจอไฮไลต์โฟกัสจะหายไป
  • สัมผัส→โรตารี่ เมื่อผู้ใช้เขยิบหมุนหรือกดปุ่มกึ่งกลางไฮไลท์โฟกัสจะปรากฏขึ้น

ปุ่มด้านหลังและบ้านไม่มีผลต่อโหมดอินพุต

Piggybacks แบบโรตารี่บนแนวคิดที่มีอยู่ของ Android เกี่ยวกับ โหมดสัมผัส คุณสามารถใช้ View.isInTouchMode() เพื่อกำหนดโหมดอินพุตที่ผู้ใช้ใช้ คุณสามารถใช้ OnTouchModeChangeListener เพื่อฟังการเปลี่ยนแปลง แม้ว่าสิ่งนี้สามารถใช้ในการปรับแต่งส่วนต่อประสานผู้ใช้ของคุณสำหรับโหมดอินพุตปัจจุบันให้หลีกเลี่ยงการเปลี่ยนแปลงที่สำคัญใด ๆ เนื่องจากสามารถทำให้สับสน

การแก้ไขปัญหา

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

หากปุ่มหรือสวิตช์สูญเสียโฟกัสเมื่อกดผ่านคอนโทรลเลอร์โรตารี่อาจใช้หนึ่งในเงื่อนไขเหล่านี้:

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

พื้นแบบโรตารีเพลย์

RotaryPlayground เป็นแอพอ้างอิงสำหรับโรตารี่ ใช้เพื่อเรียนรู้วิธีการรวมคุณสมบัติโรตารี่เข้ากับแอพของคุณ RotaryPlayground รวมอยู่ในการสร้าง Emulator และในการสร้างสำหรับอุปกรณ์ที่ใช้ Android Automotive OS (AAOS)

  • ที่เก็บ RotaryPlayground : packages/apps/Car/tests/RotaryPlayground/
  • เวอร์ชัน: Android 11 QPR3, Android 11 Car และ Android 12

แอพ RotaryPlayground แสดงแท็บต่อไปนี้ทางด้านซ้าย:

  • การ์ด ทดสอบการนำทางไปรอบ ๆ พื้นที่โฟกัสข้ามองค์ประกอบที่ไม่สามารถโฟกัสและอินพุตข้อความได้
  • การจัดการโดยตรง วิดเจ็ตทดสอบที่รองรับโหมดการจัดการโดยตรงที่เรียบง่ายและขั้นสูง แท็บนี้มีไว้สำหรับการจัดการโดยตรงภายในหน้าต่างแอพ
  • การจัดการ SYS UI วิดเจ็ตทดสอบที่รองรับการจัดการโดยตรงในหน้าต่างระบบที่รองรับโหมดการจัดการโดยตรงอย่างง่ายเท่านั้น
  • กริด ทดสอบการนำทางโรตารี่ Z-Pattern ด้วยการเลื่อน
  • การแจ้งเตือน ทดสอบการผลักดันเข้าและออกจากการแจ้งเตือนแบบหัวขึ้น
  • เลื่อน ทดสอบการเลื่อนผ่านการผสมผสานของเนื้อหาที่มุ่งเน้นและไม่สามารถโฟกัสได้
  • WebView ทดสอบการนำทางผ่านลิงก์ใน WebView
  • FocusArea กำหนดเอง ทดสอบการปรับแต่ง FocusArea :
    • ห่อรอบ ๆ.
    • android:focusedByDefault และ app:defaultFocus
    • .
    • เป้าหมายเขยิบที่ชัดเจน
    • ทางลัดเขยิบ
    • FocusArea ที่ไม่มีมุมมองที่โฟกัสได้