程式碼研究室:使用 Gradle 建構系統使用 car-ui-lib 元件建立 RRO

使用 car-ui-lib 程式庫啟動自洽的車內資訊娛樂 (IVI) 系統。本程式碼研究室將介紹 car-ui-lib,以及如何使用執行階段資源覆蓋 (RRO) 自訂程式庫中的元件。

課程內容

操作說明:

  • 在 Android 應用程式中加入 car-ui-lib 元件。
  • 使用 Gradle 建構 Android 應用程式和 RRO。
  • 搭配 car-ui-lib 使用 RRO。

本程式碼研究室不會詳細說明 RRO 的運作方式。如要進一步瞭解,請參閱「在執行階段變更應用程式資源的值」和「排解執行階段資源重疊問題」。

事前準備

必要條件

開始之前,請確認您具備下列項目:

  • 有指令列的電腦 (Linux 機器、Mac 或 Windows 機器,且已安裝 Windows Subsystem for Linux)。

  • Android Studio

  • 已連接至電腦的 Android 裝置或模擬器。請參閱「下載 Android 原始碼」和「建構 Android」。

  • 具備 RRO 的基本知識。

建立新的 Android 應用程式

所需時間:15 分鐘

在本節中,您將建立新的 Android Studio 專案。

  1. 在 Android Studio 中,使用 EmptyActivity 建立應用程式。

    建立空白活動
    圖 1.建立空白活動
  2. 將應用程式命名為 CarUiCodelab,然後選取 Java 語言。您也可以視需要選取檔案位置。接受其餘設定的預設值。

     為應用程式命名
    圖 2. 為應用程式命名
  3. 請使用下列程式碼區塊取代 activity_main.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"
        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>
    

    這個程式碼區塊會顯示未定義的字串 sample_text

  4. strings.xml 檔案中新增 sample_text 資源字串,並將其設為「Hello World!」。如要開啟這個檔案,請依序選取「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. 如要建構應用程式,請按一下右上方的綠色「Play」按鈕。這樣一來,系統會透過 Gradle 自動將 APK 安裝到模擬器或 Android 裝置。

    播放按鈕

新的應用程式應會自動在模擬器或 Android 裝置上開啟。如果沒有,請從已安裝的應用程式啟動器開啟 CarUiCodelab 應用程式。如下所示:

開啟新的 CarUiCodelab 應用程式
圖 3. 開啟新的 CarUiCodelab 應用程式

將 car-ui-lib 新增至 Android 應用程式

所需時間:15 分鐘

在應用程式中新增 car-ui-lib

  1. 如要在專案的 build.gradle 檔案中新增 car-ui-lib 依附元件,請依序選取「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'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    }
    

在 Android 應用程式中使用 car-ui-lib 元件

有了 car-ui-lib 後,您可以將工具列新增至應用程式。

  1. MainActivity.java 檔案中,覆寫 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. 請務必匯入 ToolbarController

    import com.android.car.ui.core.CarUi;
    import com.android.car.ui.toolbar.ToolbarController;
    
  3. 如要使用 Theme.CarUi.WithToolbar 主題,請依序選取「app」 >「src」 >「main」 >「AndroidManifest.xml」,然後更新 AndroidManifest.xml,如下所示:

    <?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. 如要建構應用程式,請按下綠色的「Play」按鈕。

    建構應用程式

在應用程式中新增 RRO

所需時間:30 分鐘

如果您熟悉 RRO,請參閱下一節「在應用程式中新增權限控制器」。如果您想瞭解 RRO 的基本概念,請參閱「在執行階段變更應用程式資源的值」。

在應用程式中新增權限控制器

如要控制 RRO 套件疊加哪些資源,請將名為 overlayable.xml 的檔案新增至應用程式的 /res 資料夾。這個檔案可做為應用程式 (「目標」) 和 RRO 套件 (「疊加層」) 之間的權限控制器。

  1. res/values/overlayable.xml 新增至應用程式,並將以下內容複製到檔案中:

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

    由於字串 sample_text 必須可由 RRO 重疊,請在應用程式的 overlayable.xml 中加入資源名稱。

    overlayable.xml 檔案「必須」位於 res/values/ 中。如果沒有,OverlayManagerService 就無法找到該檔案。

    如要進一步瞭解可疊加的資源及其設定方式,請參閱「限制可疊加的資源」。

建立 RRO 套件

在本節中,您將建立 RRO 套件,將上述顯示的字串從「Hello World!」變更為「Hello World RRO」。

  1. 如要建立新專案,請依序選取「File」>「New」>「New Project」。請務必選取「No Activity」,而非「Empty Activity」,因為 RRO 套件只包含資源。

    您的設定會顯示類似下圖所示。儲存位置可能不同:

  2. 建立新的 CarUiRRO 專案後,請修改 AndroidManifest.xml,將專案宣告為 RRO。

    <?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>
    

    這麼做會導致 @xml/sample_overlay 發生錯誤。resourcesMap 檔案會將目標套件的資源名稱對應至 RRO 套件。對於 RRO 套件,必須將 hasCode 旗標設為 false。此外,RRO 套件不得包含 DEX 檔案。

  3. 將下列程式碼區塊複製到 …/res/xml/sample_overlay.xml 中:

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
        <item target="string/sample_text" value="@string/sample_text"/>
    </overlay>
    
  4. sample_text 新增至 …/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>
    
    建立 RRO 的 Gradle 建構作業
  5. 如要建構 RRO 目標,請按下綠色的「Play」按鈕,在模擬器或 Android 裝置上建立 RRO 的 Gradle 版本。

  6. 如要確認 RRO 是否正確安裝,請執行以下命令:

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

    這個指令會顯示系統上 RRO 套件狀態的實用資訊。

    • [ ] 表示 RRO 已安裝,可供啟用。
    • --- 表示 RRO 已安裝,但含有錯誤。
    • [X] 表示已安裝並啟用 RRO。

    如果 RRO 含有錯誤,請先參閱「排解執行階段資源覆蓋問題」,再繼續操作。

  7. 如要啟用 RRO 並確認已啟用,請按照下列步驟操作:

    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
    

應用程式會顯示「Hello World RRO」字串。

Hello World RRO!
圖 4:Hello World RRO!

恭喜!您已建立第一個 RRO。

使用 RRO 時,建議您使用「連結選項」一節所述的 Android 資產封裝工具 (AAPT2) 標記 --no-resource-deduping--no-resource-removal。您不必在本程式碼研究室中加入標記,但建議您在 RRO 中使用標記,以免移除資源 (以及造成偵錯問題)。您可以將這些項目新增至 RRO 的 build.gradle 檔案,如下所示:

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

如要進一步瞭解這些標記,請參閱「建構套件」和 AAPT2

在 Android 應用程式中使用 RRO 修改 car-ui-lib 元件

本頁面說明如何使用執行階段資源覆蓋 (RRO),修改 Android 應用程式中 car-ui-lib 程式庫的元件。

設定工具列背景顏色

所需時間:15 分鐘

如要變更工具列的背景顏色,請按照下列步驟操作:

  1. 請在 RRO 應用程式中新增下列值,並將資源設為亮綠色 (#0F0):

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

    car-ui-lib 程式庫包含名為 car_ui_toolbar_background 的資源。當這項資源包含在 RRO 的設定中時,系統會鎖定錯誤的值,因此工具列不會變更。

  2. 在 RRO 的 AndroidManifest.xml 中,將 targetName 更新為指向 car-ui-lib

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

    您必須為每個要進行 RRO 的目標套件建立新的 RRO 套件。舉例來說,如果要為兩個不同的目標建立覆蓋圖,就必須建立兩個覆蓋圖 APK。

  3. 按照先前的做法,建構、驗證、安裝及啟用 RRO。

應用程式會顯示如下:

新的工具列背景顏色
圖 5:新的工具列背景顏色

RRO 版面配置和樣式

所需時間:15 分鐘

在本練習中,您將建構與先前建立的應用程式類似的新應用程式。這個應用程式可讓您疊加版面配置。請按照先前的步驟操作,或修改現有應用程式。

  1. 請務必在 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. 確認 activity_main.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"
        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. 在 RRO 應用程式中建立 res/layout/activity_main.xml,並新增下列內容:

    <?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. 更新 res/values/styles.xml,將我們的樣式新增至 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. 變更 AndroidManifest.xml 中的 targetName,指向新應用程式的名稱:

    …
    android:targetName="CarUiCodelab"
    …
    
  6. 將資源新增至 RRO 中的 sample_overlay.xml 檔案:

    <?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. 請按照先前的做法 (綠色「Play」按鈕) 建構及安裝應用程式和 RRO。請務必啟用 RRO。

應用程式和 RRO 的算繪結果如下。「Hello World」RRO 文字為綠色,並以版面配置 RRO 中指定的對齊方式顯示。

Hello World RRO
圖 6:Hello World RRO

在應用程式中新增 CarUiRecyclerView

所需時間:15 分鐘

CarUiRecyclerView 介面提供 API,可存取透過 car-ui-lib 資源自訂的 RecyclerView。舉例來說,CarUiRecyclerView 會在執行階段檢查標記,判斷是否應啟用捲軸,並選取相應的版面配置。

CarUiRecyclerViewContainer
圖 7. CarUiRecyclerViewContainer
  1. 如要新增 CarUiRecyclerView,請將其新增至 activity_main.xmlMainActivity.java 檔案。您可以從頭開始建立新的應用程式,也可以修改現有的應用程式。如果您修改現有的應用程式,請務必從 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"/>
    

    系統可能會顯示以下錯誤,您可以忽略:

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

    只要您的類別拼寫正確,且已將 car-ui-lib 新增為依附元件,即可建構及編譯 APK。如要移除錯誤,請依序選取「File」>「Invalidate Caches」,然後按一下「Invalidate and Restart」

    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. 依照先前的方式建構及安裝應用程式。

畫面上會顯示 CarUiRecyclerView

CarUiRecyclerView
圖 7 :CarUiRecyclerView

使用 RRO 移除捲軸

所需時間:10 分鐘

本練習將說明如何使用 RRO 從 CarUiRecyclerView 移除捲軸。

  1. 在 RRO 中新增及修改下列檔案:

    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>
    

    資源 car_ui_scrollbar_enablecar-ui-lib 布林值資源,用於控制 CarUiRecyclerView 中是否顯示含有向上和向下按鈕的汽車最佳化捲軸。設為 false 時,CarUiRecyclerView 會像 AndroidX RecyclerView 一樣運作。

    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>
    

依照平常的方式建構及安裝應用程式。系統已從 CarUiRecyclerView 中移除捲軸:

沒有捲軸的 CarUiRecyclerView
圖 8.沒有捲軸的 CarUiRecyclerView

使用版面配置重疊 CarUiRecyclerView 捲軸

所需時間:15 分鐘

在本練習中,您將修改 CarUiRecyclerView 捲軸版面配置。

  1. 在 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>
    

    如要疊加版面配置檔案,您必須將所有 ID 和命名空間屬性新增至 RRO 的 overlay.xml。請參閱下方檔案。

    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>
    

    建議您檢查這些檔案的互動方式。

    為了簡化操作,我們將尺寸和顏色硬式編碼。不過,最佳做法是在 dimens.xmlcolors.xml 中宣告這些值,甚至將其指定為 res/color/ 資料夾中的色彩檔案。詳情請參閱 AOSP Java 程式碼風格 (供貢獻者參考)

  2. 依照平常的方式建構及安裝應用程式。您已使用藍色捲動條和灰色軌道建構 CarUiRecyclerView

恭喜!兩個箭頭會沿著捲軸底部顯示,表示您已成功透過 Android Studio 使用 Gradle 建構系統,將 RRO 套用至 car-ui-lib 版面配置資源檔案。

CarUiRecyclerView,其中包含藍色捲動條和灰色軌道
圖 9.CarUiRecyclerView,帶有藍色捲軸和灰色軌道

RRO 清單項目

所需時間:15 分鐘

到目前為止,您已使用架構元件 (而非 AndroidX) 將 RRO 套用至 car-ui-lib 元件。如要在 RRO 中使用 AndroidX 元件,您必須在應用程式和 RRO build.gradle. 中新增該元件的依附元件。您也必須在應用程式的 overlayable.xml 和 RRO 的 sample_overlay.xml 中,新增該元件的 attrs

我們的程式庫 (car-ui-lib) 使用 ConstraintLayout 和其他 AndroidX 元件,因此其 overlayable.xml 可能會如下所示:

<?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. 使用 ConstraintLayout 變更 CarUiRecyclerView 中清單項目的版面配置。在 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 會參照多個未納入應用程式依附元件的元件/資源。這些是 car-ui-lib 資源。如要修正這個問題,請在 app/build.gradle 中將 car-ui-lib 新增為 RRO 應用程式的依附元件:

    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'
    }
    

標題和內文現在是靠右對齊,而非靠左對齊。

靠右對齊的標題和內文
圖 10. 靠右對齊的標題和內文

只有在 car-ui-lib 屬性出現在名為 overlayable.xmlcar-ui-lib 檔案和 RRO sample_overlay.xml 時,我們才會使用 AndroidX 元件 (ConstraintLayout) 將 RRO 套用至 car-ui-lib。您也可以在自己的應用程式中執行類似的操作。只要將所有對應的 attrs 新增至應用程式的 overlayable.xml,類似於 car-ui-lib

不過,如果應用程式在其 build.gradle 中使用 car-ui-lib 做為依附元件 (應用程式使用 car-ui-lib 元件),則無法使用 AndroidX 元件 RRO 應用程式。由於屬性對應項目已在 car-ui-lib 程式庫的 overlayable.xml 中定義,因此將這些項目加入應用程式的 overlayable.xml 中,並將 car-ui-lib 設為依附元件,就會導致 mergeDebugResources 錯誤,如下所示。這是因為這些屬性會出現在多個 overlayable.xml 檔案中:

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