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 te presenta car-ui-lib y cómo puedes usar superposiciones de recursos de tiempo de ejecución (RRO) para personalizar componentes en la biblioteca.

Qué aprenderás

Aprenderás a hacer lo siguiente:

  • 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 los RRO. Consulta Cómo cambiar el valor de los recursos de una app en el tiempo de ejecución y Cómo solucionar problemas relacionados con las 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 nueva app para Android

Duración: 15 minutos

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

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

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

     Asigna un nombre a tu app
    Figura 2: Asigna un nombre a 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 > valores > 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 la app, haz clic en el botón verde Play en la parte superior derecha. De esta manera, se instalará el APK automáticamente en tu emulador o dispositivo Android a través de Gradle.

    Botón para reproducir

La nueva app debería abrirse automáticamente en tu emulador o dispositivo Android. De lo contrario, abre la app de CarUiCodelab desde el selector de aplicaciones, que ya está instalado. Se verá 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 de car-ui-lib al archivo build.gradle de tu proyecto, selecciona app > build.gradle. Tus dependencias deberían aparecer 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, reemplaza 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 conoces los RRO, ve a la siguiente sección, Cómo agregar un controlador de permisos a tu app. De lo contrario, para conocer los conceptos básicos de los RRO, consulta Cómo cambiar el valor de los recursos de una app en el tiempo 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 funciona como un controlador de permisos entre tu app (el objetivo) 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>
    

    Como la cadena sample_text debe poder superponerse con un RRO, incluye el nombre del recurso en overlayable.xml de la app.

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

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

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 parámetros de configuración aparecerán de forma similar a los que se ilustran a continuación. La ubicación en la que se guardan puede diferir:

  2. Después de crear el nuevo proyecto CarUiRRO, modifica AndroidManifest.xml para declararlo como un 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>
    

    Si lo haces, se creará un error con @xml/sample_overlay. El archivo resourcesMap asigna los nombres de los recursos del paquete de destino al paquete de RRO. Es obligatorio establecer la marca hasCode como false 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ó la compilación de Gradle de 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 el RRO esté instalado correctamente, ejecuta el siguiente comando:

    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.

    • [ ] designa que el RRO está instalado y listo para activarse.
    • --- indica que el RRO está instalado, pero contiene errores.
    • [X] significa que el RRO está instalado y activado.

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

  7. Para habilitar el RRO y verificar que esté habilitado, 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".

Hello World RRO!
Figura 4: RRO de Hello World

¡Felicitaciones! Creaste tu primer RRO.

Cuando uses RRO, te recomendamos que uses las marcas --no-resource-deduping y --no-resource-removal de Android Asset Packaging Tool (AAPT2) 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 agregarlos 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 Cómo compilar el paquete y AAPT2.

Modifica los componentes 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 de tiempo de ejecución (RRO) para modificar componentes de la biblioteca car-ui-lib en tu app para Android.

Cómo establecer 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 establece 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 un RRO, la barra de herramientas no cambia porque se segmenta el 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 paquete de RRO nuevo para cada paquete de destino que quieras que tenga este tipo de lanzamiento. Por ejemplo, cuando creas superposiciones para dos objetivos diferentes, debes crear dos APK de superposición.

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

Tu app aparecerá de la siguiente manera:

Nuevo color de fondo de la 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 superponer 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 al 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 el RRO de la misma manera que antes (botón verde Play). Asegúrate de habilitar tu RRO.

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

RRO de Hello World
Figura 6: RRO de Hello World

Agrega CarUiRecyclerView a tu app

Duración: 15 minutos

La interfaz CarUiRecyclerView proporciona APIs para acceder a un RecyclerView personalizado a través de recursos car-ui-lib. Por ejemplo, CarUiRecyclerView verifica una marca en el tiempo de ejecución para determinar si la barra de desplazamiento debe estar habilitada o no 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"/>
    

    Es posible que aparezca el siguiente error, que puedes ignorar:

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

    Siempre que la 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 un RRO para quitar la barra de desplazamiento

Duración: 10 minutos

En este ejercicio, se muestra cómo usar un 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 está presente o no la barra de desplazamiento optimizada para automóviles con botones Arriba y Abajo en CarUiRecyclerView. Cuando se establece en false, CarUiRecyclerView actúa como un RecyclerView de 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>
    

Compila e instala tu app como antes. 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 que aparecen 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 recomienda 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 un 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 con 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 un RRO a los componentes car-ui-lib con componentes del framework (no AndroidX). Para usar componentes de AndroidX en un RRO, debes agregar las dependencias de ese componente a la app y al build.gradle. de RRO. 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 una 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 un 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 el 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 realizar la RRO de una app que usa componentes de AndroidX cuando esta 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'