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

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

מה תלמדו

איך:

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

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

לפני שמתחילים

דרישות מוקדמות

לפני שמתחילים, חשוב לוודא שיש לכם:

יצירת אפליקציה חדשה ל-Android

משך: 15 דקות

בקטע הזה יוצרים פרויקט חדש ב-Android Studio.

  1. יוצרים אפליקציה עם EmptyActivity ב-Android Studio.

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

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

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

משך: 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, צריך לשנות את 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. חובה להגדיר את הדגל 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, לוחצים על הלחצן הירוק Play כדי ליצור build של Gradle של ה-RRO במהדורה של Android או במהדורה של Android ב-emulator.

  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, מומלץ להשתמש בדגלים --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 באמצעות RROs באפליקציה ל-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. מעדכנים את 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. משנים את 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. כדי להסיר את השגיאה, בוחרים באפשרות 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_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 באמצעות רכיבי מסגרת (לא 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 מפנה לכמה רכיבים או משאבים שלא נכללים בתלות של האפליקציה. אלה משאבי car-ui-lib. כדי לפתור את הבעיה, צריך להוסיף את 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). מאחר שמיפויי המאפיינים כבר הוגדרו ב-overlayable.xml של הספרייה car-ui-lib, הוספה שלהם ל-overlayable.xml של האפליקציה עם car-ui-lib כיחס תלות תגרום לשגיאת mergeDebugResources כמו זו שבהמשך. הסיבה לכך היא שהמאפיינים האלה נמצאים במספר קבצים מסוג overlayable.xml:

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