Phát triển ứng dụng

Tài liệu sau đây dành cho nhà phát triển ứng dụng.

Để ứng dụng của bạn hỗ trợ tính năng xoay, bạn PHẢI:

  1. Đặt FocusParkingView vào bố cục hoạt động tương ứng.
  2. Đảm bảo các thành phần hiển thị có thể (hoặc không thể) lấy tiêu điểm.
  3. Sử dụng FocusArea để bao bọc tất cả các thành phần hiển thị có thể lấy tiêu điểm, ngoại trừ FocusParkingView.

Bạn có thể tham khảo thông tin chi tiết về từng việc cần làm ở bên dưới, sau khi thiết lập môi trường để phát triển ứng dụng hỗ trợ màn hình xoay.

Thiết lập bộ điều khiển xoay

Trước khi có thể bắt đầu phát triển ứng dụng hỗ trợ thao tác xoay, bạn cần có bộ điều khiển xoay hoặc thiết bị thay thế. Bạn có các lựa chọn được mô tả bên dưới.

Trình mô phỏng

source build/envsetup.sh && lunch car_x86_64-userdebug
m -j
emulator -wipe-data -no-snapshot -writable-system

Bạn cũng có thể sử dụng aosp_car_x86_64-userdebug.

Cách truy cập vào bộ điều khiển xoay được mô phỏng:

  1. Nhấn vào biểu tượng ba dấu chấm ở cuối thanh công cụ:

    Truy cập vào bộ điều khiển xoay được mô phỏng
    Hình 1. Truy cập vào bộ điều khiển xoay được mô phỏng
  2. Chọn Car rotary (Xe xoay) trong cửa sổ điều khiển mở rộng:

    Chọn Car rotary (Xe xoay)
    Hình 2. Chọn Xe xoay

Bàn phím USB

  • Cắm bàn phím USB vào thiết bị chạy Android Automotive OS (AAOS). Trong một số trường hợp, việc này sẽ ngăn bàn phím ảo xuất hiện.
  • Sử dụng bản dựng userdebug hoặc eng.
  • Bật tính năng lọc sự kiện nhấn phím:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • Hãy xem bảng dưới đây để tìm khoá tương ứng cho từng hành động:
    Khoá Thao tác xoay
    Hỏi Xoay ngược chiều kim đồng hồ
    E Xoay theo chiều kim đồng hồ
    A Dịch sang trái
    D Dịch sang phải
    W Dịch lên trên
    S Dịch xuống dưới
    F hoặc dấu phẩy Nút giữa
    R hoặc Esc Nút quay lại

Lệnh ADB

Bạn có thể sử dụng các lệnh car_service để chèn các sự kiện đầu vào xoay. Bạn có thể chạy các lệnh này trên các thiết bị chạy Android Automotive OS (AAOS) hoặc trên trình mô phỏng.

lệnh car_service Phương thức nhập dữ liệu xoay
adb shell cmd car_service inject-rotary Xoay ngược chiều kim đồng hồ
adb shell cmd car_service inject-rotary -c true Xoay theo chiều kim đồng hồ
adb shell cmd car_service inject-rotary -dt 100 50 Xoay ngược chiều kim đồng hồ nhiều lần (100 mili giây trước và 50 mili giây trước)
adb shell cmd car_service inject-key 282 Dịch sang trái
adb shell cmd car_service inject-key 283 Dịch sang phải
adb shell cmd car_service inject-key 280 Dịch lên trên
adb shell cmd car_service inject-key 281 Dịch xuống dưới
adb shell cmd car_service inject-key 23 Nhấp vào nút giữa
adb shell input keyevent inject-key 4 Nhấp vào nút quay lại

Bộ điều khiển xoay của OEM

Khi phần cứng tay điều khiển xoay của bạn đang hoạt động, đây là tuỳ chọn thực tế nhất. Điều này đặc biệt hữu ích khi kiểm thử tính năng xoay nhanh.

FocusParkingView

FocusParkingView là một thành phần hiển thị trong suốt trong Thư viện giao diện người dùng ô tô (car-ui-library). RotaryService sử dụng nó để hỗ trợ điều hướng bằng bộ điều khiển xoay. FocusParkingView phải là thành phần hiển thị có thể lấy tiêu điểm đầu tiên trong bố cục. Bạn phải đặt lớp này bên ngoài tất cả FocusArea. Mỗi cửa sổ phải có một FocusParkingView. Nếu đang sử dụng bố cục cơ sở car-ui-library chứa FocusParkingView, bạn không cần thêm FocusParkingView khác. Dưới đây là ví dụ về FocusParkingView trong 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>

Sau đây là lý do bạn cần có FocusParkingView:

  1. Android không tự động xoá tiêu điểm khi tiêu điểm được đặt trong một cửa sổ khác. Nếu bạn cố gắng xoá tiêu điểm trong cửa sổ trước đó, Android sẽ lấy lại tiêu điểm cho một thành phần hiển thị trong cửa sổ đó, dẫn đến việc hai cửa sổ được lấy tiêu điểm cùng lúc. Bạn có thể khắc phục vấn đề này bằng cách thêm FocusParkingView vào mỗi cửa sổ. Chế độ xem này có tính chất trong suốt và điểm nổi bật tiêu điểm mặc định của chế độ xem này bị tắt, vì vậy, người dùng sẽ không nhìn thấy chế độ xem này bất kể có được lấy tiêu điểm hay không. Nó có thể lấy tiêu điểm để RotaryService có thể đặt tiêu điểm trên đó để xoá điểm nổi bật của tiêu điểm.
  2. Nếu chỉ có một FocusArea trong cửa sổ hiện tại, thì việc xoay bộ điều khiển trong FocusArea sẽ khiến RotaryService di chuyển tiêu điểm từ thành phần hiển thị bên phải sang thành phần hiển thị bên trái (và ngược lại). Bạn có thể khắc phục vấn đề này bằng cách thêm thành phần hiển thị này vào từng cửa sổ. Khi RotaryService xác định mục tiêu tiêu điểm là FocusParkingView, nó có thể xác định một vòng lặp sắp diễn ra tại thời điểm nào, tránh vòng lặp bằng cách không di chuyển tiêu điểm.
  3. Khi nút điều khiển xoay khởi chạy một ứng dụng, Android sẽ đặt tiêu điểm vào thành phần hiển thị có thể lấy tiêu điểm đầu tiên, luôn là FocusParkingView. FocusParkingView xác định chế độ xem tối ưu để lấy tiêu điểm, sau đó áp dụng tiêu điểm.

Chế độ xem có thể lấy tiêu điểm

RotaryService được xây dựng dựa trên khái niệm hiện có về tiêu điểm thành phần hiển thị của khung Android, từ thời điện thoại có bàn phím thực và bàn phím D-pad. Thuộc tính android:nextFocusForward hiện có được sử dụng lại cho màn hình xoay (xem phần Tuỳ chỉnh FocusArea), nhưng android:nextFocusLeft, android:nextFocusRight, android:nextFocusUpandroid:nextFocusDown thì không.

RotaryService chỉ tập trung vào các thành phần hiển thị có thể lấy tiêu điểm. Một số thành phần hiển thị, chẳng hạn như Button, thường có thể lấy tiêu điểm. Các loại khác, chẳng hạn như TextViewViewGroup, thường không được hỗ trợ. Các thành phần hiển thị có thể nhấp sẽ tự động có thể lấy tiêu điểm và các thành phần hiển thị sẽ tự động có thể nhấp khi có trình nghe lượt nhấp. Nếu logic tự động này mang lại khả năng lấy tiêu điểm mong muốn, bạn không cần phải đặt rõ ràng khả năng lấy tiêu điểm của thành phần hiển thị. Nếu logic tự động không mang lại khả năng lấy tiêu điểm mong muốn, hãy đặt thuộc tính android:focusable thành true hoặc false, hoặc đặt khả năng lấy tiêu điểm của thành phần hiển thị bằng View.setFocusable(boolean) theo phương thức lập trình. Để RotaryService lấy tiêu điểm, thành phần hiển thị PHẢI đáp ứng các yêu cầu sau:

  • Có thể làm tâm điểm
  • Đã bật
  • Đã hiển thị
  • Có giá trị khác 0 cho chiều rộng và chiều cao

Nếu một thành phần hiển thị không đáp ứng tất cả các yêu cầu này, chẳng hạn như một nút có thể lấy tiêu điểm nhưng bị tắt, thì người dùng không thể sử dụng nút điều khiển xoay để lấy tiêu điểm trên nút đó. Nếu bạn muốn tập trung vào các thành phần hiển thị bị vô hiệu hoá, hãy cân nhắc sử dụng trạng thái tuỳ chỉnh thay vì android:state_enabled để kiểm soát cách thành phần hiển thị xuất hiện mà không cho biết Android nên coi thành phần hiển thị đó là bị vô hiệu hoá. Ứng dụng của bạn có thể thông báo cho người dùng lý do chế độ xem bị tắt khi nhấn vào. Phần tiếp theo sẽ giải thích cách thực hiện việc này.

Trạng thái tuỳ chỉnh

Cách thêm trạng thái tuỳ chỉnh:

  1. Để thêm thuộc tính tuỳ chỉnh vào thành phần hiển thị. Ví dụ: để thêm trạng thái tuỳ chỉnh state_rotary_enabled vào lớp thành phần hiển thị CustomView, hãy sử dụng:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. Để theo dõi trạng thái này, hãy thêm một biến thực thể vào thành phần hiển thị cùng với các phương thức truy cập:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. Cách đọc giá trị của thuộc tính khi thành phần hiển thị được tạo:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. Trong lớp thành phần hiển thị, hãy ghi đè phương thức onCreateDrawableState(), sau đó thêm trạng thái tuỳ chỉnh (khi thích hợp). Ví dụ:
    @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. Tùy vào trạng thái của thành phần hiển thị mà trình xử lý lượt nhấp của thành phần hiển thị đó sẽ hoạt động theo cách khác nhau. Ví dụ: trình xử lý lượt nhấp có thể không làm gì cả hoặc có thể bật lên một thông báo ngắn khi mRotaryEnabledfalse.
  6. Để nút xuất hiện ở trạng thái tắt, trong nền có thể vẽ của thành phần hiển thị, hãy sử dụng app:state_rotary_enabled thay vì android:state_enabled. Nếu chưa có, bạn cần thêm:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Nếu thành phần hiển thị của bạn bị tắt trong bất kỳ bố cục nào, hãy thay thế android:enabled="false" bằng app:state_rotary_enabled="false", sau đó thêm không gian tên app như trên.
  8. Nếu chế độ xem của bạn bị tắt theo phương thức lập trình, hãy thay thế các lệnh gọi đến setEnabled() bằng các lệnh gọi đến setRotaryEnabled().

FocusArea

Sử dụng FocusAreas để phân vùng các thành phần hiển thị có thể lấy tiêu điểm thành các khối để giúp việc điều hướng dễ dàng hơn và nhất quán với các ứng dụng khác. Ví dụ: nếu ứng dụng của bạn có thanh công cụ, thì thanh công cụ phải nằm trong một FocusArea riêng biệt với phần còn lại của ứng dụng. Thanh thẻ và các thành phần điều hướng khác cũng phải được tách biệt với phần còn lại của ứng dụng. Các danh sách lớn thường phải có FocusArea riêng. Nếu không, người dùng phải xoay qua toàn bộ danh sách để truy cập vào một số chế độ xem.

FocusArea là lớp con của LinearLayout trong thư viện car-ui-library. Khi tính năng này được bật, FocusArea sẽ vẽ một điểm nổi bật khi một trong các thành phần con của nó được lấy tiêu điểm. Để tìm hiểu thêm, hãy xem phần Tuỳ chỉnh điểm nhấn tiêu điểm.

Khi tạo một khối điều hướng trong tệp bố cục, nếu bạn định sử dụng LinearLayout làm vùng chứa cho khối đó, hãy sử dụng FocusArea. Nếu không, hãy gói khối trong FocusArea.

KHÔNG lồng FocusArea trong một FocusArea khác. Việc này dẫn đến hành vi điều hướng không xác định. Đảm bảo rằng tất cả thành phần hiển thị có thể lấy tiêu điểm đều được lồng trong FocusArea.

Dưới đây là ví dụ về FocusArea trong 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 hoạt động như sau:

  1. Khi xử lý các thao tác xoay và đẩy, RotaryService sẽ tìm các thực thể của FocusArea trong hệ phân cấp chế độ xem.
  2. Khi nhận được sự kiện xoay, RotaryService sẽ di chuyển tiêu điểm sang một Khung hiển thị khác có thể lấy tiêu điểm trong cùng một FocusArea.
  3. Khi nhận được sự kiện đẩy, RotaryService sẽ di chuyển tiêu điểm sang một thành phần hiển thị khác có thể lấy tiêu điểm trong một FocusArea khác (thường là liền kề).

Nếu bạn không đưa FocusAreas nào vào bố cục, thì thành phần hiển thị gốc sẽ được coi là vùng tiêu điểm ngầm ẩn. Người dùng không thể nhấn để điều hướng trong ứng dụng. Thay vào đó, họ sẽ xoay qua tất cả các chế độ xem có thể lấy tiêu điểm, điều này có thể phù hợp với các hộp thoại.

Tuỳ chỉnh FocusArea

Bạn có thể sử dụng hai thuộc tính Chế độ xem chuẩn để tuỳ chỉnh tính năng điều hướng xoay:

  • android:nextFocusForward cho phép nhà phát triển ứng dụng chỉ định thứ tự xoay trong một khu vực tiêu điểm. Đây cũng là thuộc tính dùng để kiểm soát thứ tự Tab cho thao tác điều hướng bằng bàn phím. KHÔNG sử dụng thuộc tính này để tạo vòng lặp. Thay vào đó, hãy sử dụng app:wrapAround (xem bên dưới) để tạo một vòng lặp.
  • android:focusedByDefault cho phép nhà phát triển ứng dụng chỉ định chế độ xem tiêu điểm mặc định trong cửa sổ. KHÔNG sử dụng thuộc tính này và app:defaultFocus (xem bên dưới) trong cùng một FocusArea.

FocusArea cũng xác định một số thuộc tính để tuỳ chỉnh thao tác điều hướng xoay. Bạn không thể tuỳ chỉnh các vùng tiêu điểm ngầm ẩn bằng các thuộc tính này.

  1. (Android 11 QPR3, Android 11 Car, Android 12)
    app:defaultFocus có thể được dùng để chỉ định mã nhận dạng của thành phần hiển thị con cháu có thể lấy tiêu điểm. Thành phần hiển thị này sẽ được lấy tiêu điểm khi người dùng nhấn vào FocusArea này.
  2. (Android 11 QPR3, Android 11 Car, Android 12)
    app:defaultFocusOverridesHistory có thể được đặt thành true để đưa thành phần hiển thị được chỉ định ở trên vào tiêu điểm ngay cả khi có nhật ký để cho biết một thành phần hiển thị khác trong FocusArea này đã được lấy tiêu điểm.
  3. (Android 12)
    Hãy sử dụng app:nudgeLeftShortcut, app:nudgeRightShortcut, app:nudgeUpShortcutapp:nudgeDownShortcut để chỉ định mã nhận dạng của thành phần hiển thị con cháu có thể lấy tiêu điểm. Thành phần này sẽ được lấy tiêu điểm khi người dùng đẩy theo một hướng nhất định. Để tìm hiểu thêm, hãy xem nội dung về lối tắt nhấn nhẹ ở bên dưới.

    (Android 11 QPR3, Android 11 Car, không còn được dùng trong Android 12) app:nudgeShortcutapp:nudgeShortcutDirection chỉ hỗ trợ một lối tắt nhấn nhẹ.

  4. (Android 11 QPR3, Android 11 Car, Android 12)
    Để bật tính năng xoay vòng trong FocusArea này, bạn có thể đặt app:wrapAround thành true. Phương thức này thường được dùng khi các thành phần hiển thị được sắp xếp theo hình tròn hoặc hình bầu dục.
  5. (Android 11 QPR3, Android 11 Car, Android 12)
    Để điều chỉnh khoảng đệm của phần đánh dấu trong FocusArea này, hãy sử dụng app:highlightPaddingStart, app:highlightPaddingEnd, app:highlightPaddingTop, app:highlightPaddingBottom, app:highlightPaddingHorizontalapp:highlightPaddingVertical.
  6. (Android 11 QPR3, Android 11 Car, Android 12)
    Để điều chỉnh các giới hạn được nhận biết của FocusArea này nhằm tìm mục tiêu nhấn nhẹ, hãy sử dụng app:startBoundOffset, app:endBoundOffset, app:topBoundOffset, app:bottomBoundOffset, app:horizontalBoundOffsetapp:verticalBoundOffset.
  7. (Android 11 QPR3, Android 11 Car, Android 12)
    Để chỉ định rõ mã nhận dạng của một FocusArea (hoặc các khu vực) liền kề theo hướng đã cho, hãy sử dụng app:nudgeLeft, app:nudgeRight, app:nudgeUpapp:nudgeDown. Sử dụng phương thức này khi phương thức tìm kiếm hình học được sử dụng theo mặc định không tìm thấy mục tiêu mong muốn.

Thao tác đẩy thường di chuyển giữa các FocusArea. Tuy nhiên, với lối tắt cho thao tác đẩy, đôi khi thao tác đẩy trước tiên sẽ điều hướng trong FocusArea để người dùng có thể cần đẩy hai lần để điều hướng đến FocusArea tiếp theo. Lối tắt Nudge (Đẩy) rất hữu ích khi FocusArea chứa một danh sách dài, theo sau là Nút hành động nổi, như trong ví dụ bên dưới:

Lối tắt cho thao tác đẩy
Hình 3. Phím tắt Nudge (Đẩy)

Nếu không có lối tắt nhấn nhẹ, người dùng sẽ phải xoay qua toàn bộ danh sách để đến FAB.

Tuỳ chỉnh tính năng làm nổi bật tiêu điểm

Như đã lưu ý ở trên, RotaryService được xây dựng dựa trên khái niệm hiện có của khung Android về tiêu điểm thành phần hiển thị. Khi người dùng xoay và đẩy, RotaryService sẽ di chuyển tiêu điểm xung quanh, tập trung vào một thành phần hiển thị và bỏ tập trung vào một thành phần hiển thị khác. Trong Android, khi một thành phần hiển thị được lấy tiêu điểm, nếu thành phần hiển thị đó:

  • Đã chỉ định điểm nổi bật tiêu điểm riêng, Android sẽ vẽ điểm nổi bật tiêu điểm của thành phần hiển thị.
  • Không chỉ định điểm nhấn tiêu điểm và điểm nhấn tiêu điểm mặc định không bị tắt, Android sẽ vẽ điểm nhấn tiêu điểm mặc định cho thành phần hiển thị.

Các ứng dụng được thiết kế để cảm ứng thường không chỉ định các điểm nổi bật phù hợp cho tiêu điểm.

Vùng làm nổi bật tiêu điểm mặc định do khung Android cung cấp và có thể bị nhà sản xuất thiết bị gốc ghi đè. Nhà phát triển ứng dụng sẽ nhận được thông báo này khi giao diện họ đang sử dụng được lấy từ Theme.DeviceDefault.

Để mang lại trải nghiệm nhất quán cho người dùng, hãy sử dụng tính năng làm nổi bật tiêu điểm mặc định bất cứ khi nào có thể. Nếu bạn cần một điểm nổi bật có hình dạng tuỳ chỉnh (ví dụ: hình tròn hoặc hình viên nang) hoặc nếu bạn đang sử dụng giao diện không bắt nguồn từ Theme.DeviceDefault, hãy sử dụng tài nguyên thư viện giao diện người dùng ô tô để chỉ định điểm nổi bật tiêu điểm của riêng bạn cho mỗi thành phần hiển thị.

Để chỉ định một điểm nổi bật tiêu điểm tuỳ chỉnh cho một thành phần hiển thị, hãy thay đổi nền hoặc đối tượng có thể vẽ ở nền trước của thành phần hiển thị thành một đối tượng có thể vẽ khác khi thành phần hiển thị được lấy làm tiêu điểm. Thông thường, bạn sẽ thay đổi nền. Đối tượng có thể vẽ sau đây, nếu được dùng làm nền cho thành phần hiển thị hình vuông, sẽ tạo ra một điểm nổi bật tập trung hình tròn:

<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) Các tệp tham chiếu tài nguyên in đậm trong mẫu trên xác định các tài nguyên do car-ui-library xác định. OEM ghi đè các giá trị này để nhất quán với điểm nhấn tiêu điểm mặc định mà họ chỉ định. Điều này đảm bảo rằng màu đánh dấu tiêu điểm, chiều rộng nét vẽ, v.v. không thay đổi khi người dùng di chuyển giữa một thành phần hiển thị có màu đánh dấu tiêu điểm tuỳ chỉnh và một thành phần hiển thị có màu đánh dấu tiêu điểm mặc định. Mục cuối cùng là hiệu ứng gợn sóng dùng cho thao tác chạm. Giá trị mặc định dùng cho tài nguyên in đậm sẽ xuất hiện như sau:

Giá trị mặc định cho tài nguyên in đậm
Hình 4. Giá trị mặc định cho tài nguyên in đậm

Ngoài ra, một điểm nhấn tiêu điểm tuỳ chỉnh sẽ được gọi khi một nút được đặt màu nền đồng nhất để thu hút sự chú ý của người dùng, như trong ví dụ bên dưới. Điều này có thể khiến bạn khó nhìn thấy điểm nhấn tiêu điểm. Trong trường hợp này, hãy chỉ định một điểm nhấn tiêu điểm tuỳ chỉnh bằng màu phụ:

Màu nền đồng nhất
  • (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

Ví dụ:

Đã lấy nét, chưa nhấn Đã tập trung, đã nhấn
Đã lấy tiêu điểm, chưa nhấn Đã lấy tiêu điểm, đã nhấn

Cuộn xoay

Nếu ứng dụng của bạn sử dụng RecyclerView, bạn NÊN sử dụng CarUiRecyclerView. Điều này đảm bảo giao diện người dùng của bạn nhất quán với các giao diện người dùng khác vì hoạt động tuỳ chỉnh của nhà sản xuất thiết bị gốc (OEM) áp dụng cho tất cả CarUiRecyclerView.

Nếu tất cả các phần tử trong danh sách đều có thể lấy tiêu điểm, thì bạn không cần làm gì thêm. Thao tác điều hướng xoay sẽ di chuyển tiêu điểm qua các phần tử trong danh sách và danh sách sẽ cuộn để hiển thị phần tử mới được lấy tiêu điểm.

(Android 11 QPR3, Android 11 Car, Android 12)
Nếu có sự kết hợp giữa các phần tử có thể lấy tiêu điểm và không lấy tiêu điểm, hoặc nếu tất cả các phần tử đều không lấy tiêu điểm, thì bạn có thể bật tính năng cuộn xoay. Tính năng này cho phép người dùng sử dụng bộ điều khiển xoay để cuộn dần qua danh sách mà không bỏ qua các mục không lấy tiêu điểm. Để bật tính năng cuộn xoay, hãy đặt thuộc tính app:rotaryScrollEnabled thành true.

(Android 11 QPR3, Android 11 Car, Android 12)
Bạn có thể bật tính năng cuộn xoay trong bất kỳ thành phần hiển thị cuộn nào, bao gồm cả avCarUiRecyclerView, bằng phương thức setRotaryScrollEnabled() trong CarUiUtils. Nếu làm như vậy, bạn cần:

  • Đặt chế độ xem cuộn thành có thể lấy tiêu điểm để có thể lấy tiêu điểm khi không có thành phần hiển thị con nào có thể lấy tiêu điểm xuất hiện,
  • Tắt tính năng làm nổi bật tiêu điểm mặc định trên thành phần hiển thị có thể cuộn bằng cách gọi setDefaultFocusHighlightEnabled(false) để thành phần hiển thị có thể cuộn không xuất hiện là được lấy tiêu điểm,
  • Đảm bảo rằng chế độ xem cuộn được lấy tiêu điểm trước các thành phần con của chế độ xem đó bằng cách gọi setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS).
  • Theo dõi MotionEvents bằng SOURCE_ROTARY_ENCODERAXIS_VSCROLL hoặc AXIS_HSCROLL để cho biết khoảng cách cần cuộn và hướng (thông qua dấu).

Khi tính năng cuộn xoay được bật trên CarUiRecyclerView và người dùng xoay sang một khu vực không có chế độ xem có thể lấy tiêu điểm, thanh cuộn sẽ thay đổi từ màu xám sang màu xanh dương, như thể cho biết thanh cuộn đang được lấy tiêu điểm. Bạn có thể triển khai hiệu ứng tương tự nếu muốn.

MotionEvents giống như các sự kiện do con lăn trên chuột tạo ra, ngoại trừ nguồn.

Chế độ thao tác trực tiếp

Thông thường, thao tác đẩy và xoay sẽ di chuyển qua giao diện người dùng, trong khi thao tác nhấn nút Trung tâm sẽ thực hiện hành động, mặc dù điều này không phải lúc nào cũng đúng. Ví dụ: nếu muốn điều chỉnh âm lượng chuông báo, người dùng có thể sử dụng bộ điều khiển xoay để chuyển đến thanh trượt âm lượng, nhấn nút Trung tâm, xoay bộ điều khiển để điều chỉnh âm lượng chuông báo, sau đó nhấn nút Quay lại để quay lại thao tác điều hướng. Đây được gọi là chế độ thao tác trực tiếp (DM). Ở chế độ này, bộ điều khiển xoay được dùng để tương tác trực tiếp với thành phần hiển thị thay vì để điều hướng.

Triển khai DM theo một trong hai cách. Nếu bạn chỉ cần xử lý việc xoay và thành phần hiển thị mà bạn muốn thao tác phản hồi ACTION_SCROLL_FORWARDACTION_SCROLL_BACKWARD AccessibilityEvent một cách thích hợp, hãy sử dụng cơ chế đơn giản. Nếu không, hãy sử dụng cơ chế nâng cao.

Cơ chế đơn giản là lựa chọn duy nhất trong cửa sổ hệ thống; các ứng dụng có thể sử dụng một trong hai cơ chế.

Cơ chế đơn giản

(Android 11 QPR3, Android 11 Car, Android 12)
Ứng dụng của bạn phải gọi DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable). RotaryService nhận biết thời điểm người dùng ở chế độ DM và chuyển sang chế độ DM khi người dùng nhấn nút Trung tâm trong khi một thành phần hiển thị được lấy làm tiêu điểm. Khi ở chế độ DM, các thao tác xoay sẽ thực hiện ACTION_SCROLL_FORWARD hoặc ACTION_SCROLL_BACKWARD và thoát khỏi chế độ DM khi người dùng nhấn nút Quay lại. Cơ chế đơn giản này sẽ bật/tắt trạng thái đã chọn của khung hiển thị khi vào và thoát khỏi chế độ DM.

Để cung cấp tín hiệu hình ảnh cho biết người dùng đang ở chế độ DM, hãy làm cho chế độ xem của bạn trông khác khi được chọn. Ví dụ: thay đổi nền khi android:state_selectedtrue.

Cơ chế nâng cao

Ứng dụng xác định thời điểm RotaryService chuyển sang và thoát khỏi chế độ DM. Để mang lại trải nghiệm nhất quán cho người dùng, khi nhấn nút Trung tâm với chế độ xem DM được lấy làm tâm điểm, bạn sẽ chuyển sang chế độ DM và nút Quay lại sẽ thoát khỏi chế độ DM. Nếu không sử dụng nút Trung tâm và/hoặc thao tác đẩy, bạn có thể sử dụng các cách khác để thoát khỏi chế độ tin nhắn trực tiếp. Đối với các ứng dụng như Maps, bạn có thể sử dụng nút đại diện cho DM để chuyển sang chế độ DM.

Để hỗ trợ chế độ DM nâng cao, thành phần hiển thị:

  1. (Android 11 QPR3, Android 11 Car, Android 12) PHẢI nghe sự kiện KEYCODE_DPAD_CENTER để chuyển sang chế độ DM và nghe sự kiện KEYCODE_BACK để thoát khỏi chế độ DM, gọi DirectManipulationHelper.enableDirectManipulationMode() trong mỗi trường hợp. Để theo dõi các sự kiện này, hãy làm theo một trong những cách sau:
    • Đăng ký OnKeyListener.
    • hoặc,
    • Mở rộng thành phần hiển thị rồi ghi đè phương thức dispatchKeyEvent() của thành phần đó.
  2. NÊN theo dõi các sự kiện nhắc nhở (KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT hoặc KEYCODE_DPAD_RIGHT) nếu thành phần hiển thị phải xử lý các thao tác nhắc.
  3. NÊN nghe MotionEvent và nhận số lượt xoay trong AXIS_SCROLL nếu thành phần hiển thị muốn xử lý việc xoay. Có một số cách để thực hiện việc này:
    1. Đăng ký OnGenericMotionListener.
    2. Mở rộng thành phần hiển thị và ghi đè phương thức dispatchTouchEvent() của thành phần hiển thị đó.
  4. Để tránh bị kẹt trong chế độ DM, BẮT BUỘC phải thoát khỏi chế độ DM khi Mảnh hoặc Hoạt động mà thành phần hiển thị thuộc về không có tính tương tác.
  5. NÊN cung cấp tín hiệu hình ảnh để cho biết chế độ xem đang ở chế độ DM.

Dưới đây là ví dụ về một thành phần hiển thị tuỳ chỉnh sử dụng chế độ DM để kéo và thu phóng bản đồ:

/** 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(); }

Bạn có thể xem thêm ví dụ trong dự án RotaryPlayground.

ActivityView

Khi sử dụng ActivityView:

  • ActivityView không được có tiêu điểm.
  • (Android 11 QPR3, Android 11 Car, không dùng nữa trong Android 11)
    Nội dung của ActivityView PHẢI chứa FocusParkingView làm thành phần hiển thị có thể lấy tiêu điểm đầu tiên và thuộc tính app:shouldRestoreFocus của thành phần hiển thị đó PHẢI là false.
  • Nội dung của ActivityView không được có thành phần hiển thị android:focusByDefault.

Đối với người dùng, ActivityViews không được ảnh hưởng đến hoạt động điều hướng, ngoại trừ việc các khu vực tiêu điểm không được trải dài trên ActivityViews. Nói cách khác, bạn không thể có một vùng tiêu điểm duy nhất có nội dung bên trong bên ngoài ActivityView. Nếu bạn không thêm FocusArea nào vào ActivityView, thì phần gốc của hệ phân cấp khung hiển thị trong ActivityView được coi là vùng tiêu điểm ngầm ẩn.

Nút hoạt động khi được giữ

Hầu hết các nút đều thực hiện một số thao tác khi được nhấp vào. Một số nút hoạt động khi bạn giữ. Ví dụ: các nút Tua nhanh và Tua lại thường hoạt động khi được giữ chặt. Để các nút như vậy hỗ trợ xoay, hãy theo dõi KEYCODE_DPAD_CENTER KeyEvents như sau:

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;
});

Trong đó, mRunnable thực hiện một hành động (chẳng hạn như tua lại) và tự lên lịch chạy sau một khoảng thời gian trễ.

Chế độ cảm ứng

Người dùng có thể sử dụng bộ điều khiển xoay để tương tác với đầu phát trung tâm trong ô tô theo hai cách, bằng cách sử dụng bộ điều khiển xoay hoặc bằng cách chạm vào màn hình. Khi sử dụng bộ điều khiển xoay, một trong các chế độ xem có thể lấy nét sẽ được làm nổi bật. Khi chạm vào màn hình, không có điểm nổi bật tiêu điểm nào xuất hiện. Người dùng có thể chuyển đổi giữa các chế độ nhập này bất cứ lúc nào:

  • Xoay → chạm. Khi người dùng chạm vào màn hình, điểm nhấn tiêu điểm sẽ biến mất.
  • Cảm ứng → xoay. Khi người dùng đẩy, xoay hoặc nhấn nút Trung tâm, điểm nhấn tiêu điểm sẽ xuất hiện.

Các nút Quay lại và Trang chủ không ảnh hưởng đến chế độ nhập.

Chế độ xoay dựa trên khái niệm hiện có của Android về chế độ cảm ứng. Bạn có thể sử dụng View.isInTouchMode() để xác định chế độ nhập mà người dùng đang sử dụng. Bạn có thể sử dụng OnTouchModeChangeListener để theo dõi các thay đổi. Mặc dù bạn có thể sử dụng tính năng này để tuỳ chỉnh giao diện người dùng cho chế độ nhập hiện tại, nhưng hãy tránh mọi thay đổi lớn vì chúng có thể gây khó chịu.

Khắc phục sự cố

Trong một ứng dụng được thiết kế để chạm, thường thì các thành phần hiển thị có thể lấy tiêu điểm sẽ được lồng. Ví dụ: có thể có một FrameLayout xung quanh ImageButton, cả hai đều có thể lấy tiêu điểm. Điều này không gây hại cho thao tác chạm nhưng có thể khiến người dùng có trải nghiệm không tốt khi xoay vì người dùng phải xoay tay điều khiển hai lần để chuyển sang chế độ xem tương tác tiếp theo. Để mang lại trải nghiệm tốt cho người dùng, Google khuyên bạn nên đặt tiêu điểm vào chế độ xem bên ngoài hoặc chế độ xem bên trong, nhưng không nên đặt tiêu điểm vào cả hai.

Nếu một nút hoặc nút chuyển mất tiêu điểm khi nhấn thông qua bộ điều khiển xoay, thì một trong các điều kiện sau đây có thể áp dụng:

  • Nút hoặc công tắc đang bị tắt (trong thời gian ngắn hoặc vô thời hạn) do nút đang được nhấn. Trong cả hai trường hợp, bạn có thể giải quyết vấn đề này theo hai cách:
    • Giữ nguyên trạng thái android:enabledtrue và sử dụng trạng thái tuỳ chỉnh để chuyển nút hoặc nút chuyển sang màu xám như mô tả trong phần Trạng thái tuỳ chỉnh.
    • Sử dụng vùng chứa để bao quanh nút hoặc nút chuyển và đặt tiêu điểm vào vùng chứa thay vì nút hoặc nút chuyển. (Trình nghe lượt nhấp phải nằm trên vùng chứa.)
  • Nút hoặc công tắc đang được thay thế. Ví dụ: thao tác được thực hiện khi nhấn nút hoặc bật/tắt nút chuyển có thể kích hoạt việc làm mới các thao tác có sẵn, khiến các nút mới thay thế các nút hiện có. Có hai cách để giải quyết vấn đề này:
    • Thay vì tạo nút hoặc nút chuyển mới, hãy đặt biểu tượng và/hoặc văn bản của nút hoặc nút chuyển hiện có.
    • Như trên, hãy thêm một vùng chứa có thể lấy tiêu điểm xung quanh nút hoặc nút chuyển.

RotaryPlayground

RotaryPlayground là một ứng dụng tham chiếu cho phương thức nhập dữ liệu xoay. Hãy sử dụng lớp này để tìm hiểu cách tích hợp các tính năng xoay vào ứng dụng của bạn. RotaryPlayground có trong các bản dựng trình mô phỏng và trong các bản dựng dành cho thiết bị chạy Android Automotive OS (AAOS).

  • Kho lưu trữ RotaryPlayground: packages/apps/Car/tests/RotaryPlayground/
  • Phiên bản: Android 11 QPR3, Android 11 Car và Android 12

Ứng dụng RotaryPlayground hiển thị các thẻ sau ở bên trái:

  • Thẻ. Kiểm thử thao tác di chuyển xung quanh các khu vực tiêu điểm, bỏ qua các phần tử không thể lấy tiêu điểm và nhập văn bản.
  • Thao tác trực tiếp. Kiểm thử các tiện ích hỗ trợ chế độ thao tác trực tiếp đơn giản và nâng cao. Thẻ này dành riêng cho thao tác trực tiếp trong cửa sổ ứng dụng.
  • Thao tác trên giao diện người dùng Sys. Kiểm thử các tiện ích hỗ trợ thao tác trực tiếp trong cửa sổ hệ thống chỉ hỗ trợ chế độ thao tác trực tiếp đơn giản.
  • Lưới. Kiểm thử thao tác điều hướng xoay theo mẫu z bằng tính năng cuộn.
  • Thông báo. Kiểm thử thao tác đẩy vào và đẩy ra thông báo quan trọng.
  • Cuộn. Kiểm thử thao tác cuộn qua nội dung có thể lấy tiêu điểm và không thể lấy tiêu điểm.
  • WebView. Kiểm thử thao tác điều hướng qua các đường liên kết trong WebView.
  • FocusArea tuỳ chỉnh. Kiểm thử tuỳ chỉnh FocusArea:
    • Bao quanh.
    • android:focusedByDefaultapp:defaultFocus
    • .
    • Mục tiêu nhắc nhở rõ ràng.
    • Lối tắt cho thao tác đẩy.
    • FocusArea không có thành phần hiển thị có thể lấy tiêu điểm.