Codelab: Crea RROs con componentes car-ui-lib a través del sistema de compilación de Gradle

Usa la biblioteca car-ui-lib para iniciar sistemas de infoentretenimiento (IVI) en el vehículo y coherentes. En este codelab, se presenta car-ui-lib y cómo puedes usar superposiciones de recursos del entorno de ejecución (RRO) para personalizar componentes en la biblioteca.

Qué aprenderás

Instructivo:

  • Incluye componentes car-ui-lib en tu app para Android.
  • Usa Gradle para compilar apps para Android y RRO.
  • Usa RRO con car-ui-lib.

En este codelab, no se detalla cómo funcionan las RRO. Consulta Cómo cambiar el valor de los recursos de una app en el entorno de ejecución y Soluciona problemas de superposiciones de recursos del entorno de ejecución para obtener más información.

Antes de comenzar

Requisitos previos

Antes de comenzar, asegúrate de tener lo siguiente:

Crea una app para Android nueva

Duración: 15 minutos

En esta sección, crearás un nuevo proyecto de Android Studio.

  1. En Android Studio, crea una app con una EmptyActivity.

    Cómo crear una actividad vacía
    Figura 1.Crea una actividad vacía
  2. Nombra la app CarUiCodelab y, luego, selecciona el lenguaje Java. También puedes seleccionar una ubicación de archivo si lo deseas. Acepta los valores predeterminados para la configuración restante.

     Escribe el nombre de tu app.
    Figura 2. Nombra tu app
  3. Reemplaza activity_main.xml por el siguiente bloque de código:

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

    Este bloque de código muestra la cadena sample_text, que no está definida.

  4. Agrega la cadena de recursos sample_text y configúrala como "Hello World!" en tu archivo strings.xml. Para abrir este archivo, selecciona 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. Para compilar tu app, haz clic en el botón verde Play en la esquina superior derecha. Si lo haces, se instalará automáticamente el APK en tu emulador o dispositivo Android a través de Gradle.

    Botón de reproducción

La app nueva debería abrirse automáticamente en tu emulador o dispositivo Android. Si no es así, abre la app CarUiCodelab desde el selector de aplicaciones, que ahora está instalado. Se ve de la siguiente manera:

Abre la nueva app de CarUiCodelab
Figura 3: Abre la nueva app de CarUiCodelab

Agrega car-ui-lib a tu app para Android

Duración: 15 minutos

Agrega car-ui-lib a tu app:

  1. Para agregar la dependencia car-ui-lib al archivo build.gradle de tu proyecto, selecciona app > build.gradle. Tus dependencias deberían verse de la siguiente manera:

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

Usa componentes de car-ui-lib en tu app para Android

Ahora que tienes car-ui-lib, agrega una barra de herramientas a tu app.

  1. En tu archivo MainActivity.java, anula el método 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. Asegúrate de importar ToolbarController:

    import com.android.car.ui.core.CarUi;
    import com.android.car.ui.toolbar.ToolbarController;
    
  3. Para usar el tema Theme.CarUi.WithToolbar, selecciona app > src > main > AndroidManifest.xml y, luego, actualiza AndroidManifest.xml para que aparezca de la siguiente manera:

    <?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. Para compilar la app, presiona el botón verde Play como antes.

    Compila la app

Agrega RRO a tu app

Duración: 30 minutos

Si estás familiarizado con las RRO, ve a la siguiente sección, Agrega un controlador de permisos a tu app. De lo contrario, para aprender los conceptos básicos de las RRO, consulta Cómo cambiar el valor de los recursos de una app en el entorno de ejecución.

Agrega un controlador de permisos a tu app

Para controlar qué recursos superpone un paquete de RRO, agrega un archivo llamado overlayable.xml a la carpeta /res de tu app. Este archivo sirve como un controlador de permisos entre tu app (el destino) y tu paquete de RRO (la superposición).

  1. Agrega res/values/overlayable.xml a tu app y copia el siguiente contenido en tu archivo:

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

    Dado que la cadena sample_text debe superponerse con una RRO, incluye el nombre del recurso en el archivo overlayable.xml de la app.

    El archivo overlayable.xml DEBE residir en res/values/. De lo contrario, OverlayManagerService no podrá ubicarlo.

    Para obtener más información sobre los recursos superponibles y cómo se pueden configurar, consulta Restringe los recursos superponibles.

Crea un paquete de RRO

En esta sección, crearás un paquete de RRO para cambiar la cadena que se muestra arriba de "Hello World!" a "Hello World RRO".

  1. Para crear un proyecto nuevo, selecciona File > New > New Project. Asegúrate de seleccionar No Activity en lugar de Empty Activity, ya que los paquetes de RRO solo contienen recursos.

    Tus configuraciones se verán de manera similar a las que se muestran a continuación. La ubicación en la que se guardan puede diferir:

  2. Después de crear el nuevo proyecto CarUiRRO, decláralo como una RRO modificando 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>
    

    Si lo haces, se generará un error con @xml/sample_overlay. El archivo resourcesMap asigna nombres de recursos del paquete de destino al paquete de RRO. Configurar la marca hasCode en false es obligatorio para los paquetes de RRO. Además, los paquetes de RRO no pueden contener archivos DEX.

  3. Copia el siguiente bloque de código en …/res/xml/sample_overlay.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
        <item target="string/sample_text" value="@string/sample_text"/>
    </overlay>
    
  4. Agrega sample_text a …/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>
    
    Se creó una compilación de Gradle del RRO
  5. Para compilar tu destino de RRO, presiona el botón verde Play para crear una compilación de Gradle de tu RRO en el emulador o dispositivo Android.

  6. Para verificar que la RRO esté instalada correctamente, ejecuta lo siguiente:

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

    Este comando muestra información útil sobre el estado de los paquetes de RRO en el sistema.

    • [ ] indica que la RRO está instalada y lista para activarse.
    • --- indica que la RRO está instalada, pero contiene errores.
    • [X] significa que la RRO está instalada y activada.

    Si tu RRO contiene errores, consulta Soluciona problemas de superposiciones de recursos del entorno de ejecución antes de continuar.

  7. Para habilitar la RRO y verificar que esté habilitada, haz lo siguiente:

    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
    

Tu app muestra la cadena "Hello World RRO".

¡Hola, RR de Hello World!
Figura 4: Hello World RRO!

¡Felicitaciones! Creaste tu primera RRO.

Cuando uses RRO, es posible que quieras usar las marcas de Android Asset Packaging Tool (AAPT2) --no-resource-deduping y --no-resource-removal que se describen en Opciones de vinculación. No es necesario agregar las marcas en este codelab, pero te sugerimos que las uses en tus RRO para evitar la eliminación de recursos (y los dolores de cabeza de depuración). Puedes agregarlas al archivo build.gradle de tu RRO de la siguiente manera:

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

Para obtener más información sobre estas marcas, consulta Compila el paquete y AAPT2.

Modifica componentes de car-ui-lib con RRO en tu app para Android

En esta página, se describe cómo puedes usar una superposición de recursos del entorno de ejecución (RRO) para modificar componentes de la biblioteca car-ui-lib en tu app para Android.

Establece el color de fondo de la barra de herramientas

Duración: 15 minutos

Para cambiar el color de fondo de la barra de herramientas, haz lo siguiente:

  1. Agrega el siguiente valor a tu app de RRO y configura el recurso en verde brillante (#0F0):

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

    La biblioteca car-ui-lib contiene un recurso llamado car_ui_toolbar_background. Cuando este recurso se incluye en la configuración de una RRO, la barra de herramientas no cambia porque se apunta al valor incorrecto.

  2. En el AndroidManifest.xml de tu RRO, actualiza targetName para que apunte a car-ui-lib:

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

    DEBES crear un nuevo paquete de RRO para cada paquete de destino que quieras RRO. Por ejemplo, cuando creas superposiciones para dos destinos diferentes, debes crear dos APK de superposición.

  3. Compila, verifica, instala y habilita la RRO de la misma manera que antes.

Tu app se ve de la siguiente manera:

Color de fondo de la nueva barra de herramientas
Figura 5: Nuevo color de fondo de la barra de herramientas

Diseños y estilos de RRO

Duración: 15 minutos

En este ejercicio, compilarás una app nueva similar a la que compilaste antes. Esta app permite que se superponga el diseño. Sigue los mismos pasos que antes o modifica tu app existente.

  1. Asegúrate de agregar las siguientes líneas a 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. Asegúrate de que activity_main.xml aparezca de la siguiente manera:

    <?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. En tu app de RRO, crea un res/layout/activity_main.xml y agrega lo siguiente:

    <?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. Actualiza res/values/styles.xml para agregar nuestro estilo a la 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. Cambia el targetName en AndroidManifest.xml para que apunte al nombre de tu app nueva:

    …
    android:targetName="CarUiCodelab"
    …
    
  6. Agrega los recursos al archivo sample_overlay.xml en tu 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. Compila e instala la app y la RRO de la misma manera que antes (botón verde Play). Asegúrate de habilitar tu RRO.

La app y la RRO se renderizan de la siguiente manera. El texto Hello World RRO es verde y está centrado como se especifica en la RRO de diseño.

RRR de Hello World
Figura 6: Hello World RRO

Agrega CarUiRecyclerView a tu app

Duración: 15 minutos

La interfaz CarUiRecyclerView proporciona APIs para acceder a una RecyclerView que se personaliza a través de recursos car-ui-lib. Por ejemplo, CarUiRecyclerView verifica una marca en el tiempo de ejecución para determinar si se debe habilitar o no la barra de desplazamiento y selecciona el diseño correspondiente.

CarUiRecyclerViewContainer
Figura 7: CarUiRecyclerViewContainer
  1. Para agregar un CarUiRecyclerView, agrégalo a tus archivos activity_main.xml y MainActivity.java. Puedes crear una app nueva desde cero o modificar la app existente. Si modificas la app existente, asegúrate de quitar los recursos no declarados de 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"/>
    

    Puede aparecer el siguiente error, que puedes ignorar:

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

    Siempre que tu clase esté escrita correctamente y hayas agregado car-ui-lib como dependencia, puedes compilar tu APK. Para quitar el error, selecciona File > Invalidate Caches y, luego, haz clic en Invalidate and Restart.

    Agrega lo siguiente a 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. Compila e instala tu app como antes.

Ahora verás un CarUiRecyclerView:

CarUiRecyclerView
Figura 7 : CarUiRecyclerView

Usa una RRO para quitar la barra de desplazamiento

Duración: 10 minutos

En este ejercicio, se muestra cómo usar una RRO para quitar la barra de desplazamiento de CarUiRecyclerView.

  1. En tu RRO, agrega y modifica los siguientes archivos:

    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>
    

    El recurso car_ui_scrollbar_enable es un recurso booleano car-ui-lib, que controla si la barra de desplazamiento optimizada para el vehículo con los botones Arriba y Abajo en CarUiRecyclerView está presente o no. Cuando se establece en false, CarUiRecyclerView actúa como un 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>
    

Compila e instala tu app como antes. Ahora se quitó la barra de desplazamiento de CarUiRecyclerView:

CarUiRecyclerView sin barra de desplazamiento
Figura 8. CarUiRecyclerView sin barra de desplazamiento

Usa un diseño para superponer la barra de desplazamiento de CarUiRecyclerView

Duración: 15 minutos

En este ejercicio, modificarás el diseño de la barra de desplazamiento CarUiRecyclerView.

  1. Agrega y modifica los siguientes archivos en tu app de 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>
    

    Para superponer un archivo de diseño, debes agregar todos los IDs y atributos de espacio de nombres al overlay.xml de tu RRO. Consulta los archivos a continuación.

    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>
    

    Se sugiere examinar cómo interactúan estos archivos.

    Para simplificar, las dimensiones y los colores están codificados. Sin embargo, una práctica recomendada es declarar estos valores en dimens.xml y colors.xml, o incluso designarlos como archivos de color en la carpeta res/color/. Para obtener más información, consulta Estilo de código Java del AOSP para colaboradores.

  2. Compila e instala tu app como antes. Compilaste CarUiRecyclerView con una barra de desplazamiento azul y rieles grises.

¡Felicitaciones! Ambas flechas aparecen en la parte inferior de la barra de desplazamiento. Aplicaste correctamente una RRO a un archivo de recursos de diseño car-ui-lib con el sistema de compilación de Gradle a través de Android Studio.

CarUiRecyclerView con una barra de desplazamiento azul y rieles grises
Figura 9. CarUiRecyclerView con una barra de desplazamiento azul con rieles grises

Elementos de lista de RRO

Duración: 15 minutos

Hasta este punto, aplicaste una RRO a los componentes car-ui-lib con componentes de framework (no AndroidX). Para usar componentes de AndroidX en una RRO, debes agregar las dependencias de ese componente a la app y a la RRO build.gradle. También debes agregar el attrs de ese componente a overlayable.xml en tu app, así como el sample_overlay.xml en tu RRO.

Nuestra biblioteca (car-ui-lib) usa ConstraintLayout, así como otros componentes de AndroidX, por lo que su overlayable.xml podría verse de la siguiente manera:

<?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. Cambia el diseño de los elementos de la lista en CarUiRecyclerView con ConstraintLayout. Agrega o modifica los siguientes archivos en tu 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 hace referencia a varios componentes o recursos que no se incluyen como dependencias de la app. Estos son recursos car-ui-lib. Para solucionar este problema, agrega car-ui-lib como dependencia a tu app de RRO en 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'
    }
    

El título y el cuerpo ahora están alineados a la derecha en lugar de a la izquierda.

Título y cuerpo alineados a la derecha
Figura 10: Título y cuerpo alineados a la derecha

Solo aplicamos una RRO a car-ui-lib con componentes de AndroidX (ConstraintLayout) cuando sus atributos estaban presentes en el archivo car-ui-lib llamado overlayable.xml, así como en la RRO sample_overlay.xml. Es posible hacer algo similar en tu propia app. Solo agrega todos los attrs correspondientes al overlayable.xml de tu app, de manera similar a car-ui-lib.

Sin embargo, no es posible RRO una app con componentes de AndroidX cuando la app tiene car-ui-lib como dependencia en su build.gradle (cuando la app usa componentes car-ui-lib). Dado que las asignaciones de atributos ya se definieron en el overlayable.xml de la biblioteca car-ui-lib, agregarlas al overlayable.xml de tu app con car-ui-lib como dependencia provocaría un error mergeDebugResources como el que se muestra a continuación. Esto se debe a que estos atributos están presentes en varios archivos overlayable.xml:

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