Codelab: Gradle 빌드 시스템을 사용하여 car-ui-lib 구성요소가 포함된 RRO 만들기

car-ui-lib 라이브러리를 사용하여 일관된 차량용 인포테인먼트(IVI) 시스템을 실행합니다. 이 Codelab에서는 car-ui-lib를 소개하고 런타임 리소스 오버레이(RRO)를 사용하여 이 라이브러리의 구성요소를 맞춤설정하는 방법을 설명합니다.

학습할 내용

다음을 학습합니다.

  • Android 앱에 car-ui-lib 구성요소를 포함하는 방법
  • Gradle을 사용하여 Android 앱과 RRO를 빌드하는 방법
  • car-ui-lib와 함께 RRO를 사용하는 방법

이 Codelab에서는 RRO 작동 방식을 자세히 다루지 않습니다. 자세한 내용은 런타임에 앱의 리소스 값 변경런타임 리소스 오버레이 문제 해결을 참고하세요.

시작하기 전에

기본 요건

시작하려면 다음 요건이 충족되어야 합니다.

새 Android 앱 만들기

시간: 15분

이 섹션에서는 새로운 Android 스튜디오 프로젝트를 만듭니다.

  1. Android 스튜디오에서 EmptyActivity를 사용하여 앱을 만듭니다.

    Empty Activity 만들기
    그림 1.Empty Activity 만들기
  2. 앱 이름을 CarUiCodelab으로 지정하고 Java 언어를 선택합니다. 원하는 경우 파일 위치도 선택할 수 있습니다. 나머지 설정은 기본값을 사용합니다.

    앱 이름 지정
    그림 2. 앱 이름 지정
  3. activity_main.xml을 다음 코드 블록으로 대체합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/sample_text"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    이 코드 블록은 정의되지 않은 문자열 sample_text를 표시합니다.

  4. strings.xml 파일에서 sample_text 리소스 문자열을 추가하고 'Hello World!'로 설정합니다. 이 파일을 열려면 app > src > main > res > values > strings.xml을 선택합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_name">CarUiCodelab</string>
        <string name="sample_text">Hello World!</string>
    </resources>
    
  5. 앱을 빌드하려면 오른쪽 상단의 녹색 Play 버튼을 클릭합니다. 이렇게 하면 Gradle을 통해 에뮬레이터나 Android 기기에 자동으로 APK가 설치됩니다.

    Play 버튼

새 앱이 에뮬레이터나 Android 기기에서 자동으로 열립니다. 열리지 않으면 지금 설치된 앱 런처에서 CarUiCodelab 앱을 엽니다. 다음과 같이 표시됩니다.

새 CarUiCodelab 앱 열기
그림 3. 새 CarUiCodelab 앱 열기

Android 앱에 car-ui-lib 추가

시간: 15분

다음과 같이 앱에 car-ui-lib를 추가합니다.

  1. 프로젝트의 build.gradle 파일에 car-ui-lib 종속 항목을 추가하려면 app > build.gradle을 선택합니다. 종속 항목은 다음과 같이 표시됩니다.

    dependencies {
        implementation 'com.android.car.ui:car-ui-lib:2.0.0'
        implementation 'androidx.appcompat:appcompat:1.4.1'
        implementation 'com.google.android.material:material:1.4.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    }
    

Android 앱에서 car-ui-lib 구성요소 사용

이제 car-ui-lib가 있으므로 툴바를 앱에 추가합니다.

  1. MainActivity.java 파일에서 다음과 같이 onCreate 메서드를 덮어씁니다.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Get the toolbar controller instance.
        ToolbarController toolbar = CarUi.getToolbar(this);
        // Set the title on toolbar.
        toolbar.setTitle(getTitle());
        // Set the logo to be shown with the title.
        toolbar.setLogo(R.mipmap.ic_launcher_round);
    }
    
  2. ToolbarController를 가져와야 합니다.

    import com.android.car.ui.core.CarUi;
    import com.android.car.ui.toolbar.ToolbarController;
    
  3. Theme.CarUi.WithToolbar 테마를 사용하려면 app > src > main > AndroidManifest.xml을 선택하고 다음과 같이 표시되도록 AndroidManifest.xml을 업데이트합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.example.caruicodelab">
    
        <application
            android:allowBackup="true"
            android:dataExtractionRules="@xml/data_extraction_rules"
            android:fullBackupContent="@xml/backup_rules"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.CarUi.WithToolbar"
            tools:targetApi="31">
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    
  4. 앱을 빌드하려면 이전처럼 녹색 Play 버튼을 누릅니다.

    앱 빌드

앱에 RRO 추가

시간: 30분

RRO를 잘 알고 있다면 다음 섹션 앱에 권한 컨트롤러 추가로 이동합니다. 잘 모르는 경우 RRO의 기본사항을 알아보려면 런타임에 앱의 리소스 값 변경을 참고하세요.

앱에 권한 컨트롤러 추가

RRO 패키지에서 오버레이하는 리소스를 제어하려면 overlayable.xml 파일을 앱의 /res 폴더에 추가합니다. 이 파일은 앱(타겟)과 RRO 패키지(오버레이) 간의 권한 컨트롤러 역할을 합니다.

  1. 앱에 res/values/overlayable.xml을 추가하고 다음 콘텐츠를 파일에 복사합니다.

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

    문자열 sample_text는 RRO에서 오버레이 가능해야 하므로 앱의 overlayable.xml에 리소스 이름을 추가합니다.

    overlayable.xml 파일은 res/values/있어야 합니다. 그렇지 않으면 OverlayManagerService가 파일을 찾을 수 없습니다.

    오버레이 가능한 리소스와 이를 구성하는 방법에 관한 자세한 내용은 오버레이 가능한 리소스 제한을 참고하세요.

RRO 패키지 만들기

이 섹션에서는 위에 표시된 문자열을 'Hello World!'에서 'Hello World RRO'로 변경하는 RRO 패키지를 만듭니다.

  1. 새 프로젝트를 만들려면 File > New > New Project를 선택합니다. RRO 패키지에는 리소스만 포함되므로 Empty Activity가 아닌 No Activity를 선택해야 합니다.

    구성은 아래 그림과 같이 표시됩니다. 저장되는 위치는 다를 수 있습니다.

  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 패키지로 매핑합니다.

  3. 다음 코드 블록을 …/res/xml/sample_overlay.xml에 복사합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
        <item target="string/sample_text" value="@string/sample_text"/>
    </overlay>
    
  4. sample_text…/res/values/strings.xml에 추가합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_name">CarUiRRO</string>
        <string name="sample_text">Hello World RRO</string>
    </resources>
    
    RRO의 Gradle 빌드 생성됨
  5. RRO 타겟을 빌드하려면 녹색 Play 버튼을 눌러 에뮬레이터나 Android 기기에서 RRO의 Gradle 빌드를 만듭니다.

  6. RRO가 제대로 설치되었는지 확인하려면 다음을 실행합니다.

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

    이 명령어는 시스템의 RRO 패키지 상태에 관한 유용한 정보를 표시합니다.

    • [ ]는 RRO가 설치되었으며 활성화될 준비가 되었음을 나타냅니다.
    • ---는 RRO가 설치되었지만 오류가 포함되어 있음을 나타냅니다.
    • [X]는 RRO가 설치되고 활성화되었음을 나타냅니다.

    RRO에 오류가 포함되어 있으면 계속하기 전에 런타임 리소스 오버레이 문제 해결을 참고하세요.

  7. RRO를 사용 설정하고 사용 설정되었는지 확인하려면 다음을 실행합니다.

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

앱에 문자열 'Hello World RRO'가 표시됩니다.

Hello World RRO!
그림 4: Hello World RRO!

축하합니다. 첫 번째 RRO가 생성되었습니다.

RRO를 사용할 때는 링크 옵션에 설명된 Android Asset Packaging Tool(AAPT2) 플래그 --no-resource-deduping--no-resource-removal을 사용하는 것이 좋습니다. 이 Codelab에서는 이러한 플래그를 추가하지 않아도 되지만 리소스 삭제와 디버깅 문제를 방지하려면 RRO에서 이를 사용하는 것이 좋습니다. 다음과 같이 RRO의 build.gradle 파일에 추가하면 됩니다.

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

이러한 플래그에 관한 자세한 내용은 패키지 빌드AAPT2를 참고하세요.

Android 앱에서 RRO를 사용하여 car-ui-lib 구성요소 수정

이 페이지에서는 런타임 리소스 오버레이(RRO)를 사용하여 Android 앱에서 car-ui-lib 라이브러리의 구성요소를 수정하는 방법을 설명합니다.

툴바 배경 색상 설정

시간: 15분

툴바의 배경 색상을 변경하려면 다음을 실행하세요.

  1. 다음 값을 RRO 앱에 추가하고 리소스를 밝은 녹색(#0F0)으로 설정합니다.

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

    car-ui-lib 라이브러리에는 리소스 car_ui_toolbar_background가 포함되어 있습니다. 이 리소스가 RRO 구성에 포함되면 잘못된 값이 타겟팅되므로 툴바가 변경되지 않습니다.

  2. RRO의 AndroidManifest.xml에서 car-ui-lib를 가리키도록 targetName을 업데이트합니다.

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

    RRO하려는 타겟 패키지마다 새 RRO 패키지를 만들어야 합니다(MUST). 예를 들어 두 타겟에 관한 오버레이를 만들면 두 개의 오버레이 APK를 만들어야 합니다.

  3. 이전과 동일한 방식으로 RRO를 빌드, 확인, 설치, 사용 설정합니다.

앱이 다음과 같이 표시됩니다.

새로운 툴바 배경 색상
그림 5: 새로운 툴바 배경 색상

RRO 레이아웃과 스타일

시간: 15분

이 연습에서는 앞서 빌드한 앱과 유사한 새 앱을 빌드합니다. 이 앱에서는 레이아웃을 오버레이할 수 있습니다. 전과 동일한 단계를 따르거나 기존 앱을 수정하세요.

  1. 다음 줄을 overlayable.xml에 추가해야 합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
      <overlayable name="CarUiCodelab">
        <policy type="public">
          <item type="string" name="sample_text"/>
          <item type="layout" name="activity_main"/>
          <item type="id" name="textView"/>
        </policy>
      </overlayable>
    </resources>
    
  2. activity_main.xml이 다음과 같이 표시되어야 합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/sample_text"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  3. RRO 앱에서 res/layout/activity_main.xml을 만들고 다음을 추가합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
      <TextView
          android:id="@+id/textView"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@string/sample_text"
          android:textAppearance="@style/TextAppearance.CarUi"
          android:layout_gravity="center_vertical|center_horizontal"/>
    </FrameLayout>
    
  4. res/values/styles.xml을 업데이트하여 스타일을 RRO에 추가합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <style name="TextAppearance.CarUi" parent="android:TextAppearance.DeviceDefault">
            <item name="android:textColor">#0f0</item>
            <item name="android:textSize">100sp</item>
        </style>
    </resources>
    
  5. 새 앱의 이름을 가리키도록 AndroidManifest.xmltargetName을 변경합니다.

    …
    android:targetName="CarUiCodelab"
    …
    
  6. RRO의 sample_overlay.xml 파일에 리소스를 추가합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
        <item target="string/sample_text" value="@string/sample_text"/>
        <item target="id/textView" value="@id/textView"/>
        <item target="layout/activity_main" value="@layout/activity_main"/>
    </overlay>
    
  7. 전과 같은 방식(녹색 Play 버튼)으로 앱과 RRO를 빌드하고 설치합니다. RRO를 사용 설정해야 합니다.

앱과 RRO가 다음과 같이 렌더링됩니다. Hello World RRO 텍스트가 녹색이며 레이아웃 RRO에 지정된 대로 중앙에 위치합니다.

Hello World RRO
그림 6: Hello World RRO

앱에 CarUiRecyclerView 추가

시간: 15분

CarUiRecyclerView 인터페이스는 car-ui-lib 리소스를 통해 맞춤설정된 RecyclerView에 액세스하는 API를 제공합니다. 예를 들어 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_enablecar-ui-lib 불리언 리소스로, CarUiRecyclerView의 Up 버튼과 Down 버튼이 있는 자동차 최적화 스크롤바가 있는지를 제어합니다. false로 설정하면 CarUiRecyclerView는 AndroidX RecyclerView처럼 동작합니다.

    res/xml/sample_overlay.xml

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
        <item target="bool/car_ui_scrollbar_enable" value="@bool/car_ui_scrollbar_enable"/>
    </overlay>
    

전과 같이 앱을 빌드하고 설치합니다. 이제 스크롤바가 CarUiRecyclerView에서 삭제됩니다.

스크롤바가 없는 CarUiRecyclerView
그림 8. 스크롤바가 없는 CarUiRecyclerView

레이아웃을 사용하여 CarUiRecyclerView 스크롤바 오버레이

시간: 15분

이 연습에서는 CarUiRecyclerView 스크롤바 레이아웃을 수정합니다.

  1. RRO 앱에서 다음 파일을 추가하고 수정합니다.

    res/layout/car_ui_recycler_view_scrollbar.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="112dp"
        android:layout_height="match_parent"
        android:id="@+id/car_ui_scroll_bar">
        <!-- View height is dynamically calculated during layout. -->
        <View
            android:id="@+id/car_ui_scrollbar_thumb"
            android:layout_width="6dp"
            android:layout_height="20dp"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:background="@drawable/car_ui_recyclerview_scrollbar_thumb"/>
        <View
            android:id="@+id/car_ui_scrollbar_track"
            android:layout_width="10dp"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:layout_centerHorizontal="true"
            android:layout_above="@+id/car_ui_scrollbar_page_up"/>
        <View
            android:layout_width="2dp"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:background="#323232"
            android:layout_toLeftOf="@+id/car_ui_scrollbar_thumb"
            android:layout_above="@+id/car_ui_scrollbar_page_up"
            android:layout_marginRight="5dp"/>
        <View
            android:layout_width="2dp"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:background="#323232"
            android:layout_toRightOf="@+id/car_ui_scrollbar_thumb"
            android:layout_above="@+id/car_ui_scrollbar_page_up"
            android:layout_marginLeft="5dp"/>
        <ImageView
            android:id="@+id/car_ui_scrollbar_page_up"
            android:layout_width="75dp"
            android:layout_height="75dp"
            android:focusable="false"
            android:hapticFeedbackEnabled="false"
            android:src="@drawable/car_ui_recyclerview_ic_up"
            android:scaleType="centerInside"
            android:background="?android:attr/selectableItemBackgroundBorderless"
            android:layout_centerHorizontal="true"
            android:layout_above="@+id/car_ui_scrollbar_page_down"/>
        <ImageView
            android:id="@+id/car_ui_scrollbar_page_down"
            android:layout_width="75dp"
            android:layout_height="75dp"
            android:focusable="false"
            android:hapticFeedbackEnabled="false"
            android:src="@drawable/car_ui_recyclerview_ic_down"
            android:scaleType="centerInside"
            android:background="?android:attr/selectableItemBackgroundBorderless"
            android:layout_centerHorizontal="true"
            android:layout_alignParentBottom="true"/>
    </RelativeLayout>
    

    레이아웃 파일을 오버레이하려면 모든 ID 및 네임스페이스 속성을 RRO의 overlay.xml에 추가해야 합니다. 아래 파일을 참고하세요.

    res/xml/sample_overlay.xml

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
       <item target="drawable/car_ui_recyclerview_ic_down" value="@drawable/car_ui_recyclerview_ic_down"/>
       <item target="drawable/car_ui_recyclerview_ic_up" value="@drawable/car_ui_recyclerview_ic_up"/>
       <item target="drawable/car_ui_recyclerview_scrollbar_thumb" value="@drawable/car_ui_recyclerview_scrollbar_thumb"/>
       <item target="id/car_ui_scroll_bar" value="@id/car_ui_scroll_bar"/>
       <item target="id/car_ui_scrollbar_thumb" value="@id/car_ui_scrollbar_thumb"/>
       <item target="id/car_ui_scrollbar_track" value="@id/car_ui_scrollbar_track"/>
       <item target="id/car_ui_scrollbar_page_up" value="@id/car_ui_scrollbar_page_up"/>
       <item target="id/car_ui_scrollbar_page_down" value="@id/car_ui_scrollbar_page_down"/>
       <item target="layout/car_ui_recyclerview_scrollbar" value="@layout/car_ui_recyclerview_scrollbar"/>
    </overlay>
    

    res/drawable/car_ui_recyclerview_ic_up.xml

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="48dp"
        android:height="48dp"
        android:viewportWidth="48.0"
        android:viewportHeight="48.0">
        <path
            android:pathData="M14.83,30.83L24,21.66l9.17,9.17L36,28 24,16 12,28z"
            android:fillColor="#0000FF"/>
    </vector>
    

    res/drawable/car_ui_recyclerview_ic_down.xml

    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="48dp"
        android:height="48dp"
        android:viewportWidth="48.0"
        android:viewportHeight="48.0">
        <path
            android:pathData="M14.83,16.42L24,25.59l9.17,-9.17L36,19.25l-12,12 -12,-12z"
            android:fillColor="#0000FF"/>
    </vector>
    

    res/drawable/car_ui_recyclerview_scrollbar_thumb.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <solid android:color="#0000FF" />
        <corners android:radius="100dp"/>
    </shape>
    

    이러한 파일이 어떻게 상호작용하는지 검토하는 것이 좋습니다.

    편의상 크기와 색상은 하드코딩됩니다. 하지만 이러한 값을 dimens.xmlcolors.xml에서 선언하거나 res/color/ 폴더의 색상 파일로 지정하는 것이 좋습니다. 자세한 내용은 기여자를 위한 AOSP Java 코드 스타일을 참고하세요.

  2. 전과 같이 앱을 빌드하고 설치합니다. 파란색 스크롤바와 회색 레일로 CarUiRecyclerView를 빌드했습니다.

축하합니다. 두 화살표가 모두 스크롤바 하단을 따라 표시됩니다. Android 스튜디오를 통해 Gradle 빌드 시스템을 사용하여 RRO를 car-ui-lib 레이아웃 리소스 파일에 성공적으로 적용했습니다.

파란색 스크롤바와 회색 레일이 있는 CarUiRecyclerView
그림 9. 파란색 스크롤바와 회색 레일이 있는 CarUiRecyclerView

RRO 목록 항목

시간: 15분

지금까지 프레임워크 구성요소(AndroidX 아님)를 사용하여 RRO를 car-ui-lib 구성요소에 적용했습니다. RRO에서 AndroidX 구성요소를 사용하려면 해당 구성요소의 종속 항목을 앱과 RRO build.gradle.에 모두 추가해야 합니다. 또한 해당 구성요소의 attrs를 앱의 overlayable.xml과 RRO의 sample_overlay.xml에 추가해야 합니다.

라이브러리(car-ui-lib)는 ConstraintLayout은 물론 기타 AndroidX 구성요소를 사용하므로 overlayable.xml이 다음과 같을 수 있습니다.

<?xml version='1.0' encoding='UTF-8'?>
<resources>
    <overlayable name="car-ui-lib">
        …
        <item type="attr" name="layout_constraintBottom_toBottomOf"/>
        <item type="attr" name="layout_constraintBottom_toTopOf"/>
        <item type="attr" name="layout_constraintCircle"/>
        <item type="attr" name="layout_constraintCircleAngle"/>
        <item type="attr" name="layout_constraintCircleRadius"/>
        <item type="attr" name="layout_constraintDimensionRatio"/>
        <item type="attr" name="layout_constraintEnd_toEndOf"/>
        <item type="attr" name="layout_constraintEnd_toStartOf"/>
        <item type="attr" name="layout_constraintGuide_begin"/>
        <item type="attr" name="layout_constraintGuide_end"/>
        <item type="attr" name="layout_constraintGuide_percent"/>
        <item type="attr" name="layout_constraintHeight_default"/>
        <item type="attr" name="layout_constraintHeight_max"/>
        <item type="attr" name="layout_constraintHeight_min"/>
        <item type="attr" name="layout_constraintHeight_percent"/>
        <item type="attr" name="layout_constraintHorizontal_bias"/>
        <item type="attr" name="layout_constraintHorizontal_chainStyle"/>
        <item type="attr" name="layout_constraintHorizontal_weight"/>
        <item type="attr" name="layout_constraintLeft_creator"/>
        <item type="attr" name="layout_constraintLeft_toLeftOf"/>
        <item type="attr" name="layout_constraintLeft_toRightOf"/>
        <item type="attr" name="layout_constraintRight_creator"/>
        <item type="attr" name="layout_constraintRight_toLeftOf"/>
        <item type="attr" name="layout_constraintRight_toRightOf"/>
        <item type="attr" name="layout_constraintStart_toEndOf"/>
        <item type="attr" name="layout_constraintStart_toStartOf"/>
        <item type="attr" name="layout_constraintTag"/>
        <item type="attr" name="layout_constraintTop_creator"/>
        <item type="attr" name="layout_constraintTop_toBottomOf"/>
        <item type="attr" name="layout_constraintTop_toTopOf"/>
        <item type="attr" name="layout_constraintVertical_bias"/>
        <item type="attr" name="layout_constraintVertical_chainStyle"/>
        …
    </overlayable>
</resources>
  1. ConstraintLayout을 사용하여 CarUiRecyclerView의 목록 항목 레이아웃을 변경합니다. RRO에서 다음 파일을 추가하거나 수정합니다.

    res/xml/sample_overlay.xml

    <?xml version="1.0" encoding="utf-8"?>
    <overlay>
       <item target="id/car_ui_list_item_touch_interceptor" value="@id/car_ui_list_item_touch_interceptor"/>
       <item target="id/car_ui_list_item_reduced_touch_interceptor" value="@id/car_ui_list_item_reduced_touch_interceptor"/>
       <item target="id/car_ui_list_item_start_guideline" value="@id/car_ui_list_item_start_guideline"/>
       <item target="id/car_ui_list_item_icon_container" value="@id/car_ui_list_item_icon_container"/>
       <item target="id/car_ui_list_item_icon" value="@id/car_ui_list_item_icon"/>
       <item target="id/car_ui_list_item_content_icon" value="@id/car_ui_list_item_content_icon"/>
       <item target="id/car_ui_list_item_avatar_icon" value="@id/car_ui_list_item_avatar_icon"/>
       <item target="id/car_ui_list_item_title" value="@id/car_ui_list_item_title"/>
       <item target="id/car_ui_list_item_body" value="@id/car_ui_list_item_body"/>
       <item target="id/car_ui_list_item_action_container_touch_interceptor" value="@id/car_ui_list_item_action_container_touch_interceptor"/>
       <item target="id/car_ui_list_item_action_container" value="@id/car_ui_list_item_action_container"/>
       <item target="id/car_ui_list_item_action_divider" value="@id/car_ui_list_item_action_divider"/>
       <item target="id/car_ui_list_item_switch_widget" value="@id/car_ui_list_item_switch_widget"/>
       <item target="id/car_ui_list_item_checkbox_widget" value="@id/car_ui_list_item_checkbox_widget"/>
       <item target="id/car_ui_list_item_radio_button_widget" value="@id/car_ui_list_item_radio_button_widget"/>
       <item target="id/car_ui_list_item_supplemental_icon" value="@id/car_ui_list_item_supplemental_icon"/>
       <item target="id/car_ui_list_item_end_guideline" value="@id/car_ui_list_item_end_guideline"/>
       <item target="attr/layout_constraintBottom_toBottomOf" value="@attr/layout_constraintBottom_toBottomOf"/>
       <item target="attr/layout_constraintBottom_toTopOf" value="@attr/layout_constraintBottom_toTopOf"/>
       <item target="attr/layout_constraintEnd_toEndOf" value="@attr/layout_constraintEnd_toEndOf"/>
       <item target="attr/layout_constraintEnd_toStartOf" value="@attr/layout_constraintEnd_toStartOf"/>
       <item target="attr/layout_constraintGuide_begin" value="@attr/layout_constraintGuide_begin"/>
       <item target="attr/layout_constraintGuide_end" value="@attr/layout_constraintGuide_end"/>
       <item target="attr/layout_constraintHorizontal_bias" value="@attr/layout_constraintHorizontal_bias"/>
       <item target="attr/layout_constraintLeft_toLeftOf" value="@attr/layout_constraintLeft_toLeftOf"/>
       <item target="attr/layout_constraintLeft_toRightOf" value="@attr/layout_constraintLeft_toRightOf"/>
       <item target="attr/layout_constraintRight_toLeftOf" value="@attr/layout_constraintRight_toLeftOf"/>
       <item target="attr/layout_constraintRight_toRightOf" value="@attr/layout_constraintRight_toRightOf"/>
       <item target="attr/layout_constraintStart_toEndOf" value="@attr/layout_constraintStart_toEndOf"/>
       <item target="attr/layout_constraintStart_toStartOf" value="@attr/layout_constraintStart_toStartOf"/>
       <item target="attr/layout_constraintTop_toBottomOf" value="@attr/layout_constraintTop_toBottomOf"/>
       <item target="attr/layout_constraintTop_toTopOf" value="@attr/layout_constraintTop_toTopOf"/>
       <item target="attr/layout_goneMarginBottom" value="@attr/layout_goneMarginBottom"/>
       <item target="attr/layout_goneMarginEnd" value="@attr/layout_goneMarginEnd"/>
       <item target="attr/layout_goneMarginLeft" value="@attr/layout_goneMarginLeft"/>
       <item target="attr/layout_goneMarginRight" value="@attr/layout_goneMarginRight"/>
       <item target="attr/layout_goneMarginStart" value="@attr/layout_goneMarginStart"/>
       <item target="attr/layout_goneMarginTop" value="@attr/layout_goneMarginTop"/>
       <item target="attr/layout_constraintVertical_chainStyle" value="@attr/layout_constraintVertical_chainStyle"/>
       <item target="layout/car_ui_list_item" value="@layout/car_ui_list_item"/>
    </overlay>
    

    res/layout/car_ui_list_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:tag="carUiListItem"
        android:minHeight="@dimen/car_ui_list_item_height">
    
        <!-- The following touch interceptor views are sized to encompass the specific sub-sections of
        the list item view to easily control the bounds of a background ripple effects. -->
        <com.android.car.ui.SecureView
            android:id="@+id/car_ui_list_item_touch_interceptor"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@drawable/car_ui_list_item_background"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <!-- This touch interceptor does not include the action container -->
        <com.android.car.ui.SecureView
            android:id="@+id/car_ui_list_item_reduced_touch_interceptor"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@drawable/car_ui_list_item_background"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/car_ui_list_item_action_container"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/car_ui_list_item_start_guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_begin="@dimen/car_ui_list_item_start_inset" />
    
        <FrameLayout
            android:id="@+id/car_ui_list_item_icon_container"
            android:layout_width="@dimen/car_ui_list_item_icon_container_width"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="@+id/car_ui_list_item_start_guideline"
            app:layout_constraintTop_toTopOf="parent">
    
            <ImageView
                android:id="@+id/car_ui_list_item_icon"
                android:layout_width="@dimen/car_ui_list_item_icon_size"
                android:layout_height="@dimen/car_ui_list_item_icon_size"
                android:layout_gravity="center"
                android:visibility="gone"
                android:scaleType="fitCenter" />
    
            <ImageView
                android:id="@+id/car_ui_list_item_content_icon"
                android:layout_width="@dimen/car_ui_list_item_content_icon_width"
                android:layout_height="@dimen/car_ui_list_item_content_icon_height"
                android:layout_gravity="center"
                android:visibility="gone"
                android:scaleType="fitCenter" />
    
            <ImageView
                android:id="@+id/car_ui_list_item_avatar_icon"
                android:background="@drawable/car_ui_list_item_avatar_icon_outline"
                android:layout_width="@dimen/car_ui_list_item_avatar_icon_width"
                android:layout_height="@dimen/car_ui_list_item_avatar_icon_height"
                android:layout_gravity="center"
                android:visibility="gone"
                android:scaleType="fitCenter" />
        </FrameLayout>
    
        <CarUiTextView
            android:id="@+id/car_ui_list_item_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/car_ui_list_item_text_start_margin"
            android:singleLine="@bool/car_ui_list_item_single_line_title"
            android:textAppearance="@style/TextAppearance.CarUi.ListItem"
            android:layout_gravity="right"
            android:gravity="right"
            android:textAlignment="viewEnd"
            app:layout_constraintBottom_toTopOf="@+id/car_ui_list_item_body"
            app:layout_constraintEnd_toStartOf="@+id/car_ui_list_item_action_container"
            app:layout_constraintStart_toEndOf="@+id/car_ui_list_item_icon_container"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_chainStyle="packed"
            app:layout_goneMarginStart="@dimen/car_ui_list_item_text_no_icon_start_margin" />
        <CarUiTextView
            android:id="@+id/car_ui_list_item_body"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/car_ui_list_item_text_start_margin"
            android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body"
            android:layout_gravity="right"
            android:gravity="right"
            android:textAlignment="viewEnd"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/car_ui_list_item_action_container"
            app:layout_constraintStart_toEndOf="@+id/car_ui_list_item_icon_container"
            app:layout_constraintTop_toBottomOf="@+id/car_ui_list_item_title"
            app:layout_goneMarginStart="@dimen/car_ui_list_item_text_no_icon_start_margin" />
    
        <!-- This touch interceptor is sized and positioned to encompass the action container   -->
        <com.android.car.ui.SecureView
            android:id="@+id/car_ui_list_item_action_container_touch_interceptor"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@drawable/car_ui_list_item_background"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="@id/car_ui_list_item_action_container"
            app:layout_constraintEnd_toEndOf="@id/car_ui_list_item_action_container"
            app:layout_constraintStart_toStartOf="@id/car_ui_list_item_action_container"
            app:layout_constraintTop_toTopOf="@id/car_ui_list_item_action_container" />
    
        <FrameLayout
            android:id="@+id/car_ui_list_item_action_container"
            android:layout_width="wrap_content"
            android:minWidth="@dimen/car_ui_list_item_icon_container_width"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="@+id/car_ui_list_item_end_guideline"
            app:layout_constraintTop_toTopOf="parent">
    
            <View
                android:id="@+id/car_ui_list_item_action_divider"
                android:layout_width="@dimen/car_ui_list_item_action_divider_width"
                android:layout_height="@dimen/car_ui_list_item_action_divider_height"
                android:layout_gravity="start|center_vertical"
                android:background="@drawable/car_ui_list_item_divider" />
    
            <Switch
                android:id="@+id/car_ui_list_item_switch_widget"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:clickable="false"
                android:focusable="false" />
    
            <CheckBox
                android:id="@+id/car_ui_list_item_checkbox_widget"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:clickable="false"
                android:focusable="false" />
    
            <RadioButton
                android:id="@+id/car_ui_list_item_radio_button_widget"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:clickable="false"
                android:focusable="false" />
    
            <ImageView
                android:id="@+id/car_ui_list_item_supplemental_icon"
                android:layout_width="@dimen/car_ui_list_item_supplemental_icon_size"
                android:layout_height="@dimen/car_ui_list_item_supplemental_icon_size"
                android:layout_gravity="center"
                android:scaleType="fitCenter" />
        </FrameLayout>
    
        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/car_ui_list_item_end_guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_end="@dimen/car_ui_list_item_end_inset" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. car_ui_list_item.xml은 앱의 종속 항목으로 포함되지 않은 여러 구성요소/리소스를 참조합니다. 이것이 car-ui-lib 리소스입니다. app/build.gradle에서 RRO 앱에 car-ui-lib을 종속 항목으로 추가하여 이 문제를 해결할 수 있습니다.

    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. 오른쪽으로 정렬된 제목과 본문

해당 속성이 overlayable.xml이라는 car-ui-lib 파일과 RRO sample_overlay.xml에 있을 때 AndroidX 구성요소(ConstraintLayout)를 사용하여 car-ui-lib에 RRO를 적용했습니다. 자체 앱에서도 비슷한 작업을 할 수 있습니다. car-ui-lib과 마찬가지로 앱의 overlayable.xml에 해당 attrs를 모두 추가하면 됩니다.

하지만 앱의 build.gradlecar-ui-lib이 종속 항목으로 포함되어 있으면(앱이 car-ui-lib 구성요소를 사용하면) AndroidX 구성요소를 사용하여 앱을 RRO하는 것은 불가능합니다. 이미 속성 매핑이 car-ui-lib 라이브러리의 overlayable.xml에 정의되어 있으므로 car-ui-lib을 종속 항목으로 사용하여 이를 앱의 overlayable.xml에 추가하면 아래와 같은 mergeDebugResources 오류가 발생합니다. 이는 이러한 속성이 여러 overlayable.xml 파일에 있기 때문입니다.

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