car-ui-lib
라이브러리를 사용하여 일관된 차량용 인포테인먼트(IVI) 시스템을 실행합니다. 이 Codelab에서는 car-ui-lib
를 소개하고 런타임 리소스 오버레이(RRO)를 사용하여 이 라이브러리의 구성요소를 맞춤설정하는 방법을 설명합니다.
학습할 내용
다음을 학습합니다.
- Android 앱에
car-ui-lib
구성요소를 포함하는 방법 - Gradle을 사용하여 Android 앱과 RRO를 빌드하는 방법
car-ui-lib
와 함께 RRO를 사용하는 방법
이 Codelab에서는 RRO 작동 방식을 자세히 다루지 않습니다. 자세한 내용은 런타임에 앱의 리소스 값 변경과 런타임 리소스 오버레이 문제 해결을 참고하세요.
시작하기 전에
기본 요건
시작하려면 다음 요건이 충족되어야 합니다.
명령줄이 있는 컴퓨터(Linux 컴퓨터나 Mac, Linux용 Windows 하위 시스템이 있는 Windows 컴퓨터)
내 컴퓨터에 연결된 Android 기기나 에뮬레이터. Android 소스 다운로드와 Android 빌드를 참고하세요.
RRO에 관한 기본 지식
새 Android 앱 만들기
시간: 15분
이 섹션에서는 새로운 Android 스튜디오 프로젝트를 만듭니다.
Android 스튜디오에서
EmptyActivity
를 사용하여 앱을 만듭니다.그림 1.Empty Activity 만들기 앱 이름을
CarUiCodelab
으로 지정하고 Java 언어를 선택합니다. 원하는 경우 파일 위치도 선택할 수 있습니다. 나머지 설정은 기본값을 사용합니다.그림 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>
이 코드 블록은 정의되지 않은 문자열
sample_text
를 표시합니다.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>
앱을 빌드하려면 오른쪽 상단의 녹색 Play 버튼을 클릭합니다. 이렇게 하면 Gradle을 통해 에뮬레이터나 Android 기기에 자동으로 APK가 설치됩니다.
새 앱이 에뮬레이터나 Android 기기에서 자동으로 열립니다. 열리지 않으면 지금 설치된 앱 런처에서 CarUiCodelab
앱을 엽니다.
다음과 같이 표시됩니다.
![새 CarUiCodelab 앱 열기](https://source.android.com/static/docs/automotive/images/codelab_04.png?hl=ko)
Android 앱에 car-ui-lib 추가
시간: 15분
다음과 같이 앱에 car-ui-lib
를 추가합니다.
프로젝트의
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
가 있으므로 툴바를 앱에 추가합니다.
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); }
ToolbarController
를 가져와야 합니다.import com.android.car.ui.core.CarUi; import com.android.car.ui.toolbar.ToolbarController;
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>
앱을 빌드하려면 이전처럼 녹색 Play 버튼을 누릅니다.
앱에 RRO 추가
시간: 30분
RRO를 잘 알고 있다면 다음 섹션 앱에 권한 컨트롤러 추가로 이동합니다. 잘 모르는 경우 RRO의 기본사항을 알아보려면 런타임에 앱의 리소스 값 변경을 참고하세요.
앱에 권한 컨트롤러 추가
RRO 패키지에서 오버레이하는 리소스를 제어하려면 overlayable.xml
파일을 앱의 /res
폴더에 추가합니다. 이 파일은 앱(타겟)과 RRO 패키지(오버레이) 간의 권한 컨트롤러 역할을 합니다.
앱에
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 패키지를 만듭니다.
새 프로젝트를 만들려면 File > New > New Project를 선택합니다. RRO 패키지에는 리소스만 포함되므로 Empty Activity가 아닌 No Activity를 선택해야 합니다.
구성은 아래 그림과 같이 표시됩니다. 저장되는 위치는 다를 수 있습니다.
새
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 패키지로 매핑합니다.다음 코드 블록을
…/res/xml/sample_overlay.xml
에 복사합니다.<?xml version="1.0" encoding="utf-8"?> <overlay> <item target="string/sample_text" value="@string/sample_text"/> </overlay>
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 타겟을 빌드하려면 녹색 Play 버튼을 눌러 에뮬레이터나 Android 기기에서 RRO의 Gradle 빌드를 만듭니다.
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에 오류가 포함되어 있으면 계속하기 전에 런타임 리소스 오버레이 문제 해결을 참고하세요.
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!](https://source.android.com/static/docs/automotive/images/codelab_09.png?hl=ko)
축하합니다. 첫 번째 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분
툴바의 배경 색상을 변경하려면 다음을 실행하세요.
다음 값을 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 구성에 포함되면 잘못된 값이 타겟팅되므로 툴바가 변경되지 않습니다.RRO의
AndroidManifest.xml
에서car-ui-lib
를 가리키도록targetName
을 업데이트합니다.… android:targetName="car-ui-lib" …
RRO하려는 타겟 패키지마다 새 RRO 패키지를 만들어야 합니다(MUST). 예를 들어 두 타겟에 관한 오버레이를 만들면 두 개의 오버레이 APK를 만들어야 합니다.
이전과 동일한 방식으로 RRO를 빌드, 확인, 설치, 사용 설정합니다.
앱이 다음과 같이 표시됩니다.
![새로운 툴바 배경 색상](https://source.android.com/static/docs/automotive/images/codelab_10.png?hl=ko)
RRO 레이아웃과 스타일
시간: 15분
이 연습에서는 앞서 빌드한 앱과 유사한 새 앱을 빌드합니다. 이 앱에서는 레이아웃을 오버레이할 수 있습니다. 전과 동일한 단계를 따르거나 기존 앱을 수정하세요.
다음 줄을
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>
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>
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>
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>
새 앱의 이름을 가리키도록
AndroidManifest.xml
의targetName
을 변경합니다.… android:targetName="CarUiCodelab" …
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>
전과 같은 방식(녹색 Play 버튼)으로 앱과 RRO를 빌드하고 설치합니다. RRO를 사용 설정해야 합니다.
앱과 RRO가 다음과 같이 렌더링됩니다. Hello World RRO 텍스트가 녹색이며 레이아웃 RRO에 지정된 대로 중앙에 위치합니다.
![Hello World RRO](https://source.android.com/static/docs/automotive/images/codelab_11.png?hl=ko)
앱에 CarUiRecyclerView 추가
시간: 15분
CarUiRecyclerView
인터페이스는 car-ui-lib
리소스를 통해 맞춤설정된 RecyclerView
에 액세스하는 API를 제공합니다. 예를 들어 CarUiRecyclerView
는 런타임에 플래그를 확인하여 스크롤바의 사용 설정 여부를 결정하고 상응하는 레이아웃을 선택합니다.
![CarUiRecyclerViewContainer](https://source.android.com/static/docs/automotive/images/codelab_12.png?hl=ko)
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; }
전과 같이 앱을 빌드하고 설치합니다.
이제 다음과 같이 CarUiRecyclerView
가 표시됩니다.
![CarUiRecyclerView](https://source.android.com/static/docs/automotive/images/codelab_13.png?hl=ko)
RRO를 사용하여 스크롤바 삭제
시간: 10분
이 연습에서는 RRO를 사용하여 스크롤바를 CarUiRecyclerView
에서 삭제하는 방법을 보여줍니다.
RRO에서 다음 파일을 추가하고 수정합니다.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.caruirro"> <application android:hasCode="false" /> <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/> <overlay android:targetPackage="com.example.caruicodelab" android:targetName="car-ui-lib" android:isStatic="false" android:resourcesMap="@xml/sample_overlay" /> </manifest>
res/values/bools.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <bool name="car_ui_scrollbar_enable">false</bool> </resources>
리소스
car_ui_scrollbar_enable
은car-ui-lib
불리언 리소스로,CarUiRecyclerView
의 Up 버튼과 Down 버튼이 있는 자동차 최적화 스크롤바가 있는지를 제어합니다.false
로 설정하면CarUiRecyclerView
는 AndroidXRecyclerView
처럼 동작합니다.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](https://source.android.com/static/docs/automotive/images/codelab_14.png?hl=ko)
레이아웃을 사용하여 CarUiRecyclerView 스크롤바 오버레이
시간: 15분
이 연습에서는 CarUiRecyclerView
스크롤바 레이아웃을 수정합니다.
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.xml
및colors.xml
에서 선언하거나res/color/
폴더의 색상 파일로 지정하는 것이 좋습니다. 자세한 내용은 기여자를 위한 AOSP Java 코드 스타일을 참고하세요.전과 같이 앱을 빌드하고 설치합니다. 파란색 스크롤바와 회색 레일로
CarUiRecyclerView
를 빌드했습니다.
축하합니다. 두 화살표가 모두 스크롤바 하단을 따라 표시됩니다. Android 스튜디오를 통해 Gradle 빌드 시스템을 사용하여 RRO를 car-ui-lib
레이아웃 리소스 파일에 성공적으로 적용했습니다.
![파란색 스크롤바와 회색 레일이 있는 CarUiRecyclerView](https://source.android.com/static/docs/automotive/images/codelab_15.png?hl=ko)
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>
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>
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' }
이제 제목과 본문이 왼쪽이 아닌 오른쪽으로 정렬됩니다.
![오른쪽으로 정렬된 제목과 본문](https://source.android.com/static/docs/automotive/images/codelab_16.png?hl=ko)
해당 속성이 overlayable.xml
이라는 car-ui-lib
파일과 RRO sample_overlay.xml
에 있을 때 AndroidX 구성요소(ConstraintLayout
)를 사용하여 car-ui-lib
에 RRO를 적용했습니다. 자체 앱에서도 비슷한 작업을 할 수 있습니다. car-ui-lib
과 마찬가지로 앱의 overlayable.xml
에 해당 attrs
를 모두 추가하면 됩니다.
하지만 앱의 build.gradle
에 car-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'