พัฒนาแอป

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

หากต้องการให้แอปรองรับการหมุน คุณต้องดำเนินการต่อไปนี้

  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. แตะจุด 3 จุดที่ด้านล่างของแถบเครื่องมือ

    เข้าถึงตัวควบคุมแบบหมุนจำลอง
    รูปที่ 1 เข้าถึงตัวควบคุมแบบหมุนจำลอง
  2. เลือกปุ่มหมุนของรถยนต์ในหน้าต่างการควบคุมแบบขยาย

    เลือกภาพรถยนต์
    รูปที่ 2 เลือก Rotary สำหรับรถยนต์

แป้นพิมพ์ USB

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

คำสั่ง ADB

คุณสามารถใช้คำสั่ง car_service เพื่อแทรกเหตุการณ์การป้อนข้อมูลแบบหมุนได้ คำสั่งเหล่านี้สามารถเรียกใช้ได้ในอุปกรณ์ที่ใช้ Android Automotive OS (AAOS) หรือในโปรแกรมจำลอง

คำสั่ง car_service การป้อนข้อมูลด้วยปุ่มหมุน
adb shell cmd car_service inject-rotary หมุนทวนเข็มนาฬิกา
adb shell cmd car_service inject-rotary -c true หมุนตามเข็มนาฬิกา
adb shell cmd car_service inject-rotary -dt 100 50 หมุนทวนเข็มนาฬิกาหลายครั้ง (100 มิลลิวินาทีที่ผ่านมาและ 50 มิลลิวินาทีที่ผ่านมา)
adb shell cmd car_service inject-key 282 กดไปทางซ้าย
adb shell cmd car_service inject-key 283 กดไปทางขวา
adb shell cmd car_service inject-key 280 กดขึ้น
adb shell cmd car_service inject-key 281 กดลง
adb shell cmd car_service inject-key 23 การคลิกปุ่มกลาง
adb shell input keyevent inject-key 4 การคลิกปุ่มย้อนกลับ

ตัวควบคุมแบบหมุนของ OEM

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

FocusParkingView

FocusParkingView คือมุมมองแบบโปร่งใสในไลบรารี UI ของรถ (car-ui-library) RotaryService ใช้เพื่อรองรับการนำทางด้วยตัวควบคุมแบบหมุน FocusParkingView ต้องเป็นมุมมองแรกที่โฟกัสได้ ในเลย์เอาต์ โดยต้องอยู่นอก FocusArea ทั้งหมด แต่ละกรอบต้องมี FocusParkingView 1 รายการ หากคุณใช้เลย์เอาต์พื้นฐาน car-ui-library ซึ่งมี FocusParkingView อยู่แล้ว ก็ไม่จำเป็นต้องเพิ่ม FocusParkingView อื่น ตัวอย่าง FocusParkingView ใน RotaryPlaygroundแสดงอยู่ด้านล่าง

<FrameLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <com.android.car.ui.FocusParkingView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
</FrameLayout>

เหตุผลที่ต้องมี FocusParkingView มีดังนี้

  1. Android จะไม่ล้างโฟกัสโดยอัตโนมัติเมื่อตั้งค่าโฟกัสในหน้าต่างอื่น หากคุณพยายามล้างโฟกัสในหน้าต่างก่อนหน้า Android จะโฟกัสมุมมองในหน้าต่างนั้นอีกครั้ง ซึ่งส่งผลให้มีโฟกัส 2 หน้าต่างพร้อมกัน การเพิ่ม 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 มักจะไม่ มุมมองที่คลิกได้จะโฟกัสโดยอัตโนมัติและมุมมองจะคลิกได้โดยอัตโนมัติเมื่อมีตัวรับฟังการคลิก หากตรรกะอัตโนมัตินี้ส่งผลให้เกิดโหมดโฟกัสที่ต้องการ คุณก็ไม่จําเป็นต้องตั้งค่าโหมดโฟกัสของมุมมองอย่างชัดเจน หากตรรกะอัตโนมัติไม่ได้ทําให้โฟกัสได้ตามที่คุณต้องการ ให้ตั้งค่าแอตทริบิวต์ android:focusable เป็น true หรือ false หรือตั้งค่าโฟกัสได้ของมุมมองด้วยโปรแกรมโดยใช้ View.setFocusable(boolean) มุมมองต้องเป็นไปตามข้อกำหนดต่อไปนี้ RotaryService จึงจะโฟกัสที่มุมมองนั้นได้

  • โฟกัสได้
  • เปิดใช้
  • แสดง
  • มีค่าความกว้างและความสูงที่ไม่ใช่ 0

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

FocusArea

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

FocusArea เป็นคลาสย่อยของ LinearLayout ใน car-ui-library เมื่อเปิดใช้ฟีเจอร์นี้ FocusArea จะวาดไฮไลต์เมื่อโฟกัสที่องค์ประกอบย่อย ดูข้อมูลเพิ่มเติมได้ที่การปรับแต่งไฮไลต์โฟกัส

เมื่อสร้างบล็อกการนําทางในไฟล์เลย์เอาต์ หากคุณต้องการใช้ LinearLayout เป็นคอนเทนเนอร์สําหรับบล็อกนั้น ให้ใช้ FocusArea แทน หรือจะตัดบล็อกใน FocusArea ก็ได้

อย่าฝัง FocusArea ใน FocusArea อื่น เนื่องจากจะทําให้ระบุลักษณะการนําทางไม่ได้ ตรวจสอบว่ามุมมองที่โฟกัสได้ทั้งหมดอยู่ภายใน FocusArea

ตัวอย่าง FocusArea ใน RotaryPlayground แสดงอยู่ด้านล่าง

<com.android.car.ui.FocusArea
       android:layout_margin="16dp"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">
       <EditText
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:singleLine="true">
       </EditText>
   </com.android.car.ui.FocusArea>

FocusArea จะทํางานดังนี้

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

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

การปรับแต่ง FocusArea

คุณใช้แอตทริบิวต์มุมมองมาตรฐาน 2 รายการต่อไปนี้เพื่อปรับแต่งการไปยังส่วนต่างๆ ด้วยปุ่มหมุนได้

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

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

  1. (Android 11 QPR3, Android 11 Car, Android 12)
    app:defaultFocus ใช้เพื่อระบุรหัสของมุมมองที่รับค่าสืบทอดซึ่งโฟกัสได้ ซึ่งควรโฟกัสเมื่อผู้ใช้แตะ 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 เพื่อระบุรหัสของมุมมองที่สืบทอดซึ่งโฟกัสได้ ซึ่งควรโฟกัสเมื่อผู้ใช้ปัดไปในทิศทางหนึ่งๆ ดูข้อมูลเพิ่มเติมได้ที่เนื้อหาเกี่ยวกับแป้นพิมพ์ลัดในการแตะเบาๆด้านล่าง

    (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:highlightPaddingTop, app:highlightPaddingBottom, 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)
    หากต้องการระบุรหัสของ FocusArea (หรือพื้นที่) ที่อยู่ติดกันในทิศทางที่ระบุ ให้ใช้ app:nudgeLeft, app:nudgeRight, app:nudgeUp และ app:nudgeDown ใช้ตัวเลือกนี้เมื่อการค้นหาเชิงเรขาคณิตที่ใช้โดยค่าเริ่มต้นไม่พบเป้าหมายที่ต้องการ

โดยปกติการนudge จะไปยังส่วนต่างๆ ของ FocusArea แต่การใช้แป้นพิมพ์ลัดสำหรับการกระตุ้นเตือนบางครั้งการกระตุ้นเตือนจะไปยังส่วนต่างๆ ภายใน FocusArea ก่อน ผู้ใช้จึงอาจต้องกระตุ้นเตือน 2 ครั้งเพื่อไปยัง 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 Car, Android 12)
    car_ui_rotary_focus_fill_secondary_color
    car_ui_rotary_focus_stroke_secondary_color
  • (Android 12)
    car_ui_rotary_focus_pressed_fill_secondary_color
    car_ui_rotary_focus_pressed_stroke_secondary_color

เช่น

โฟกัสอยู่ ไม่ได้กด โฟกัส กด
โฟกัสอยู่ ไม่ได้กด โฟกัส กด

การเลื่อนด้วยปุ่มหมุน

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

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

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

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

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

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

MotionEvents จะเหมือนกับเหตุการณ์ที่เกิดจากล้อเลื่อนบนเมาส์ ยกเว้นแหล่งที่มา

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

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

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

กลไกแบบง่ายเป็นตัวเลือกเดียวในหน้าต่างระบบ ส่วนแอปจะใช้กลไกใดก็ได้

กลไกที่เรียบง่าย

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

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

กลไกขั้นสูง

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

มุมมองต้องมีคุณสมบัติต่อไปนี้จึงจะรองรับโหมด DM ขั้นสูง

  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) หากมุมมองควรจัดการ Nudge
  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

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

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

ปุ่มที่ทำงานเมื่อกดค้างไว้

ปุ่มส่วนใหญ่จะทําให้เกิดการดำเนินการบางอย่างเมื่อคลิก ปุ่มบางปุ่มจะทำงานเมื่อกดค้างไว้แทน เช่น ปุ่มกรอไปข้างหน้าและกรอกลับมักจะทำงานเมื่อกดค้างไว้ หากต้องการให้ปุ่มดังกล่าวรองรับปุ่มหมุน ให้ฟังหา KEYCODE_DPAD_CENTER KeyEvents ดังนี้

mButton.setOnKeyListener((v, keyCode, event) ->
{
    if (keyCode != KEYCODE_DPAD_CENTER) {
        return false;
    }
    if (event.getAction() == ACTION_DOWN) {
        mButton.setPressed(true);
        mHandler.post(mRunnable);
    } else {
        mButton.setPressed(false);
        mHandler.removeCallbacks(mRunnable);
    }
    return true;
});

ซึ่ง mRunnable จะดำเนินการ (เช่น กรอกลับ) และกำหนดเวลาให้ทำงานหลังจากรอเวลา

โหมดสัมผัส

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

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

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

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

การแก้ปัญหา

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

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

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

RotaryPlayground

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

  • RotaryPlayground repository: packages/apps/Car/tests/RotaryPlayground/
  • เวอร์ชัน: Android 11 QPR3, Android 11 Car และ Android 12

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

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