Codelab: יצירת RROs עם רכיבי car-ui-lib באמצעות מערכת ה-build של Gradle

אפשר להשתמש בספרייה car-ui-lib כדי להפעיל מערכות מידע ובידור (IVI) ברכב, שהן עקביות בפני עצמן. ב-Codelab הזה נסביר על car-ui-lib ואיך אפשר להשתמש בשכבות-על של משאבי זמן ריצה (RRO) כדי להתאים אישית רכיבים בספרייה.

מה תלמדו

איך עושים את זה:

  • כוללים רכיבי car-ui-lib באפליקציית Android.
  • אפשר להשתמש ב-Gradle כדי ליצור אפליקציות ל-Android ו-RRO.
  • שימוש ב-RRO עם car-ui-lib.

בשיעור Codelab הזה לא נפרט איך פועלים 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. מוסיפים את מחרוזת המשאב sample_text ומגדירים אותה ל-Hello World! בקובץ strings.xml. כדי לפתוח את הקובץ הזה, בוחרים באפשרות 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. כדי ליצור את האפליקציה, לוחצים על הלחצן הירוק הפעלה בפינה השמאלית העליונה. פעולה כזו תגרום להתקנת קובץ ה-APK באמולטור או במכשיר Android באמצעות Gradle.

    כפתור ההפעלה

האפליקציה החדשה אמורה להיפתח אוטומטית באמולטור או במכשיר Android. אם לא, פותחים את אפליקציית CarUiCodelab ממרכז האפליקציות, שהותקנה עכשיו. כך זה נראה:

פותחים את האפליקציה החדשה CarUiCodelab
איור 3. פותחים את האפליקציה החדשה CarUiCodelab

הוספת car-ui-lib לאפליקציית Android

משך: 15 דקות

להוסיף את car-ui-lib לאפליקציה:

  1. כדי להוסיף את התלות car-ui-lib לקובץ build.gradle של הפרויקט, בוחרים באפשרות 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'
    }
    

שימוש ברכיבים של car-ui-lib באפליקציית Android

עכשיו, אחרי שהוספתם את 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. כדי ליצור את האפליקציה, לוחצים על לחצן ההפעלה הירוק כמו קודם.

    פיתוח האפליקציה

הוספת 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. כדי ליצור פרויקט חדש, בוחרים באפשרות קובץ > חדש > פרויקט חדש. חשוב לבחור באפשרות No Activity (ללא פעילות) ולא באפשרות Empty Activity (פעילות ריקה), כי חבילות RRO מכילות רק משאבים.

    ההגדרות שלכם ייראו כמו בדוגמה שלמטה. המיקום שבו הם נשמרים עשוי להיות שונה:

  2. אחרי שיוצרים את פרויקט CarUiRRO החדש, צריך להצהיר על הפרויקט כ-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="CarUiCodelab"
            android:isStatic="false"
            android:resourcesMap="@xml/sample_overlay"
            />
    </manifest>
    

    הפעולה הזו יוצרת שגיאה ב-@xml/sample_overlay. הקובץ resourcesMap ממפה שמות של משאבים מחבילת היעד לחבילת ה-RRO. חובה להגדיר את הדגל hasCode לערך false בחבילות RRO. בנוסף, אסור לחבילות 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>
    
    נוצר build של RRO ב-Gradle
  5. כדי ליצור את יעד ה-RRO, לוחצים על לחצן ההפעלה הירוק כדי ליצור גרסת build של Gradle של ה-RRO באמולטור או במכשיר Android.

  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.

שלום עולם RRO!
איור 4: Hello World RRO!

מזל טוב! יצרתם את כלל ההגבלה הראשון.

כשמשתמשים ב-RRO, כדאי להשתמש בדגלים --no-resource-deduping ו---no-resource-removal של Android Asset Packaging Tool (AAPT2) שמתוארים באפשרויות הקישור. לא צריך להוסיף את הדגלים האלה ב-Codelab הזה, אבל מומלץ להשתמש בהם ב-RRO כדי להימנע מהסרת משאבים (ומבעיות בניפוי באגים). אפשר להוסיף אותם לקובץ build.gradle של ה-RRO באופן הבא:

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

מידע נוסף על הדגלים האלה זמין במאמרים יצירת החבילה וAAPT2.

שינוי של car-ui-lib רכיבים באמצעות RRO באפליקציית Android

בדף הזה מוסבר איך להשתמש בשכבת-על של משאבי זמן ריצה (RRO) כדי לשנות רכיבים מהספרייה car-ui-lib באפליקציית Android.

הגדרת צבע הרקע של סרגל הכלים

משך: 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. ב-AndroidManifest.xml של ה-RRO, מעדכנים את 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. כדי להוסיף את הסגנון שלנו ל-RRO, צריך לעדכן את res/values/styles.xml:

    <?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. משנים את targetName ב-AndroidManifest.xml כך שיצביע על השם של האפליקציה החדשה:

    …
    android:targetName="CarUiCodelab"
    …
    
  6. מוסיפים את המשאבים לקובץ sample_overlay.xml ב-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. יוצרים ומתקינים את האפליקציה ומפעילים את RRO באותה דרך כמו קודם (הלחצן הירוק Play). חשוב להפעיל את ה-RRO.

האפליקציה ו-RRO מוצגים באופן הבא. הטקסט Hello World RRO הוא ירוק וממורכז כמו שצוין בפריסת RRO.

Hello World RRO
איור 6: Hello World RRO

הוספת CarUiRecyclerView לאפליקציה

משך: 15 דקות

ממשק CarUiRecyclerView מספק ממשקי API לגישה אל RecyclerView שמותאם אישית באמצעות משאבי car-ui-lib. לדוגמה, CarUiRecyclerView בודק דגל בזמן ריצה כדי לקבוע אם להפעיל את סרגל הגלילה או לא ובוחר את הפריסה המתאימה.

CarUiRecyclerViewContainer
איור 7. CarUiRecyclerViewContainer
  1. כדי להוסיף CarUiRecyclerView, מוסיפים אותו לקובצי activity_main.xml וMainActivity.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. כדי להסיר את השגיאה, בוחרים באפשרות קובץ > ביטול תוקף של מטמונים ואז לוחצים על ביטול תוקף והפעלה מחדש.

    מוסיפים את הטקסט הבא לקובץ 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_enable הוא משאב בוליאני car-ui-lib, שקובע אם סרגל הגלילה המותאם לרכב עם לחצני 'למעלה' ו'למטה' ב-CarUiRecyclerView מוצג או לא. כשהערך מוגדר כ-false,‏ CarUiRecyclerView פועל כמו 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>
    

יוצרים ומתקינים את האפליקציה כמו קודם. סרגל הגלילה הוסר מ-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>
    

    כדי להוסיף שכבת-על לקובץ פריסה, צריך להוסיף את כל המזהים ומאפייני מרחב השמות ל-overlay.xml של ה-RRO. הקבצים מופיעים בהמשך.

    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.xml ו-colors.xml או אפילו להגדיר אותם כקבצי צבעים בתיקייה res/color/. מידע נוסף זמין במאמר בנושא סגנון קוד Java ב-AOSP לתורמים.

  2. יוצרים ומתקינים את האפליקציה כמו קודם. יצרתם CarUiRecyclerView עם סרגל גלילה כחול ומסילות אפורות.

מזל טוב! שני החיצים מופיעים לאורך החלק התחתון של סרגל הגלילה. הצלחתם להחיל RRO על קובץ משאב של פריסה car-ui-lib באמצעות מערכת ה-build של Gradle דרך Android Studio.

‫CarUiRecyclerView עם סרגל גלילה כחול עם מסילות אפורות
איור 9. ‫CarUiRecyclerView עם פס גלילה כחול עם מסילות אפורות

פריטים ברשימת RRO

משך: 15 דקות

עד עכשיו, החלתם RRO על רכיבי car-ui-lib באמצעות רכיבי framework (לא AndroidX). כדי להשתמש ברכיבי AndroidX ב-RRO, צריך להוסיף את התלות של הרכיב הזה גם לאפליקציה וגם ל-RRO build.gradle. צריך גם להוסיף את attrs של הרכיב הזה ל-overlayable.xml באפליקציה, וגם את sample_overlay.xml ב-RRO.

הספרייה שלנו (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. כדי לשנות את הפריסה של הפריטים ברשימה ב-CarUiRecyclerView, משתמשים ב-ConstraintLayout. מוסיפים או משנים את הקבצים הבאים ב-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 references several references several components/ resources that are not included as dependencies of the app. These are car-ui-lib resources. כדי לפתור את הבעיה, צריך להוסיף את car-ui-lib כתלות לאפליקציית ה-RRO ב-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'
    }
    

הכותרת והתוכן מיושרים עכשיו לימין במקום לשמאל.

כותרת וגוף הטקסט מיושרים לימין
איור 10. כותרת וטקסט מיושרים לימין

השתמשנו ב-RRO רק ב-car-ui-lib באמצעות רכיבי AndroidX‏ (ConstraintLayout) כשהמאפיינים שלו הופיעו גם בקובץ car-ui-lib בשם overlayable.xml וגם ב-RRO‏ sample_overlay.xml. אפשר לעשות משהו דומה באפליקציה שלכם. פשוט מוסיפים את כל attrs המתאימים ל-overlayable.xml של האפליקציה, בדומה ל-car-ui-lib.

עם זאת, אי אפשר להשתמש ב-RRO באפליקציה באמצעות רכיבי AndroidX אם car-ui-lib מוגדר כתלות ב-build.gradle של האפליקציה (כשהאפליקציה משתמשת ברכיבי car-ui-lib). מיפויי המאפיינים כבר הוגדרו בספרייה car-ui-lib של overlayable.xml, ולכן הוספה שלהם ל-overlayable.xml של האפליקציה עם car-ui-lib כתלות תגרום לשגיאה mergeDebugResources כמו זו שמופיעה בהמשך. הסיבה לכך היא שהמאפיינים האלה מופיעים בכמה קבצים של overlayable.xml:

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