Lớp học lập trình: Tạo RRO với các thành phần car-ui-lib bằng hệ thống xây dựng Gradle

Sử dụng thư viện car-ui-lib để chạy các hệ thống thông tin giải trí (IVI) nhất quán và độc lập trên xe. Lớp học lập trình này giới thiệu cho bạn về car-ui-lib và cách bạn có thể sử dụng lớp phủ tài nguyên thời gian chạy (RRO) để tuỳ chỉnh các thành phần trong thư viện.

Kiến thức bạn sẽ học được

Cách:

  • Thêm các thành phần car-ui-lib vào ứng dụng Android của bạn.
  • Sử dụng Gradle để tạo ứng dụng Android và RRO.
  • Sử dụng RRO với car-ui-lib.

Lớp học lập trình này không trình bày chi tiết cách hoạt động của RRO. Hãy xem phần Thay đổi giá trị của tài nguyên ứng dụng trong thời gian chạyKhắc phục sự cố về lớp phủ tài nguyên trong thời gian chạy để tìm hiểu thêm.

Trước khi bắt đầu

Điều kiện tiên quyết

Trước khi bắt đầu, hãy đảm bảo rằng bạn có:

  • Máy tính có dòng lệnh (máy Linux, máy Mac hoặc máy Windows có Hệ thống con Windows cho Linux).

  • Android Studio.

  • Trình mô phỏng hoặc thiết bị Android được kết nối với máy tính của bạn. Xem phần Tải nguồn Android xuốngTạo Android.

  • Kiến thức cơ bản về RRO.

Tạo một ứng dụng Android mới

Thời lượng: 15 phút

Trong phần này, bạn sẽ tạo một dự án Android Studio mới.

  1. Trong Android Studio, hãy tạo một ứng dụng có EmptyActivity.

    Tạo một Hoạt động trống
    Hình 1.Tạo một Hoạt động trống
  2. Đặt tên cho ứng dụng CarUiCodelab rồi chọn ngôn ngữ Java. Bạn cũng có thể chọn vị trí lưu tệp nếu muốn. Chấp nhận các giá trị mặc định cho các chế độ cài đặt còn lại.

     Đặt tên cho ứng dụng
    Hình 2. Đặt tên cho ứng dụng của bạn
  3. Thay thế activity_main.xml bằng khối mã sau:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/sample_text"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    Khối mã này hiển thị chuỗi sample_text chưa được xác định.

  4. Thêm chuỗi tài nguyên sample_text và đặt thành "Hello World!" trong tệp strings.xml. Để mở tệp này, hãy chọn app > src > main > res > values > strings.xml.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_name">CarUiCodelab</string>
        <string name="sample_text">Hello World!</string>
    </resources>
    
  5. Để tạo ứng dụng, hãy nhấp vào nút Phát màu xanh lục ở trên cùng bên phải. Thao tác này sẽ tự động cài đặt apk vào trình mô phỏng hoặc thiết bị Android thông qua Gradle.

    Nút phát

Ứng dụng mới sẽ tự động mở trên trình mô phỏng hoặc thiết bị Android của bạn. Nếu không, hãy mở ứng dụng CarUiCodelab qua trình chạy ứng dụng (ứng dụng này hiện đã được cài đặt). Thông báo sẽ có dạng như sau:

Mở ứng dụng CarUiCodelab mới
Hình 3. Mở ứng dụng CarUiCodelab mới

Thêm car-ui-lib vào ứng dụng Android

Thời lượng: 15 phút

Thêm car-ui-lib vào ứng dụng của bạn:

  1. Để thêm phần phụ thuộc car-ui-lib vào tệp build.gradle của dự án, hãy chọn app > build.gradle. Các phần phụ thuộc của bạn sẽ có dạng như sau:

    dependencies {
        implementation 'com.android.car.ui:car-ui-lib:2.0.0'
        implementation 'androidx.appcompat:appcompat:1.4.1'
        implementation 'com.google.android.material:material:1.4.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    }
    

Sử dụng các thành phần car-ui-lib trong ứng dụng Android

Giờ đây, khi đã có car-ui-lib, hãy thêm một thanh công cụ vào ứng dụng của bạn.

  1. Trong tệp MainActivity.java, hãy ghi đè phương thức onCreate:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Get the toolbar controller instance.
        ToolbarController toolbar = CarUi.getToolbar(this);
        // Set the title on toolbar.
        toolbar.setTitle(getTitle());
        // Set the logo to be shown with the title.
        toolbar.setLogo(R.mipmap.ic_launcher_round);
    }
    
  2. Hãy nhớ nhập ToolbarController:

    import com.android.car.ui.core.CarUi;
    import com.android.car.ui.toolbar.ToolbarController;
    
  3. Để sử dụng giao diện Theme.CarUi.WithToolbar, hãy chọn app > src > main > AndroidManifest.xml rồi cập nhật AndroidManifest.xml để xuất hiện như sau:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.example.caruicodelab">
    
        <application
            android:allowBackup="true"
            android:dataExtractionRules="@xml/data_extraction_rules"
            android:fullBackupContent="@xml/backup_rules"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.CarUi.WithToolbar"
            tools:targetApi="31">
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    
  4. Để tạo ứng dụng, hãy nhấn vào nút Play (Phát) màu xanh lục như trước.

    Tạo ứng dụng

Thêm RRO vào ứng dụng

Thời lượng: 30 phút

Nếu bạn đã quen thuộc với RRO, hãy chuyển đến phần tiếp theo: Thêm bộ điều khiển quyền vào ứng dụng. Nếu không, để tìm hiểu những kiến thức cơ bản về RRO, hãy xem phần Thay đổi giá trị của tài nguyên ứng dụng trong thời gian chạy.

Thêm bộ điều khiển quyền vào ứng dụng

Để kiểm soát những tài nguyên mà gói RRO sẽ phủ lên, hãy thêm một tệp có tên là overlayable.xml vào thư mục /res của ứng dụng. Tệp này đóng vai trò là bộ điều khiển quyền giữa ứng dụng của bạn (mục tiêu) và gói RRO (lớp phủ).

  1. Thêm res/values/overlayable.xml vào ứng dụng rồi sao chép nội dung sau vào tệp:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <overlayable name="CarUiCodelab">
            <policy type="public">
                <item type="string" name="sample_text"/>
            </policy>
        </overlayable>
    </resources>
    

    Vì chuỗi sample_text phải được RRO phủ lên, hãy thêm tên tài nguyên vào overlayable.xml của ứng dụng.

    Tệp overlayable.xml của bạn PHẢI nằm trong res/values/. Nếu không, OverlayManagerService sẽ không thể xác định vị trí của tệp.

    Để tìm hiểu thêm về tài nguyên có thể phủ lên và cách định cấu hình các tài nguyên này, hãy xem phần Hạn chế tài nguyên có thể phủ lên.

Tạo gói RRO

Trong phần này, bạn sẽ tạo một gói RRO để thay đổi chuỗi hiển thị ở trên từ "Hello World!" thành "Hello World RRO".

  1. Để tạo một dự án mới, hãy chọn File > New > New Project (Tệp > Mới > Dự án mới). Nhớ chọn No Activity (Không có hoạt động) thay vì Empty Activity (Hoạt động trống) vì các gói RRO chỉ chứa tài nguyên.

    Cấu hình của bạn sẽ xuất hiện tương tự như những cấu hình minh hoạ bên dưới. Vị trí lưu các tệp này có thể khác nhau:

  2. Sau khi tạo dự án CarUiRRO mới, hãy khai báo dự án đó dưới dạng RRO bằng cách sửa đổi AndroidManifest.xml.

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.caruirro">
    
        <application android:hasCode="false" />
    
        <uses-sdk
            android:minSdkVersion="29"
            android:targetSdkVersion="29"/>
    
        <overlay
            android:targetPackage="com.example.caruicodelab"
            android:targetName="CarUiCodelab"
            android:isStatic="false"
            android:resourcesMap="@xml/sample_overlay"
            />
    </manifest>
    

    Việc này sẽ gây ra lỗi với @xml/sample_overlay. Tệp resourcesMap ánh xạ tên tài nguyên từ gói đích đến gói RRO. Bạn bắt buộc phải đặt cờ hasCode thành false đối với các gói RRO. Ngoài ra, các gói RRO không được phép chứa tệp DEX.

  3. Sao chép khối mã sau vào …/res/xml/sample_overlay.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
        <item target="string/sample_text" value="@string/sample_text"/>
    </overlay>
    
  4. Thêm sample_text vào …/res/values/strings.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_name">CarUiRRO</string>
        <string name="sample_text">Hello World RRO</string>
    </resources>
    
    Đã tạo bản dựng Gradle của RRO
  5. Để tạo mục tiêu RRO, hãy nhấn vào nút Phát màu xanh lục để tạo bản dựng Gradle cho RRO trên trình mô phỏng hoặc thiết bị Android.

  6. Để xác minh rằng RRO đã được cài đặt đúng cách, hãy chạy:

    shell:~$ adb shell cmd overlay list --user current | grep -i com.example
    com.example.caruicodelab
    [ ] com.example.caruirro
    

    Lệnh này hiển thị thông tin hữu ích về trạng thái của các gói RRO trên hệ thống.

    • [ ] cho biết RRO đã được cài đặt và sẵn sàng kích hoạt.
    • --- cho biết RRO đã được cài đặt nhưng có lỗi.
    • [X] có nghĩa là RRO đã được cài đặt và kích hoạt.

    Nếu RRO của bạn có lỗi, hãy xem phần Khắc phục sự cố về lớp phủ tài nguyên thời gian chạy trước khi tiếp tục.

  7. Cách bật RRO và xác minh rằng RRO đã được bật:

    shell:~$ adb shell cmd overlay enable --user current com.example.caruirro
    shell:~$ adb shell cmd overlay list --user current | grep -i com.example
    com.example.caruicodelab
    [x] com.example.caruirro
    

Ứng dụng của bạn sẽ hiển thị chuỗi "Hello World RRO".

Hello World RRO!
Hình 4: Hello World RRO!

Xin chúc mừng! Bạn đã tạo RRO đầu tiên.

Khi sử dụng RRO, bạn có thể muốn sử dụng các cờ --no-resource-deduping--no-resource-removal của Công cụ đóng gói tài nguyên Android (AAPT2) như mô tả trong phần Lựa chọn liên kết. Bạn không cần thêm các cờ trong lớp học lập trình này, nhưng bạn nên sử dụng chúng trong RRO để tránh việc xoá tài nguyên (và các vấn đề gỡ lỗi). Bạn có thể thêm các quy tắc này vào tệp build.gradle của RRO như sau:

android {
    …
    aaptOptions {
        additionalParameters "--no-resource-deduping", "--no-resource-removal"
    }
}

Để tìm hiểu thêm về các cờ này, hãy xem phần Tạo góiAAPT2.

Sửa đổi các thành phần car-ui-lib bằng RRO trong ứng dụng Android

Trang này mô tả cách bạn có thể sử dụng lớp phủ tài nguyên thời gian chạy (RRO) để sửa đổi các thành phần trong thư viện car-ui-lib trong ứng dụng Android.

Đặt màu nền của thanh công cụ

Thời lượng: 15 phút

Cách thay đổi màu nền của thanh công cụ:

  1. Thêm giá trị sau vào ứng dụng RRO và đặt tài nguyên thành màu xanh lục tươi (#0F0):

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <drawable name="car_ui_toolbar_background">#0F0</drawable>
    </resources>
    

    Thư viện car-ui-lib chứa một tài nguyên có tên là car_ui_toolbar_background. Khi tài nguyên này nằm trong cấu hình của một RRO, thanh công cụ sẽ không thay đổi vì giá trị sai được nhắm đến.

  2. Trong AndroidManifest.xml cho RRO, hãy cập nhật targetName để trỏ đến car-ui-lib:

    …
    android:targetName="car-ui-lib"
    …
    

    Bạn PHẢI tạo một gói RRO mới cho mỗi gói đích mà bạn muốn RRO. Ví dụ: khi tạo lớp phủ cho 2 mục tiêu khác nhau, bạn phải tạo 2 tệp apk lớp phủ.

  3. Tạo, xác minh, cài đặt và bật RRO theo cách tương tự như trước đây.

Ứng dụng của bạn sẽ có dạng như sau:

Màu nền của thanh công cụ mới
Hình 5: Màu nền mới của thanh công cụ

Bố cục và kiểu RRO

Thời lượng: 15 phút

Trong bài tập này, bạn sẽ tạo một ứng dụng mới tương tự như ứng dụng mà bạn đã tạo trước đó. Ứng dụng này cho phép bố cục được phủ lên. Làm theo các bước tương tự như trước hoặc sửa đổi ứng dụng hiện có.

  1. Hãy nhớ thêm các dòng sau vào overlayable.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
      <overlayable name="CarUiCodelab">
        <policy type="public">
          <item type="string" name="sample_text"/>
          <item type="layout" name="activity_main"/>
          <item type="id" name="textView"/>
        </policy>
      </overlayable>
    </resources>
    
  2. Đảm bảo activity_main.xml xuất hiện như sau:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/sample_text"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  3. Trong ứng dụng RRO, hãy tạo một res/layout/activity_main.xml rồi thêm nội dung sau:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
      <TextView
          android:id="@+id/textView"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@string/sample_text"
          android:textAppearance="@style/TextAppearance.CarUi"
          android:layout_gravity="center_vertical|center_horizontal"/>
    </FrameLayout>
    
  4. Cập nhật res/values/styles.xml để thêm kiểu của chúng ta vào RRO:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <style name="TextAppearance.CarUi" parent="android:TextAppearance.DeviceDefault">
            <item name="android:textColor">#0f0</item>
            <item name="android:textSize">100sp</item>
        </style>
    </resources>
    
  5. Thay đổi targetName trong AndroidManifest.xml để trỏ đến tên của ứng dụng mới:

    …
    android:targetName="CarUiCodelab"
    …
    
  6. Thêm tài nguyên vào tệp sample_overlay.xml trong RRO:

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
        <item target="string/sample_text" value="@string/sample_text"/>
        <item target="id/textView" value="@id/textView"/>
        <item target="layout/activity_main" value="@layout/activity_main"/>
    </overlay>
    
  7. Tạo và cài đặt ứng dụng cũng như RRO theo cách tương tự như trước đây (nút Play màu xanh lục). Nhớ bật RRO.

Ứng dụng và RRO hiển thị như sau. Văn bản Hello World RRO có màu xanh lục và được căn giữa như quy định trong RRO bố cục.

RRO Hello World
Hình 6: RRO Xin chào thế giới

Thêm CarUiRecyclerView vào ứng dụng

Thời lượng: 15 phút

Giao diện CarUiRecyclerView cung cấp các API để truy cập vào một RecyclerView được tuỳ chỉnh thông qua các tài nguyên car-ui-lib. Ví dụ: CarUiRecyclerView kiểm tra một cờ tại thời gian chạy để xác định xem có nên bật thanh cuộn hay không và chọn bố cục tương ứng.

CarUiRecyclerViewContainer
Hình 7. CarUiRecyclerViewContainer
  1. Để thêm một CarUiRecyclerView, hãy thêm CarUiRecyclerView đó vào các tệp activity_main.xmlMainActivity.java. Bạn có thể tạo một ứng dụng mới từ đầu hoặc sửa đổi ứng dụng hiện có. Nếu sửa đổi ứng dụng hiện có, hãy nhớ xoá các tài nguyên chưa khai báo khỏi overlayable.xml.

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <com.android.car.ui.recyclerview.CarUiRecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    

    Bạn có thể bỏ qua lỗi sau đây:

    Cannot resolve class com.android.car.ui.recyclerview.CarUiRecyclerView

    Miễn là bạn viết đúng chính tả lớp và đã thêm car-ui-lib làm phần phụ thuộc, bạn có thể tạo và biên dịch apk. Để xoá lỗi, hãy chọn File > Invalidate Caches (Tệp > Huỷ bộ nhớ đệm) rồi nhấp vào Invalidate and Restart (Huỷ và khởi động lại).

    Thêm nội dung sau vào MainActivity.java

    package com.example.caruicodelab;
    
    import android.app.Activity;
    import android.os.Bundle;
    import com.android.car.ui.core.CarUi;
    import com.android.car.ui.recyclerview.CarUiContentListItem;
    import com.android.car.ui.recyclerview.CarUiListItem;
    import com.android.car.ui.recyclerview.CarUiListItemAdapter;
    import com.android.car.ui.recyclerview.CarUiRecyclerView;
    import com.android.car.ui.toolbar.ToolbarController;
    import java.util.ArrayList;
    
    /** Activity with a simple car-ui layout. */
    public class MainActivity extends Activity {
    
        private final ArrayList<CarUiListItem> mData = new ArrayList<>();
        private CarUiListItemAdapter mAdapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ToolbarController toolbar = CarUi.getToolbar(this);
            toolbar.setTitle(getTitle());
            toolbar.setLogo(R.mipmap.ic_launcher_round);
    
            CarUiRecyclerView recyclerView = findViewById(R.id.list);
            mAdapter = new CarUiListItemAdapter(generateSampleData());
            recyclerView.setAdapter(mAdapter);
        }
    
        private ArrayList<CarUiListItem> generateSampleData() {
            for (int i = 0; i < 20; i++) {
                CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.ICON);
                item.setTitle("Title " + i);
                item.setPrimaryIconType(CarUiContentListItem.IconType.CONTENT);
                item.setIcon(getDrawable(R.drawable.ic_launcher_foreground));
                item.setBody("body " + i);
                mData.add(item);
            }
            return mData;
    }
    
  2. Tạo bản dựng và cài đặt ứng dụng của bạn như trước đây.

Giờ đây, bạn sẽ thấy biểu tượng CarUiRecyclerView:

CarUiRecyclerView
Hình 7 : CarUiRecyclerView

Dùng RRO để xoá thanh cuộn

Thời lượng: 10 phút

Bài tập này hướng dẫn bạn cách sử dụng RRO để xoá thanh cuộn khỏi CarUiRecyclerView.

  1. Trong RRO, hãy thêm và sửa đổi các tệp sau:

    AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.caruirro">
    
        <application android:hasCode="false" />
    
        <uses-sdk
            android:minSdkVersion="29"
            android:targetSdkVersion="29"/>
    
        <overlay
            android:targetPackage="com.example.caruicodelab"
            android:targetName="car-ui-lib"
            android:isStatic="false"
            android:resourcesMap="@xml/sample_overlay"
            />
    </manifest>
    

    res/values/bools.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <bool name="car_ui_scrollbar_enable">false</bool>
    </resources>
    

    Tài nguyên car_ui_scrollbar_enable là một tài nguyên car-ui-lib boolean, kiểm soát việc thanh cuộn được tối ưu hoá cho ô tô có các nút Lên và Xuống trong CarUiRecyclerView có xuất hiện hay không. Khi được đặt thành false, CarUiRecyclerView sẽ hoạt động như một RecyclerView AndroidX.

    res/xml/sample_overlay.xml

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
        <item target="bool/car_ui_scrollbar_enable" value="@bool/car_ui_scrollbar_enable"/>
    </overlay>
    

Tạo bản dựng và cài đặt ứng dụng của bạn như trước đây. Thanh cuộn hiện đã bị xoá khỏi CarUiRecyclerView:

CarUiRecyclerView không có thanh cuộn
Hình 8. CarUiRecyclerView không có thanh cuộn

Sử dụng một bố cục để phủ lên thanh cuộn CarUiRecyclerView

Thời lượng: 15 phút

Trong bài tập này, bạn sẽ sửa đổi bố cục thanh cuộn CarUiRecyclerView.

  1. Thêm và sửa đổi các tệp sau trong ứng dụng RRO.

    res/layout/car_ui_recycler_view_scrollbar.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="112dp"
        android:layout_height="match_parent"
        android:id="@+id/car_ui_scroll_bar">
        <!-- View height is dynamically calculated during layout. -->
        <View
            android:id="@+id/car_ui_scrollbar_thumb"
            android:layout_width="6dp"
            android:layout_height="20dp"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:background="@drawable/car_ui_recyclerview_scrollbar_thumb"/>
        <View
            android:id="@+id/car_ui_scrollbar_track"
            android:layout_width="10dp"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:layout_centerHorizontal="true"
            android:layout_above="@+id/car_ui_scrollbar_page_up"/>
        <View
            android:layout_width="2dp"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:background="#323232"
            android:layout_toLeftOf="@+id/car_ui_scrollbar_thumb"
            android:layout_above="@+id/car_ui_scrollbar_page_up"
            android:layout_marginRight="5dp"/>
        <View
            android:layout_width="2dp"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:background="#323232"
            android:layout_toRightOf="@+id/car_ui_scrollbar_thumb"
            android:layout_above="@+id/car_ui_scrollbar_page_up"
            android:layout_marginLeft="5dp"/>
        <ImageView
            android:id="@+id/car_ui_scrollbar_page_up"
            android:layout_width="75dp"
            android:layout_height="75dp"
            android:focusable="false"
            android:hapticFeedbackEnabled="false"
            android:src="@drawable/car_ui_recyclerview_ic_up"
            android:scaleType="centerInside"
            android:background="?android:attr/selectableItemBackgroundBorderless"
            android:layout_centerHorizontal="true"
            android:layout_above="@+id/car_ui_scrollbar_page_down"/>
        <ImageView
            android:id="@+id/car_ui_scrollbar_page_down"
            android:layout_width="75dp"
            android:layout_height="75dp"
            android:focusable="false"
            android:hapticFeedbackEnabled="false"
            android:src="@drawable/car_ui_recyclerview_ic_down"
            android:scaleType="centerInside"
            android:background="?android:attr/selectableItemBackgroundBorderless"
            android:layout_centerHorizontal="true"
            android:layout_alignParentBottom="true"/>
    </RelativeLayout>
    

    Để phủ một tệp bố cục, bạn phải thêm tất cả các mã nhận dạng và thuộc tính không gian tên vào overlay.xml của RRO. Xem các tệp bên dưới.

    res/xml/sample_overlay.xml

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
       <item target="drawable/car_ui_recyclerview_ic_down" value="@drawable/car_ui_recyclerview_ic_down"/>
       <item target="drawable/car_ui_recyclerview_ic_up" value="@drawable/car_ui_recyclerview_ic_up"/>
       <item target="drawable/car_ui_recyclerview_scrollbar_thumb" value="@drawable/car_ui_recyclerview_scrollbar_thumb"/>
       <item target="id/car_ui_scroll_bar" value="@id/car_ui_scroll_bar"/>
       <item target="id/car_ui_scrollbar_thumb" value="@id/car_ui_scrollbar_thumb"/>
       <item target="id/car_ui_scrollbar_track" value="@id/car_ui_scrollbar_track"/>
       <item target="id/car_ui_scrollbar_page_up" value="@id/car_ui_scrollbar_page_up"/>
       <item target="id/car_ui_scrollbar_page_down" value="@id/car_ui_scrollbar_page_down"/>
       <item target="layout/car_ui_recyclerview_scrollbar" value="@layout/car_ui_recyclerview_scrollbar"/>
    </overlay>
    

    res/drawable/car_ui_recyclerview_ic_up.xml

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="48dp"
        android:height="48dp"
        android:viewportWidth="48.0"
        android:viewportHeight="48.0">
        <path
            android:pathData="M14.83,30.83L24,21.66l9.17,9.17L36,28 24,16 12,28z"
            android:fillColor="#0000FF"/>
    </vector>
    

    res/drawable/car_ui_recyclerview_ic_down.xml

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="48dp"
        android:height="48dp"
        android:viewportWidth="48.0"
        android:viewportHeight="48.0">
        <path
            android:pathData="M14.83,16.42L24,25.59l9.17,-9.17L36,19.25l-12,12 -12,-12z"
            android:fillColor="#0000FF"/>
    </vector>
    

    res/drawable/car_ui_recyclerview_scrollbar_thumb.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <solid android:color="#0000FF" />
        <corners android:radius="100dp"/>
    </shape>
    

    Bạn nên kiểm tra cách các tệp này tương tác với nhau.

    Để đơn giản, các phương diện và màu sắc được mã hoá cứng. Tuy nhiên, cách hay nhất là khai báo các giá trị này trong dimens.xmlcolors.xml hoặc thậm chí chỉ định làm tệp màu trong thư mục res/color/. Để tìm hiểu thêm, hãy xem Kiểu mã Java AOSP dành cho người đóng góp.

  2. Tạo bản dựng và cài đặt ứng dụng của bạn như trước đây. Bạn đã tạo CarUiRecyclerView có thanh cuộn màu xanh dương và các thanh ray màu xám.

Xin chúc mừng! Cả hai mũi tên đều xuất hiện ở cuối thanh cuộn. Bạn đã áp dụng thành công một RRO cho tệp tài nguyên bố cục car-ui-lib bằng hệ thống bản dựng Gradle thông qua Android Studio.

CarUiRecyclerView có thanh cuộn màu xanh dương với các đường ray màu xám
Hình 9. CarUiRecyclerView có thanh cuộn màu xanh dương với các thanh ray màu xám

Các mục trong danh sách RRO

Thời lượng: 15 phút

Đến thời điểm này, bạn đã áp dụng một RRO cho các thành phần car-ui-lib bằng cách sử dụng các thành phần khung (không phải AndroidX). Để sử dụng các thành phần AndroidX trong RRO, bạn phải thêm các phần phụ thuộc của thành phần đó vào cả ứng dụng và RRO build.gradle. Bạn cũng phải thêm attrs của thành phần đó vào overlayable.xml trong ứng dụng, cũng như sample_overlay.xml trong RRO.

Thư viện của chúng tôi (car-ui-lib) sử dụng ConstraintLayout cũng như các thành phần AndroidX khác, vì vậy, overlayable.xml của thư viện có thể trông như sau:

<?xml version='1.0' encoding='UTF-8'?>
<resources>
    <overlayable name="car-ui-lib">
        
        <item type="attr" name="layout_constraintBottom_toBottomOf"/>
        <item type="attr" name="layout_constraintBottom_toTopOf"/>
        <item type="attr" name="layout_constraintCircle"/>
        <item type="attr" name="layout_constraintCircleAngle"/>
        <item type="attr" name="layout_constraintCircleRadius"/>
        <item type="attr" name="layout_constraintDimensionRatio"/>
        <item type="attr" name="layout_constraintEnd_toEndOf"/>
        <item type="attr" name="layout_constraintEnd_toStartOf"/>
        <item type="attr" name="layout_constraintGuide_begin"/>
        <item type="attr" name="layout_constraintGuide_end"/>
        <item type="attr" name="layout_constraintGuide_percent"/>
        <item type="attr" name="layout_constraintHeight_default"/>
        <item type="attr" name="layout_constraintHeight_max"/>
        <item type="attr" name="layout_constraintHeight_min"/>
        <item type="attr" name="layout_constraintHeight_percent"/>
        <item type="attr" name="layout_constraintHorizontal_bias"/>
        <item type="attr" name="layout_constraintHorizontal_chainStyle"/>
        <item type="attr" name="layout_constraintHorizontal_weight"/>
        <item type="attr" name="layout_constraintLeft_creator"/>
        <item type="attr" name="layout_constraintLeft_toLeftOf"/>
        <item type="attr" name="layout_constraintLeft_toRightOf"/>
        <item type="attr" name="layout_constraintRight_creator"/>
        <item type="attr" name="layout_constraintRight_toLeftOf"/>
        <item type="attr" name="layout_constraintRight_toRightOf"/>
        <item type="attr" name="layout_constraintStart_toEndOf"/>
        <item type="attr" name="layout_constraintStart_toStartOf"/>
        <item type="attr" name="layout_constraintTag"/>
        <item type="attr" name="layout_constraintTop_creator"/>
        <item type="attr" name="layout_constraintTop_toBottomOf"/>
        <item type="attr" name="layout_constraintTop_toTopOf"/>
        <item type="attr" name="layout_constraintVertical_bias"/>
        <item type="attr" name="layout_constraintVertical_chainStyle"/>
        
    </overlayable>
</resources>
  1. Thay đổi bố cục của các mục trong danh sách trong CarUiRecyclerView bằng cách sử dụng ConstraintLayout. Thêm hoặc sửa đổi các tệp sau trong RRO:

    res/xml/sample_overlay.xml

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
       <item target="id/car_ui_list_item_touch_interceptor" value="@id/car_ui_list_item_touch_interceptor"/>
       <item target="id/car_ui_list_item_reduced_touch_interceptor" value="@id/car_ui_list_item_reduced_touch_interceptor"/>
       <item target="id/car_ui_list_item_start_guideline" value="@id/car_ui_list_item_start_guideline"/>
       <item target="id/car_ui_list_item_icon_container" value="@id/car_ui_list_item_icon_container"/>
       <item target="id/car_ui_list_item_icon" value="@id/car_ui_list_item_icon"/>
       <item target="id/car_ui_list_item_content_icon" value="@id/car_ui_list_item_content_icon"/>
       <item target="id/car_ui_list_item_avatar_icon" value="@id/car_ui_list_item_avatar_icon"/>
       <item target="id/car_ui_list_item_title" value="@id/car_ui_list_item_title"/>
       <item target="id/car_ui_list_item_body" value="@id/car_ui_list_item_body"/>
       <item target="id/car_ui_list_item_action_container_touch_interceptor" value="@id/car_ui_list_item_action_container_touch_interceptor"/>
       <item target="id/car_ui_list_item_action_container" value="@id/car_ui_list_item_action_container"/>
       <item target="id/car_ui_list_item_action_divider" value="@id/car_ui_list_item_action_divider"/>
       <item target="id/car_ui_list_item_switch_widget" value="@id/car_ui_list_item_switch_widget"/>
       <item target="id/car_ui_list_item_checkbox_widget" value="@id/car_ui_list_item_checkbox_widget"/>
       <item target="id/car_ui_list_item_radio_button_widget" value="@id/car_ui_list_item_radio_button_widget"/>
       <item target="id/car_ui_list_item_supplemental_icon" value="@id/car_ui_list_item_supplemental_icon"/>
       <item target="id/car_ui_list_item_end_guideline" value="@id/car_ui_list_item_end_guideline"/>
       <item target="attr/layout_constraintBottom_toBottomOf" value="@attr/layout_constraintBottom_toBottomOf"/>
       <item target="attr/layout_constraintBottom_toTopOf" value="@attr/layout_constraintBottom_toTopOf"/>
       <item target="attr/layout_constraintEnd_toEndOf" value="@attr/layout_constraintEnd_toEndOf"/>
       <item target="attr/layout_constraintEnd_toStartOf" value="@attr/layout_constraintEnd_toStartOf"/>
       <item target="attr/layout_constraintGuide_begin" value="@attr/layout_constraintGuide_begin"/>
       <item target="attr/layout_constraintGuide_end" value="@attr/layout_constraintGuide_end"/>
       <item target="attr/layout_constraintHorizontal_bias" value="@attr/layout_constraintHorizontal_bias"/>
       <item target="attr/layout_constraintLeft_toLeftOf" value="@attr/layout_constraintLeft_toLeftOf"/>
       <item target="attr/layout_constraintLeft_toRightOf" value="@attr/layout_constraintLeft_toRightOf"/>
       <item target="attr/layout_constraintRight_toLeftOf" value="@attr/layout_constraintRight_toLeftOf"/>
       <item target="attr/layout_constraintRight_toRightOf" value="@attr/layout_constraintRight_toRightOf"/>
       <item target="attr/layout_constraintStart_toEndOf" value="@attr/layout_constraintStart_toEndOf"/>
       <item target="attr/layout_constraintStart_toStartOf" value="@attr/layout_constraintStart_toStartOf"/>
       <item target="attr/layout_constraintTop_toBottomOf" value="@attr/layout_constraintTop_toBottomOf"/>
       <item target="attr/layout_constraintTop_toTopOf" value="@attr/layout_constraintTop_toTopOf"/>
       <item target="attr/layout_goneMarginBottom" value="@attr/layout_goneMarginBottom"/>
       <item target="attr/layout_goneMarginEnd" value="@attr/layout_goneMarginEnd"/>
       <item target="attr/layout_goneMarginLeft" value="@attr/layout_goneMarginLeft"/>
       <item target="attr/layout_goneMarginRight" value="@attr/layout_goneMarginRight"/>
       <item target="attr/layout_goneMarginStart" value="@attr/layout_goneMarginStart"/>
       <item target="attr/layout_goneMarginTop" value="@attr/layout_goneMarginTop"/>
       <item target="attr/layout_constraintVertical_chainStyle" value="@attr/layout_constraintVertical_chainStyle"/>
       <item target="layout/car_ui_list_item" value="@layout/car_ui_list_item"/>
    </overlay>
    

    res/layout/car_ui_list_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:tag="carUiListItem"
        android:minHeight="@dimen/car_ui_list_item_height">
    
        <!-- The following touch interceptor views are sized to encompass the specific sub-sections of
        the list item view to easily control the bounds of a background ripple effects. -->
        <com.android.car.ui.SecureView
            android:id="@+id/car_ui_list_item_touch_interceptor"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@drawable/car_ui_list_item_background"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <!-- This touch interceptor does not include the action container -->
        <com.android.car.ui.SecureView
            android:id="@+id/car_ui_list_item_reduced_touch_interceptor"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@drawable/car_ui_list_item_background"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/car_ui_list_item_action_container"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/car_ui_list_item_start_guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_begin="@dimen/car_ui_list_item_start_inset" />
    
        <FrameLayout
            android:id="@+id/car_ui_list_item_icon_container"
            android:layout_width="@dimen/car_ui_list_item_icon_container_width"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="@+id/car_ui_list_item_start_guideline"
            app:layout_constraintTop_toTopOf="parent">
    
            <ImageView
                android:id="@+id/car_ui_list_item_icon"
                android:layout_width="@dimen/car_ui_list_item_icon_size"
                android:layout_height="@dimen/car_ui_list_item_icon_size"
                android:layout_gravity="center"
                android:visibility="gone"
                android:scaleType="fitCenter" />
    
            <ImageView
                android:id="@+id/car_ui_list_item_content_icon"
                android:layout_width="@dimen/car_ui_list_item_content_icon_width"
                android:layout_height="@dimen/car_ui_list_item_content_icon_height"
                android:layout_gravity="center"
                android:visibility="gone"
                android:scaleType="fitCenter" />
    
            <ImageView
                android:id="@+id/car_ui_list_item_avatar_icon"
                android:background="@drawable/car_ui_list_item_avatar_icon_outline"
                android:layout_width="@dimen/car_ui_list_item_avatar_icon_width"
                android:layout_height="@dimen/car_ui_list_item_avatar_icon_height"
                android:layout_gravity="center"
                android:visibility="gone"
                android:scaleType="fitCenter" />
        </FrameLayout>
    
        <CarUiTextView
            android:id="@+id/car_ui_list_item_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/car_ui_list_item_text_start_margin"
            android:singleLine="@bool/car_ui_list_item_single_line_title"
            android:textAppearance="@style/TextAppearance.CarUi.ListItem"
            android:layout_gravity="right"
            android:gravity="right"
            android:textAlignment="viewEnd"
            app:layout_constraintBottom_toTopOf="@+id/car_ui_list_item_body"
            app:layout_constraintEnd_toStartOf="@+id/car_ui_list_item_action_container"
            app:layout_constraintStart_toEndOf="@+id/car_ui_list_item_icon_container"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_chainStyle="packed"
            app:layout_goneMarginStart="@dimen/car_ui_list_item_text_no_icon_start_margin" />
        <CarUiTextView
            android:id="@+id/car_ui_list_item_body"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/car_ui_list_item_text_start_margin"
            android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body"
            android:layout_gravity="right"
            android:gravity="right"
            android:textAlignment="viewEnd"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/car_ui_list_item_action_container"
            app:layout_constraintStart_toEndOf="@+id/car_ui_list_item_icon_container"
            app:layout_constraintTop_toBottomOf="@+id/car_ui_list_item_title"
            app:layout_goneMarginStart="@dimen/car_ui_list_item_text_no_icon_start_margin" />
    
        <!-- This touch interceptor is sized and positioned to encompass the action container   -->
        <com.android.car.ui.SecureView
            android:id="@+id/car_ui_list_item_action_container_touch_interceptor"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@drawable/car_ui_list_item_background"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="@id/car_ui_list_item_action_container"
            app:layout_constraintEnd_toEndOf="@id/car_ui_list_item_action_container"
            app:layout_constraintStart_toStartOf="@id/car_ui_list_item_action_container"
            app:layout_constraintTop_toTopOf="@id/car_ui_list_item_action_container" />
    
        <FrameLayout
            android:id="@+id/car_ui_list_item_action_container"
            android:layout_width="wrap_content"
            android:minWidth="@dimen/car_ui_list_item_icon_container_width"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="@+id/car_ui_list_item_end_guideline"
            app:layout_constraintTop_toTopOf="parent">
    
            <View
                android:id="@+id/car_ui_list_item_action_divider"
                android:layout_width="@dimen/car_ui_list_item_action_divider_width"
                android:layout_height="@dimen/car_ui_list_item_action_divider_height"
                android:layout_gravity="start|center_vertical"
                android:background="@drawable/car_ui_list_item_divider" />
    
            <Switch
                android:id="@+id/car_ui_list_item_switch_widget"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:clickable="false"
                android:focusable="false" />
    
            <CheckBox
                android:id="@+id/car_ui_list_item_checkbox_widget"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:clickable="false"
                android:focusable="false" />
    
            <RadioButton
                android:id="@+id/car_ui_list_item_radio_button_widget"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:clickable="false"
                android:focusable="false" />
    
            <ImageView
                android:id="@+id/car_ui_list_item_supplemental_icon"
                android:layout_width="@dimen/car_ui_list_item_supplemental_icon_size"
                android:layout_height="@dimen/car_ui_list_item_supplemental_icon_size"
                android:layout_gravity="center"
                android:scaleType="fitCenter" />
        </FrameLayout>
    
        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/car_ui_list_item_end_guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_end="@dimen/car_ui_list_item_end_inset" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. car_ui_list_item.xml tham chiếu một số thành phần/tài nguyên không được đưa vào dưới dạng phần phụ thuộc của ứng dụng. Đây là các tài nguyên car-ui-lib. Bạn có thể khắc phục vấn đề này bằng cách thêm car-ui-lib làm phần phụ thuộc vào ứng dụng RRO trong app/build.gradle:

    dependencies {
        implementation 'com.android.car.ui:car-ui-lib:2.0.0'
        implementation 'androidx.appcompat:appcompat:1.4.1'
        implementation 'com.google.android.material:material:1.4.0'
    }
    

Giờ đây, Tiêu đề và Nội dung được căn phải thay vì căn trái.

Tiêu đề và nội dung được căn phải
Hình 10. Tiêu đề và nội dung được căn phải

Chúng tôi chỉ áp dụng RRO cho car-ui-lib bằng cách sử dụng các thành phần AndroidX (ConstraintLayout) khi các thuộc tính của thành phần đó có trong tệp car-ui-lib có tên overlayable.xml cũng như RRO sample_overlay.xml. Bạn có thể làm điều tương tự trong ứng dụng của riêng mình. Chỉ cần thêm tất cả attrs tương ứng vào overlayable.xml của ứng dụng, tương tự như car-ui-lib.

Tuy nhiên, không thể RRO một ứng dụng bằng các thành phần AndroidX khi ứng dụng có car-ui-lib làm phần phụ thuộc trong build.gradle (khi ứng dụng sử dụng các thành phần car-ui-lib). Vì các ánh xạ thuộc tính đã được xác định trong overlayable.xml của thư viện car-ui-lib, nên việc thêm các ánh xạ này vào overlayable.xml của ứng dụng với car-ui-lib làm phần phụ thuộc sẽ gây ra lỗi mergeDebugResources như bên dưới. Điều này là do các thuộc tính này có trong nhiều tệp overlayable.xml:

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:mergeDebugResources'