활동 삽입과 Material Design으로 목록-세부정보 레이아웃 빌드

1. 소개

대형 디스플레이를 사용하면 사용자 환경을 개선하고 사용자 생산성을 높이는 앱 레이아웃과 UI를 만들 수 있습니다. 하지만 앱이 폴더블이 아닌 소형 디스플레이에 맞게 설계되었다면 태블릿, 폴더블, ChromeOS 기기의 추가 디스플레이 공간을 활용하지 못할 가능성이 높습니다.

대형 디스플레이를 최대한 활용하도록 앱을 업데이트하는 작업은 시간과 비용이 많이 들 수 있으며, 많은 활동을 기반으로 하는 기존 앱의 경우 특히 그러합니다.

Android 12L(API 수준 32)에 도입된 활동 삽입을 사용하면 활동 기반 앱이 대형 화면에 여러 활동을 동시에 표시하므로 목록-세부정보와 같은 창 두 개 레이아웃을 만들 수 있습니다. Kotlin이나 Java를 다시 코딩할 필요가 없습니다. 몇 가지 종속 항목을 추가하고, XML 구성 파일을 만들고, 이니셜라이저를 구현하고, 앱 매니페스트에 몇 가지 항목을 추가할 수 있습니다. 또는 코드로 작업하고 싶다면 앱 기본 활동의 onCreate() 메서드에 Jetpack WindowManager API 호출을 몇 개 추가하면 됩니다.

기본 요건

이 Codelab을 완료하려면 다음 경험이 필요합니다.

  • Android 앱 빌드
  • 활동 사용
  • XML 쓰기
  • Android 스튜디오에서 가상 기기 설정을 포함한 작업

빌드할 항목

이 Codelab에서는 SlidingPaneLayout과 유사한 창 두 개의 동적 레이아웃을 지원하도록 활동 기반 앱을 업데이트합니다. 작은 화면에서는 앱은 작업 창에서 활동을 서로 오버레이(스택)합니다.

활동 A, B, C가 작업 창에 스택됨.

대형 화면에서는 앱은 사양에 따라 두 개의 활동을 화면에 나란히 또는 위아래로 동시에 표시합니다.

4b27b07b7361d6d8.png

학습할 내용

활동 삽입을 구현하는 다음의 두 가지 방법

  • XML 구성 파일 사용
  • Jetpack WindowManager API 호출 사용

필요한 항목

  • Android 스튜디오 최신 버전
  • Android 휴대전화 또는 에뮬레이터
  • Android 소형 태블릿 또는 에뮬레이터
  • Android 대형 태블릿 또는 에뮬레이터

2. 설정

샘플 앱 가져오기

1단계: 저장소 클론하기

대형 화면 Codelab Git 저장소를 클론합니다.

git clone https://github.com/android/large-screen-codelabs

또는 대형 화면 Codelab ZIP 파일을 다운로드하고 보관 취소합니다.

소스 코드 다운로드

2단계: Codelab 소스 파일 검사하기

activity-embedding 폴더로 이동합니다.

3단계: Codelab 프로젝트 열기

Android 스튜디오에서 Kotlin 또는 Java 프로젝트를 엽니다.

저장소 및 ZIP 파일의 활동 폴더 파일 목록

저장소 및 ZIP 파일의 activity-embedding 폴더에는 두 개의 Android 스튜디오 프로젝트가 포함되어 있습니다. 하나는 Kotlin으로 작성된 프로젝트이고, 다른 하나는 Java로 작성된 프로젝트입니다. 원하는 프로젝트를 엽니다. Codelab 스니펫은 두 가지 언어로 모두 제공됩니다.

가상 기기 만들기

API 수준 32 이상의 Android 휴대전화, 소형 태블릿 또는 대형 태블릿이 없는 경우 Android 스튜디오에서 기기 관리도구를 열고 다음의 필수 가상 기기를 만듭니다.

  • 휴대전화: Pixel 6, API 수준 32 이상
  • 소형 태블릿: 7 WSVGA(태블릿), API 수준 32 이상
  • 대형 태블릿: Pixel C, API 수준 32 이상

3. 앱 실행

샘플 앱은 항목 목록을 표시합니다. 사용자가 항목을 선택하면 앱에 항목 정보가 표시됩니다.

앱은 세 가지 활동으로 구성되어 있습니다.

  • ListActivityRecyclerView의 항목 목록을 포함합니다.
  • DetailActivity - 목록에서 항목이 선택되면 목록 항목의 정보를 표시합니다.
  • SummaryActivity — 요약 목록 항목이 선택되면 정보 요약을 표시합니다.

활동 삽입을 사용하지 않을 때의 동작

샘플 앱을 실행하여 활동 삽입을 사용하지 않을 때 앱이 어떻게 작동하는지 확인합니다.

  1. 대형 태블릿 또는 Pixel C 에뮬레이터에서 샘플 앱을 실행합니다. 기본 (목록) 활동이 표시됩니다.

세로 방향으로 샘플 앱이 실행 중인 대형 태블릿. 목록 활동 전체 화면.

  1. 보조 (세부정보) 활동을 시작할 목록 항목을 선택합니다. 세부정보 활동이 목록 활동을 오버레이합니다.

세로 방향으로 샘플 앱이 실행 중인 대형 태블릿. 세부정보 활동 전체 화면.

  1. 태블릿을 가로 방향으로 회전합니다. 보조 활동이 여전히 기본 활동을 오버레이하고 전체 디스플레이를 차지합니다.

가로 방향으로 샘플 앱이 실행 중인 대형 태블릿. 세부정보 활동 전체 화면.

  1. 뒤로 컨트롤(앱 바의 왼쪽 화살표)을 선택하여 목록으로 돌아갑니다.
  2. 목록의 마지막 항목인 요약을 선택하여 요약 활동을 보조 활동으로 실행합니다. 요약 활동이 목록 활동을 오버레이합니다.

세로 방향으로 샘플 앱이 실행 중인 대형 태블릿. 요약 활동 전체 화면.

  1. 태블릿을 가로 방향으로 회전합니다. 보조 활동이 여전히 기본 활동을 오버레이하고 전체 디스플레이를 차지합니다.

가로 방향으로 샘플 앱이 실행 중인 대형 태블릿. 요약 활동 전체 화면.

활동 삽입을 사용할 때의 동작

이 Codelab을 완료하면 목록 활동과 세부정보 활동이 목록 세부정보 레이아웃에 가로 모드 방향으로 나란히 표시됩니다.

가로 방향으로 샘플 앱이 실행 중인 대형 태블릿. 목록 세부정보 레이아웃의 목록 활동 및 세부정보 활동.

그러나 활동이 분할 내에서 시작되더라도 요약이 전체 화면에 표시되도록 요약을 구성합니다. 요약이 분할을 오버레이합니다.

가로 방향으로 샘플 앱이 실행 중인 대형 태블릿. 요약 활동 전체 화면.

4. 배경

활동 삽입은 앱 작업 창을 기본 컨테이너와 보조 컨테이너로 분할합니다. 모든 활동은 다른 활동을 실행하여 분할을 시작할 수 있습니다. 시작하는 활동이 기본 컨테이너를 차지하고, 실행되는 활동이 보조 컨테이너를 차지합니다.

기본 활동은 보조 컨테이너에서 추가 활동을 실행할 수 있습니다. 그러면 두 컨테이너에서의 활동이 각 컨테이너에서의 활동을 실행할 수 있습니다. 각 컨테이너에는 활동 스택이 포함될 수 있습니다. 자세한 내용은 활동 삽입 개발자 가이드를 참고하세요.

XML 구성 파일을 만들거나 Jetpack WindowManager API를 호출하여 활동 삽입을 지원하도록 앱을 구성할 수 있습니다. XML 구성 접근 방식부터 시작하겠습니다.

5. XML 구성

활동 삽입 컨테이너와 분할은 XML 구성 파일에서 만든 분할 규칙을 기반으로 Jetpack WindowManager 라이브러리에 의해 생성되고 관리됩니다.

WindowManager 종속 항목 추가

앱의 모듈 수준 build.gradle 파일에 라이브러리 종속 항목을 추가하여 샘플 앱에서 WindowManager 라이브러리에 액세스하도록 사용 설정합니다. 예를 들면 다음과 같습니다.

build.gradle

 implementation 'androidx.window:window:1.2.0'

시스템에 통지

앱이 활동 삽입을 구현했음을 시스템이 인식하도록 합니다.

다음과 같이 앱 매니페스트 파일의 <application> 요소에 android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED 속성을 추가하고 값을 true로 설정합니다.

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
            android:value="true" />
    </application>
</manifest>

기기 제조업체(OEM)는 이 설정을 사용하여 활동 삽입을 지원하는 앱에 맞춤 기능을 사용 설정합니다. 예를 들어 기기는 가로 모드 디스플레이에서 세로 모드 전용 활동(android:screenOrientation 참고)을 레터박스 처리하여, 활동 삽입의 창 두 개 레이아웃으로 원활하게 전환되도록 활동의 방향을 설정할 수 있습니다.

가로 모드 디스플레이의 세로 모드 전용 앱에 활동 삽입이 사용된 모습. 레터박스 처리된 세로 모드 전용 활동 A가 삽입된 활동 B를 실행함.

구성 파일 만들기

resources를 루트 요소로 사용하여 앱의 res/xml 폴더에 main_split_config.xml이라는 XML 리소스 파일을 만듭니다.

XML 네임스페이스를 다음과 같이 변경합니다.

main_split_config.xml

xmlns:window="http://schemas.android.com/apk/res-auto"

분할 쌍 규칙

구성 파일에 다음 분할 규칙을 추가합니다.

main_split_config.xml

<!-- Define a split for the named activity pair. -->
<SplitPairRule
    window:splitRatio="0.33"
    window:splitMinWidthDp="840"
    window:finishPrimaryWithSecondary="never"
    window:finishSecondaryWithPrimary="always">
  <SplitPairFilter
      window:primaryActivityName=".ListActivity"
      window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

규칙은 다음을 수행합니다.

  • 분할을 공유하는 활동에 분할 옵션 구성
  • splitRatio - 기본 활동이 차지하는 작업 창 비율(33%)을 지정하고 남은 공간은 보조 활동을 위해 남겨 둡니다.
  • splitMinWidthDp - 두 활동이 동시에 화면에 표시되는 데 필요한 최소 디스플레이 너비(840)를 지정합니다. 단위는 디스플레이에 상관없이 픽셀(dp)입니다.
  • finishPrimaryWithSecondary - 보조 컨테이너의 모든 활동이 종료될 때 기본 분할 컨테이너의 활동이 종료(never)되는지를 지정합니다.
  • finishSecondaryWithPrimary - 기본 컨테이너 활동의 모든 활동이 종료될 때 보조 분할 컨테이너의 활동이 종료(always)되는지를 지정합니다.
  • 작업 창 분할을 공유하는 활동을 정의하는 분할 필터를 포함합니다. 기본 활동은 ListActivity입니다. 보조 활동은 DetailActivity입니다.

자리표시자 규칙

목록-세부정보 분할은 열려 있지만 목록 항목이 아직 선택되지 않은 경우 등 보조 컨테이너에 사용 가능한 내용이 없는 경우 자리표시자 활동이 활동 분할의 보조 컨테이너를 차지합니다. 자세한 내용은 활동 삽입 개발자 가이드의 자리표시자를 참고하세요.

다음 자리표시자 규칙을 구성 파일에 추가합니다.

main_split_config.xml

<!-- Automatically launch a placeholder for the detail activity. -->
<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity"
    window:splitRatio="0.33"
    window:splitMinWidthDp="840"
    window:finishPrimaryWithPlaceholder="always"
    window:stickyPlaceholder="false">
  <ActivityFilter
      window:activityName=".ListActivity"/>
</SplitPlaceholderRule>

규칙은 다음을 수행합니다.

  • 자리표시자 활동 PlaceholderActivity를 식별합니다. 이 활동은 다음 단계에서 만듭니다.
  • 자리표시자의 옵션을 구성합니다.
  • splitRatio - 기본 활동이 차지하는 작업 창 비율(33%)을 지정하고 남은 공간은 자리표시자를 위해 남겨 둡니다. 일반적으로 이 값은 자리표시자와 연결된 분할 쌍 규칙의 분할 비율과 일치해야 합니다.
  • splitMinWidthDp - 자리표시자가 기본 활동과 함께 화면에 표시되는 데 필요한 최소 디스플레이 너비(840)를 지정합니다. 일반적으로 이 값은 자리표시자와 연결된 분할 쌍 규칙의 최소 너비와 일치해야 합니다. 단위는 디스플레이에 상관없이 픽셀(dp)입니다.
  • finishPrimaryWithPlaceholder - 자리표시자가 종료될 때 기본 분할 컨테이너의 활동이 종료(always)되는지를 지정합니다.
  • stickyPlaceholder: 디스플레이가 창 두 개의 디스플레이에서 단일 창 디스플레이로 크기가 축소될 때(예: 폴더블 기기가 접힐 때) 자리표시자를 화면에 상단 활동으로 유지(false)해야 하는지를 나타냅니다.
  • 자리표시자와 작업 창 분할을 공유하는 활동(ListActivity)을 지정하는 활동 필터를 포함합니다.

자리표시자는 기본 활동이 자리표시자 활동 필터의 활동과 동일한, 분할 쌍 규칙의 보조 활동을 대신합니다. 이 Codelab의 'XML 구성' 섹션의 '분할 쌍 규칙'을 참고하세요.

활동 규칙

활동 규칙은 범용 규칙입니다. 전체 작업 창을 차지하도록 할 활동(즉, 분할의 일부가 되지 않는 활동)은 활동 규칙으로 지정할 수 있습니다. 자세한 내용은 활동 삽입 개발자 가이드의 전체 크기 창 모달을 참고하세요.

요약 활동이 분할을 오버레이하여 전체 작업 창을 채우도록 합니다. 뒤로 탐색을 실행하면 분할로 돌아갑니다.

구성 파일에 다음 활동 규칙을 추가합니다.

main_split_config.xml

<!-- Activities that should never be in a split. -->
<ActivityRule
    window:alwaysExpand="true">
  <ActivityFilter
      window:activityName=".SummaryActivity"/>
</ActivityRule>

규칙은 다음을 수행합니다.

  • 전체 창으로 표시되어야 하는 활동(SummaryActivity).)을 식별합니다.
  • 활동의 옵션을 구성합니다.
  • alwaysExpand - 사용 가능한 모든 디스플레이 공간을 채우도록 활동을 확장해야 하는지 지정합니다.

소스 파일

완성된 XML 구성 파일의 모습은 다음과 같습니다.

main_split_config.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:window="http://schemas.android.com/apk/res-auto">

    <!-- Define a split for the named activity pair. -->
    <SplitPairRule
        window:splitRatio="0.33"
        window:splitMinWidthDp="840"
        window:finishPrimaryWithSecondary="never"
        window:finishSecondaryWithPrimary="always">
      <SplitPairFilter
          window:primaryActivityName=".ListActivity"
          window:secondaryActivityName=".DetailActivity"/>
    </SplitPairRule>

    <!-- Automatically launch a placeholder for the detail activity. -->
    <SplitPlaceholderRule
        window:placeholderActivityName=".PlaceholderActivity"
        window:splitRatio="0.33"
        window:splitMinWidthDp="840"
        window:finishPrimaryWithPlaceholder="always"
        window:stickyPlaceholder="false">
      <ActivityFilter
          window:activityName=".ListActivity"/>
    </SplitPlaceholderRule>

    <!-- Activities that should never be in a split. -->
    <ActivityRule
        window:alwaysExpand="true">
      <ActivityFilter
          window:activityName=".SummaryActivity"/>
    </ActivityRule>

</resources>

자리표시자 활동 만들기

XML 구성 파일에 지정된 자리표시자로 사용할 새 활동을 만들어야 합니다. 이 활동은 매우 간단할 수 있습니다. 결국에는 내용이 여기에 표시될 것임을 사용자에게 알리는 작업과 같은 것입니다.

샘플 앱의 기본 소스 폴더에 활동을 만듭니다.

Android 스튜디오에서 다음을 수행합니다.

  1. 샘플 앱 소스 폴더 com.example.activity_embedding을 마우스 오른쪽 버튼으로 클릭(보조 버튼 클릭)합니다.
  2. New > Activity > Empty Views Activity를 선택합니다.
  3. 활동 이름을 PlaceholderActivity로 지정합니다.
  4. Finish를 선택합니다.

Android 스튜디오가 샘플 앱 패키지에 활동을 만들고 앱 매니페스트 파일에 활동을 추가하며 res/layout 폴더에 activity_placeholder.xml이라는 레이아웃 리소스 파일을 만듭니다.

  1. 다음과 같이 샘플 앱의 AndroidManifest.xml 파일에서 자리표시자 활동의 라벨을 빈 문자열로 설정합니다.

AndroidManifest.xml

<activity
    android:name=".PlaceholderActivity"
    android:exported="false"
    android:label="" />
  1. res/layout 폴더에 있는 activity_placeholder.xml 레이아웃 파일의 내용을 다음으로 바꿉니다.

activity_placeholder.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:background="@color/gray"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PlaceholderActivity">

  <TextView
      android:id="@+id/textViewPlaceholder"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/placeholder_text"
      android:textSize="36sp"
      android:textColor="@color/obsidian"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
  1. 마지막으로 res/values 폴더의 strings.xml 리소스 파일에 다음 문자열 리소스를 추가합니다.

strings.xml

<string name="placeholder_text">Placeholder</string>

이니셜라이저 만들기

WindowManager RuleController 구성요소는 XML 구성 파일에 정의된 규칙을 파싱하고 그 규칙을 시스템에 사용 가능하도록 만듭니다.

Jetpack Startup 라이브러리 Initializer를 사용하면 RuleController가 구성 파일에 액세스할 수 있습니다.

Startup 라이브러리는 앱 시작 시 구성요소 초기화를 실행합니다. RuleController가 분할 규칙에 액세스하고 필요한 경우 이를 적용할 수 있도록 활동이 시작되기 전에 초기화가 이루어져야 합니다.

Startup 라이브러리 종속 항목 추가

시작 기능을 사용 설정하려면 샘플 앱의 모듈 수준 build.gradle 파일에 Startup 라이브러리 종속 항목을 추가합니다. 예를 들면 다음과 같습니다.

build.gradle

implementation 'androidx.startup:startup-runtime:1.1.1'

RuleController의 이니셜라이저 구현

Startup Initializer 인터페이스를 구현합니다.

Android 스튜디오에서 다음을 실행합니다.

  1. 샘플 앱 소스 폴더 com.example.activity_embedding을 마우스 오른쪽 버튼으로 클릭(보조 버튼 클릭)합니다.
  2. New > Kotlin Class/File 또는 New > Java Class를 선택합니다.
  3. 클래스 이름을 SplitInitializer로 지정
  4. Enter 키 누르기 - Android 스튜디오의 샘플 앱 패키지에 클래스가 생성됩니다.
  5. 클래스 파일의 내용을 다음으로 바꿉니다.

SplitInitializer.kt

package com.example.activity_embedding

import android.content.Context
import androidx.startup.Initializer
import androidx.window.embedding.RuleController

class SplitInitializer : Initializer<RuleController> {

  override fun create(context: Context): RuleController {
    return RuleController.getInstance(context).apply {
      setRules(RuleController.parseRules(context, R.xml.main_split_config))
    }
  }

  override fun dependencies(): List<Class<out Initializer<*>>> {
    return emptyList()
  }
}

SplitInitializer.java

package com.example.activity_embedding;

import android.content.Context;
import androidx.annotation.NonNull;
import androidx.startup.Initializer;
import androidx.window.embedding.RuleController;
import java.util.Collections;
import java.util.List;

public class SplitInitializer implements Initializer<RuleController> {

   @NonNull
   @Override
   public RuleController create(@NonNull Context context) {
      RuleController ruleController = RuleController.getInstance(context);
      ruleController.setRules(
          RuleController.parseRules(context, R.xml.main_split_config)
      );
      return ruleController;
   }

   @NonNull
   @Override
   public List<Class<? extends Initializer<?>>> dependencies() {
       return Collections.emptyList();
   }
}

이니셜라이저는 정의((main_split_config)를 포함하는 XML 리소스 파일의 ID를 구성요소의 parseRules() 메서드에 전달하여 분할 규칙을 RuleController 구성요소에 사용 가능하도록 만듭니다. setRules() 메서드는 파싱된 규칙을 RuleController에 추가합니다.

초기화 제공자 만들기

제공자는 분할 규칙 초기화 프로세스를 호출합니다.

샘플 앱 매니페스트 파일의 <application> 요소에 제공자로 androidx.startup.InitializationProvider를 추가하고 SplitInitializer를 참조합니다.

AndroidManifest.xml

<provider android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- Make SplitInitializer discoverable by InitializationProvider. -->
    <meta-data android:name="${applicationId}.SplitInitializer"
        android:value="androidx.startup" />
</provider>

InitializationProviderSplitInitializer를 초기화하면 이 요소가 XML 구성 파일(main_split_config.xml)을 파싱하고 RuleController에 규칙을 추가하는 RuleController 메서드를 호출합니다(위의 'RuleController의 이니셜라이저 구현' 참고).

InitializationProvider가 앱의 onCreate() 메서드가 실행되기 전에 SplitInitializer를 검색하고 초기화합니다. 따라서 기본 앱 활동이 시작될 때 분할 규칙이 적용됩니다.

소스 파일

완성된 앱 매니페스트는 다음과 같습니다.

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

  <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.Activity_Embedding"
      tools:targetApi="32">
    <activity
        android:name=".ListActivity"
        android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity
        android:name=".DetailActivity"
        android:exported="false"
        android:label="" />
    <activity
        android:name=".SummaryActivity"
        android:exported="false"
        android:label="" />
    <activity
        android:name=".PlaceholderActivity"
        android:exported="false"
        android:label="" />
    <property
        android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
        android:value="true" />
    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
      <!-- Make SplitInitializer discoverable by InitializationProvider. -->
      <meta-data
          android:name="${applicationId}.SplitInitializer"
          android:value="androidx.startup" />
    </provider>
  </application>

</manifest>

초기화 바로가기

XML 구성을 WindowManager API와 함께 사용하는 데 익숙하다면 Startup 라이브러리 이니셜라이저와 매니페스트 제공자를 제거하여 훨씬 더 간단하게 구현할 수 있습니다.

XML 구성 파일을 만든 후 다음 단계를 따릅니다.

1단계: Application의 서브클래스 만들기

애플리케이션 서브클래스는 앱 프로세스를 만들 때 인스턴스화되는 첫 번째 클래스입니다. 활동이 시작되기 전에 규칙이 적용되도록 서브클래스의 onCreate() 메서드에서 RuleController에 분할 규칙을 추가합니다.

Android 스튜디오에서 다음을 실행합니다.

  1. 샘플 앱 소스 폴더 com.example.activity_embedding을 마우스 오른쪽 버튼으로 클릭(보조 버튼 클릭)합니다.
  2. New > Kotlin Class/File 또는 New > Java Class를 선택합니다.
  3. 클래스 이름을 SampleApplication으로 지정
  4. Enter 키 누르기 - Android 스튜디오의 샘플 앱 패키지에 클래스가 생성됨
  5. Application 상위 유형에서 클래스 확장

SampleApplication.kt

package com.example.activity_embedding

import android.app.Application

/**
 * Initializer for activity embedding split rules.
 */
class SampleApplication : Application() {

}

SampleApplication.java

package com.example.activity_embedding;

import android.app.Application;

/**
 * Initializer for activity embedding split rules.
 */
public class SampleApplication extends Application {

}

2단계: RuleController 초기화하기

XML 구성 파일의 분할 규칙을 애플리케이션 서브클래스의 onCreate() 메서드에 있는 RuleController에 추가합니다.

RuleController에 규칙을 추가하려면 다음 단계를 따릅니다.

  1. RuleController의 싱글톤 인스턴스 가져오기
  2. RuleController의 Java 정적 메서드 또는 Kotlin 컴패니언 parseRules() 메서드를 사용하여 XML 파일을 파싱합니다.
  3. setRules() 메서드를 사용하여 파싱된 규칙을 RuleController에 추가합니다.

SampleApplication.kt

override fun onCreate() {
  super.onCreate()
  RuleController.getInstance(this)
    .setRules(RuleController.parseRules(this, R.xml.main_split_config))
}

SampleApplication.java

@Override
public void onCreate() {
  super.onCreate();
  RuleController.getInstance(this)
    .setRules(RuleController.parseRules(this, R.xml.main_split_config));
}

3단계: 매니페스트에 서브클래스 이름 추가하기

서브클래스의 이름을 앱 매니페스트의 <application> 요소에 추가합니다.

AndroidManifest.xml

<application
    android:name=".SampleApplication"
    . . .

실행해 보세요.

샘플 앱 빌드 및 실행

폴더블이 아닌 휴대전화에서는 가로 모드 방향에서도 활동이 항상 스택됩니다.

세로 모드 방향의 휴대전화에서 목록(기본) 활동 위에 세부정보(보조) 활동이 스택된 모습. 가로 모드 방향의 휴대전화에서 목록(기본) 활동 위에 세부정보(보조) 활동이 스택된 모습.

Android 13(API 수준 33) 이하에서 활동 삽입은 분할 최소 너비 사양에 관계없이 폴더블이 아닌 휴대전화에는 사용 설정되지 않습니다.

API 수준이 그보다 더 높은, 폴더블이 아닌 휴대전화에 활동 삽입이 지원되는지 여부는 기기 제조업체에서 활동 삽입을 사용 설정했는지에 따라 다릅니다.

소형 태블릿 또는 7 WSVGA(태블릿) 에뮬레이터에서 두 활동은 세로 방향으로 스택되지만 가로 방향에서는 나란히 표시됩니다.

작은 태블릿에서 목록 활동과 세부정보 활동이 세로 모드 방향으로 스택된 모습. 작은 태블릿에서 목록 활동과 세부정보 활동이 가로 모드 방향으로 나란히 표시된 모습.

대형 태블릿 또는 Pixel C 에뮬레이터에서 활동은 세로 모드 방향(아래 '가로세로 비율' 참고)으로 스택되지만, 가로 모드 방향에서는 나란히 표시됩니다.

대형 태블릿에서 목록 활동과 세부정보 활동이 세로 모드 방향으로 스택된 모습. 대형 태블릿에서 목록 활동과 세부정보 활동이 가로 모드로 나란히 표시된 모습.

요약은 분할 내에서 시작되더라도 가로 모드의 전체 화면으로 표시됩니다.

대형 태블릿에서 요약 활동이 분할을 오버레이한 모습.

가로세로 비율

활동 분할은 분할 최소 너비 외에도 디스플레이 가로세로 비율에 따라 제어됩니다. splitMaxAspectRatioInPortraitsplitMaxAspectRatioInLandscape 속성이 활동 분할이 표시되는 최대 디스플레이 가로세로 비율(height:width)을 지정합니다. 이러한 속성은 SplitRulemaxAspectRatioInPortraitmaxAspectRatioInLandscape 속성을 나타냅니다.

디스플레이의 가로세로 비율이 어느 방향으로든 값을 초과하면 디스플레이 너비와 관계없이 분할이 사용되지 않습니다. 세로 모드 방향의 기본값은 1.4( SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT 참고)이며 이로 인해 길고 좁은 디스플레이에는 분할이 포함되지 않습니다. 기본적으로 분할은 항상 가로 모드 방향으로 허용됩니다(SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT 참고).

PIxel C 에뮬레이터의 세로 모드 표시 너비는 900dp이며 이는 샘플 앱 XML 구성 파일의 splitMinWidthDp 설정보다 더 넓습니다. 따라서 에뮬레이터가 활동 분할을 표시하게 됩니다. 하지만 세로 모드에서 Pixel C의 가로세로 비율은 1.4보다 크므로 활동 분할이 세로 모드 방향으로 표시되지 않습니다.

SplitPairRuleSplitPlaceholderRule 요소의 XML 구성 파일에서 세로 모드와 가로 모드 디스플레이의 최대 가로세로 비율을 설정할 수 있습니다. 예를 들면 다음과 같습니다.

main_split_config.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:window="http://schemas.android.com/apk/res/android">

  <!-- Define a split for the named activity pair. -->
  <SplitPairRule
      . . .
      window:splitMaxAspectRatioInPortrait="alwaysAllow"
      window:splitMaxAspectRatioInLandscape="alwaysDisallow"
      . . .
 </SplitPairRule>

  <SplitPlaceholderRule
      . . .
      window:splitMaxAspectRatioInPortrait="alwaysAllow"
      window:splitMaxAspectRatioInLandscape="alwaysDisallow"
      . . .
  </SplitPlaceholderRule>

</resources>

세로 디스플레이 너비가 840dp 이상인 대형 태블릿 또는 Pixel C 에뮬레이터에서는 활동이 세로 모드 방향으로 나란히 표시되지만 가로 모드 방향에서는 스택됩니다.

대형 태블릿에서 목록 활동과 세부정보 활동이 세로 모드 방향으로 나란히 표시된 모습. 대형 태블릿에서 목록 활동과 세부정보 활동이 가로 모드 방향으로 스택된 모습.

추가 기능

샘플 앱에서 세로 모드와 가로 모드 방향의 가로세로 비율을 위와 같이 설정해 봅니다. 대형 태블릿(세로 모드 너비가 840dp 이상인 경우) 또는 Pixel C 에뮬레이터를 사용하여 설정을 테스트합니다. 활동이 세로 모드 방향으로는 분할되지만 가로 모드로는 표시되지 않습니다.

대형 태블릿의 세로 모드 가로세로 비율을 확인합니다(Pixel C의 가로세로 비율은 1.4보다 약간 큼). splitMaxAspectRatioInPortrait를 가로세로 비율보다 높거나 낮은 값으로 설정합니다. 앱을 실행하고 결과를 확인합니다.

6. WindowManager API

코드에서 활동 임베딩을 완전히 사용 설정하려면 분할을 시작하는 활동의 onCreate() 메서드 내에서 단일 메서드를 호출하면 됩니다. XML이 아닌 코드로 작업하고 싶다면 이렇게 하면 됩니다.

WindowManager 종속 항목 추가

XML 기반 구현을 만드는지 아니면 API 호출을 사용하는지와 관계없이 앱은 WindowManager 라이브러리에 액세스해야 합니다. 앱에 WindowManager 종속 항목을 추가하는 방법에 관해서는 이 Codelab의 'XML 구성' 섹션을 참고하세요.

시스템에 통지

XML 구성 파일을 사용하든 WindowManager API 호출을 사용하든 상관없이 앱은 활동 삽입이 구현되었음을 시스템에 알려야 합니다. 시스템에 구현을 알리는 방법에 관해서는 이 Codelab의 'XML 구성' 섹션을 참고하세요.

분할을 관리하기 위한 클래스 만들기

이 Codelab 섹션에서는 샘플 앱의 기본 활동인 ListActivity에서 호출할 단일 정적 메서드 또는 컴패니언 객체 메서드 내에서 활동 분할을 완전히 구현합니다.

context 매개변수를 포함하는 메서드 createSplitSplitManager라는 클래스를 만듭니다(일부 API 호출에서 이 매개변수가 필요함).

SplitManager.kt

class SplitManager {

    companion object {

        fun createSplit(context: Context) {
        }
}

SplitManager.java

class SplitManager {

    static void createSplit(Context context) {
    }
}

Application 클래스의 서브클래스에 있는 onCreate() 메서드에서 이 메서드를 호출합니다.

Application의 서브클래스를 만드는 이유와 방법에 관한 자세한 내용은 이 Codelab의 'XML 구성' 섹션에서 '초기화 바로가기'를 참고하세요.

SampleApplication.kt

package com.example.activity_embedding

import android.app.Application

/**
 * Initializer for activity embedding split rules.
 */
class SampleApplication : Application() {

  override fun onCreate() {
    super.onCreate()
    SplitManager.createSplit(this)
  }
}

SampleApplication.java

package com.example.activity_embedding;

import android.app.Application;

/**
 * Initializer for activity embedding split rules.
 */
public class SampleApplication extends Application {

  @Override
  public void onCreate() {
    super.onCreate();
    SplitManager.createSplit(this);
  }
}

분할 규칙 만들기

필수 API:

SplitPairRule는 활동 쌍의 분할 규칙을 정의합니다.

SplitPairRule.BuilderSplitPairRule을 만듭니다. 이 빌더는 SplitPairFilter 객체 집합을 인수로 사용합니다. 필터로 규칙을 적용할 시기를 지정할 수 있습니다.

RuleController 구성요소의 싱글톤 인스턴스로 규칙을 등록하면 시스템에서 분할 규칙을 사용할 수 있게 됩니다.

분할 규칙을 만들려면 다음 단계를 따르세요.

  1. ListActivityDetailActivity를 분할을 공유하는 활동으로 식별하는 분할 쌍 필터를 만듭니다.

SplitManager.kt/createSplit()

val splitPairFilter = SplitPairFilter(
    ComponentName(context, ListActivity::class.java),
    ComponentName(context, DetailActivity::class.java),
    null
)

SplitManager.java/createSplit()

SplitPairFilter splitPairFilter = new SplitPairFilter(
    new ComponentName(context, ListActivity.class),
    new ComponentName(context, DetailActivity.class),
    null
);

필터는 보조 활동 실행을 위한 인텐트 작업(세 번째 매개변수)을 포함할 수 있습니다. 인텐트 작업을 포함하면 필터는 활동 이름과 함께 작업을 확인합니다. 자체 앱의 활동에서는 아마도 인텐트 작업을 필터링하지 않으므로 인수가 null일 수 있습니다.

  1. 필터 세트에 필터를 추가합니다.

SplitManager.kt/createSplit()

val filterSet = setOf(splitPairFilter)

SplitManager.java/createSplit()

Set<SplitPairFilter> filterSet = new HashSet<>();
filterSet.add(splitPairFilter);
  1. 분할의 레이아웃 속성을 만듭니다.

SplitManager.kt/createSplit()

val splitAttributes: SplitAttributes = SplitAttributes.Builder()
      .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
      .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
      .build()

SplitManager.java/createSplit()

SplitAttributes splitAttributes = new SplitAttributes.Builder()
  .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
  .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
  .build();

SplitAttributes.Builder는 레이아웃 속성을 포함하는 객체를 만듭니다.

  • setSplitType: 사용 가능한 표시 영역이 각 활동 컨테이너에 어떻게 할당되는지를 정의합니다. 비율 분할 유형에 따라 기본 컨테이너가 차지하는 디스플레이의 비율이 지정되고, 보조 컨테이너가 나머지 표시 영역을 차지합니다.
  • setLayoutDirection: 활동 컨테이너가 다른 컨테이너를 기준으로 어떻게 배치되는지를 지정합니다. 우선 기본 컨테이너를 기준으로 합니다.
  1. 분할 쌍 규칙 생성:

SplitManager.kt/createSplit()

val splitPairRule = SplitPairRule.Builder(filterSet)
      .setDefaultSplitAttributes(splitAttributes)
      .setMinWidthDp(840)
      .setMinSmallestWidthDp(600)
      .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
      .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
      .setClearTop(false)
      .build()

SplitManager.java/createSplit()

SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
  .setDefaultSplitAttributes(splitAttributes)
  .setMinWidthDp(840)
  .setMinSmallestWidthDp(600)
  .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
  .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
  .setClearTop(false)
  .build();

SplitPairRule.Builder는 규칙을 만들고 구성합니다.

  • filterSet: 분할을 공유하는 활동을 식별하여 규칙을 적용할 시기를 결정하는 분할 쌍 필터를 포함합니다. 샘플 앱에서 ListActivityDetailActivity는 분할 쌍 필터로 지정됩니다(이전 단계 참고).
  • setDefaultSplitAttributes: 규칙에 레이아웃 속성을 적용합니다.
  • setMinWidthDp: 분할을 허용하는 최소 디스플레이 너비(밀도 독립형 픽셀(dp))를 설정합니다.
  • setMinSmallestWidthDp: 분할을 허용하기 위해 기기 방향과 관계없이 두 디스플레이 크기 중 더 작은 값이 취해야 할 최솟값(dp)을 설정합니다.
  • setFinishPrimaryWithSecondary: 보조 컨테이너의 모든 활동이 종료될 때 기본 컨테이너의 활동에 어떠한 영향을 미치는지 설정합니다. NEVER는 보조 컨테이너의 모든 활동이 종료될 때 기본 활동이 종료되어서는 안 됨을 나타냅니다. 활동 종료를 참고하세요.
  • setFinishSecondaryWithPrimary: 기본 컨테이너의 모든 활동이 종료될 때 보조 컨테이너의 활동에 어떠한 영향을 미치는지를 설정합니다. ALWAYS는 기본 컨테이너의 모든 활동이 종료될 때 보조 컨테이너의 활동이 항상 종료되어야 함을 나타냅니다. 활동 종료를 참고하세요.
  • setClearTop: 컨테이너에서 새 활동이 실행될 때 보조 컨테이너의 모든 활동이 종료되는지를 지정합니다. False는 새 활동이 보조 컨테이너에 이미 있는 활동 위에 스택됨을 지정합니다.
  1. WindowManager RuleController의 싱글톤 인스턴스를 가져오고 규칙을 추가합니다.

SplitManager.kt/createSplit()

val ruleController = RuleController.getInstance(context)
ruleController.addRule(splitPairRule)

SplitManager.java/createSplit()

RuleController ruleController = RuleController.getInstance(context);
ruleController.addRule(splitPairRule);

자리표시자 규칙 만들기

필수 API:

SplitPlaceholderRule은 보조 컨테이너에 사용할 수 있는 내용이 없을 때 보조 컨테이너를 차지할 활동에 관한 규칙을 정의합니다. 자리표시자 활동을 만들려면 이 Codelab의 'XML 구성' 섹션에서 '자리표시자 활동 만들기'를 참고하세요. 자세한 내용은 활동 삽입 개발자 가이드의 자리표시자를 참고하세요.

SplitPlaceholderRule.BuilderSplitPlaceholderRule을 만듭니다. 이 빌더는 ActivityFilter 객체 집합을 인수로 사용합니다. 객체는 자리표시자 규칙이 연결되는 활동을 지정합니다. 필터가 시작된 활동과 일치하면 자리표시자 규칙이 적용됩니다.

RuleController 구성요소로 규칙을 등록할 수 있습니다.

분할 자리표시자 규칙을 만들려면 다음 단계를 따릅니다.

  1. ActivityFilter 만들기:

SplitManager.kt/createSplit()

val placeholderActivityFilter = ActivityFilter(
    ComponentName(context, ListActivity::class.java),
    null
)

SplitManager.java/createSplit()

ActivityFilter placeholderActivityFilter = new ActivityFilter(
    new ComponentName(context, ListActivity.class),
    null
);

이 필터는 규칙을 샘플 앱의 기본 활동(ListActivity)과 연결합니다. 따라서 목록 세부정보 레이아웃에서 사용할 수 있는 세부정보 내용이 없으면 자리표시자가 세부정보 영역을 채웁니다.

필터는 연결된 활동 실행(ListActivity 실행)을 위한 인텐트 작업(두 번째 매개변수)을 포함할 수 있습니다. 인텐트 작업을 포함하면 필터는 활동 이름과 함께 작업을 확인합니다. 자체 앱의 활동에서는 아마도 인텐트 작업을 필터링하지 않으므로 인수가 null일 수 있습니다.

  1. 필터 세트에 필터를 추가합니다.

SplitManager.kt/createSplit()

val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

SplitManager.java/createSplit()

Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
placeholderActivityFilterSet.add(placeholderActivityFilter);
  1. SplitPlaceholderRule 생성:

SplitManager.kt/createSplit()

val splitPlaceholderRule = SplitPlaceholderRule.Builder(
      placeholderActivityFilterSet,
      Intent(context, PlaceholderActivity::class.java)
    ).setDefaultSplitAttributes(splitAttributes)
     .setMinWidthDp(840)
     .setMinSmallestWidthDp(600)
     .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
     .build()

SplitManager.java/createSplit()

SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
  placeholderActivityFilterSet,
  new Intent(context, PlaceholderActivity.class)
).setDefaultSplitAttributes(splitAttributes)
 .setMinWidthDp(840)
 .setMinSmallestWidthDp(600)
 .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
 .build();

SplitPlaceholderRule.Builder는 규칙을 만들고 구성합니다.

  • placeholderActivityFilterSet: 자리표시자 활동과 연결된 활동을 식별하여 규칙을 적용할 시기를 결정하는 활동 필터를 포함합니다.
  • Intent: 자리표시자 활동 실행을 지정합니다.
  • setDefaultSplitAttributes: 규칙에 레이아웃 속성을 적용합니다.
  • setMinWidthDp: 분할을 허용하는 최소 디스플레이 너비(밀도 독립형 픽셀(dp))를 설정합니다.
  • setMinSmallestWidthDp: 분할을 허용하기 위해 기기 방향과 관계없이 두 디스플레이 크기 중 더 작은 값이 취해야 할 최솟값(dp)을 설정합니다.
  • setFinishPrimaryWithPlaceholder: 자리표시자 활동 종료가 기본 컨테이너의 활동에 어떠한 영향을 미치는지를 설정합니다. ALWAYS는 자리표시자가 종료되면 기본 컨테이너의 활동이 항상 종료되어야 함을 나타냅니다. 활동 종료를 참고하세요.
  1. WindowManager RuleController에 규칙 추가:

SplitManager.kt/createSplit()

ruleController.addRule(splitPlaceholderRule)

SplitManager.java/createSplit()

ruleController.addRule(splitPlaceholderRule);

활동 규칙 만들기

필수 API:

ActivityRule은 모달 대화상자와 같이 전체 작업 창을 차지하는 활동의 규칙을 정의하는 데 사용할 수 있습니다. 자세한 내용은 활동 삽입 개발자 가이드의 전체 크기 창 모달을 참고하세요.

SplitPlaceholderRule.BuilderSplitPlaceholderRule을 만듭니다. 이 빌더는 ActivityFilter 객체 집합을 인수로 사용합니다. 객체는 자리표시자 규칙이 연결되는 활동을 지정합니다. 필터가 시작된 활동과 일치하면 자리표시자 규칙이 적용됩니다.

RuleController 구성요소로 규칙을 등록할 수 있습니다.

활동 규칙을 만들려면 다음 단계를 따릅니다.

  1. ActivityFilter 만들기:

SplitManager.kt/createSplit()

val summaryActivityFilter = ActivityFilter(
    ComponentName(context, SummaryActivity::class.java),
    null
)

SplitManager.java/createSplit()

ActivityFilter summaryActivityFilter = new ActivityFilter(
    new ComponentName(context, SummaryActivity.class),
    null
);

필터는 규칙이 적용되는 활동(SummaryActivity)을 지정합니다.

필터는 연결된 활동 실행(SummaryActivity 실행)을 위한 인텐트 작업(두 번째 매개변수)을 포함할 수 있습니다. 인텐트 작업을 포함하면 필터는 활동 이름과 함께 작업을 확인합니다. 자체 앱의 활동에서는 아마도 인텐트 작업을 필터링하지 않으므로 인수가 null일 수 있습니다.

  1. 필터 세트에 필터를 추가합니다.

SplitManager.kt/createSplit()

val summaryActivityFilterSet = setOf(summaryActivityFilter)

SplitManager.java/createSplit()

Set<ActivityFilter> summaryActivityFilterSet = new HashSet<>();
summaryActivityFilterSet.add(summaryActivityFilter);
  1. ActivityRule 만들기:

SplitManager.kt/createSplit()

val activityRule = ActivityRule.Builder(summaryActivityFilterSet)
      .setAlwaysExpand(true)
      .build()

SplitManager.java/createSplit()

ActivityRule activityRule = new ActivityRule.Builder(
    summaryActivityFilterSet
).setAlwaysExpand(true)
 .build();

ActivityRule.Builder는 규칙을 만들고 구성합니다.

  • summaryActivityFilterSet: 분할을 제외할 활동을 식별하여 규칙을 적용할 시기를 결정하는 활동 필터를 포함합니다.
  • setAlwaysExpand- 사용 가능한 모든 디스플레이 공간을 채우도록 활동을 확장해야 하는지 지정합니다.
  1. WindowManager RuleController에 규칙 추가:

SplitManager.kt/createSplit()

ruleController.addRule(activityRule)

SplitManager.java/createSplit()

ruleController.addRule(activityRule);

실행해 보세요.

샘플 앱 빌드 및 실행

앱은 XML 구성 파일을 사용하여 맞춤설정할 때와 동일하게 동작해야 합니다.

이 Codelab의 'XML 구성' 섹션에서 '실행해 보세요.'를 참고하세요.

추가 기능

SplitPairRule.BuilderSplitPlaceholderRule.BuildersetMaxAspectRatioInPortrait 메서드와 setMaxAspectRatioInLandscape 메서드를 사용하여 샘플 앱에서 가로세로 비율을 설정해 봅니다. EmbeddingAspectRatio 클래스의 속성과 메서드를 사용하여 값을 지정합니다. 예를 들면 다음과 같습니다.

SplitPairRule.Builder(filterSet)
  . . .
  .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
  . . .
.build()

대형 태블릿 또는 Pixel C 에뮬레이터를 사용하여 설정을 테스트합니다.

대형 태블릿의 세로 모드 가로세로 비율을 확인합니다(Pixel C의 가로세로 비율은 1.4보다 약간 큼). 세로 모드의 최대 가로세로 비율을 태블릿 또는 Pixel C의 가로세로 비율보다 높거나 낮은 값으로 설정합니다. ALWAYS_ALLOWALWAYS_DISALLOW 속성을 사용해 봅니다.

앱을 실행하고 결과를 확인합니다.

자세한 내용은 이 Codelab의 'XML 구성' 섹션에서 '가로세로 비율'을 참고하세요.

7. Material Design 탐색

Material Design 가이드라인에서는 화면 크기가 다양한 여러 탐색 구성요소를 지정합니다(예: 840dp 이상 화면의 탐색 레일, 840dp 미만 화면의 하단 탐색 메뉴).

fb47462060f4818d.gif

활동 삽입을 사용하면 WindowManager 메서드 getCurrentWindowMetrics()getMaximumWindowMetrics()를 사용하여 화면 너비를 결정할 수 없습니다. 이 메서드에서 반환된 창 측정항목이 메서드를 호출한 삽입된 활동이 포함된 디스플레이 창을 설명하기 때문입니다.

활동 삽입 앱의 정확한 크기를 가져오려면 분할 속성 계산기SplitAttributesCalculatorParams를 사용하세요.

이전 섹션에 추가했다면 다음 줄을 삭제합니다.

main_split_config.xml

<SplitPairRule
    . . .
    window:splitMaxAspectRatioInPortrait="alwaysAllow" // Delete this line.
    window:splitMaxAspectRatioInLandscape="alwaysDisallow" // Delete this line.
    . . .>
</SplitPairRule>

<SplitPlaceholderRule
    . . .

    window:splitMaxAspectRatioInPortrait="alwaysAllow" // Delete this line.
    window:splitMaxAspectRatioInLandscape="alwaysDisallow" // Delete this line.
    . . .>
<SplitPlaceholderRule/>

유연한 탐색

화면 크기에 따라 탐색 구성요소를 동적으로 전환하려면 SplitAttributes 계산기를 사용합니다. 계산기를 통해 기기 방향과 창 크기의 변경사항을 감지하고 적절하게 디스플레이 크기를 다시 계산할 수 있습니다. 계산기를 SplitController와 통합하여 화면 크기 업데이트에 대한 응답으로 탐색 구성요소 변경사항을 트리거합니다.

탐색 레이아웃 만들기

먼저 탐색 레일과 탐색 메뉴를 채우는 데 사용할 메뉴를 만듭니다.

res/menu 폴더에서 새 메뉴 리소스 파일 nav_menu.xml을 만듭니다. 메뉴 파일의 내용을 다음으로 바꿉니다.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/navigation_home"
        android:title="Home" />
    <item
        android:id="@+id/navigation_dashboard"
        android:title="Dashboard" />
    <item
        android:id="@+id/navigation_settings"
        android:title="Settings" />
</menu>

그런 다음 탐색 메뉴와 탐색 레일을 레이아웃에 추가합니다. 공개 상태를 gone으로 설정하여 처음에는 숨깁니다. 나중에 레이아웃 크기에 따라 표시되도록 합니다.

activity_list.xml

<com.google.android.material.navigationrail.NavigationRailView
     android:id="@+id/navigationRailView"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     app:layout_constraintStart_toStartOf="parent"
     app:layout_constraintTop_toTopOf="parent"
     app:menu="@menu/nav_menu"
     android:visibility="gone" />

<com.google.android.material.bottomnavigation.BottomNavigationView
   android:id="@+id/bottomNavigationView"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   app:menu="@menu/nav_menu"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintEnd_toEndOf="parent"
   android:visibility="gone" />

탐색 메뉴와 탐색 레일 간 전환을 처리하는 함수를 작성합니다.

ListActivity.kt / setWiderScreenNavigation()

private fun setWiderScreenNavigation(useNavRail: Boolean) {
   val navRail: NavigationRailView  = findViewById(R.id.navigationRailView)
   val bottomNav: BottomNavigationView = findViewById(R.id.bottomNavigationView)

   if (useNavRail) {
       navRail.visibility = View.VISIBLE
       bottomNav.visibility = View.GONE
   } else {
       navRail.visibility = View.GONE
       bottomNav.visibility = View.VISIBLE
   }
}

ListActivity.java / setWiderScreenNavigation()

private void setWiderScreenNavigation(boolean useNavRail) {
   NavigationRailView navRail = findViewById(R.id.navigationRailView);
   BottomNavigationView bottomNav = findViewById(R.id.bottomNavigationView);
   if (useNavRail) {
       navRail.setVisibility(View.VISIBLE);
       bottomNav.setVisibility(View.GONE);
   } else {
       navRail.setVisibility(View.GONE);
       bottomNav.setVisibility(View.VISIBLE);
   }
}

분할 속성 계산기

SplitController는 현재 활성화된 활동 분할에 관한 정보를 가져오고, 상호작용 지점을 제공하여 분할을 맞춤설정하고 새 분할을 형성합니다.

이전 섹션에서는 XML 파일의 <SplitPairRule><SplitPlaceHolderRule> 태그에 splitRatio 및 기타 속성을 지정하거나 SplitPairRule.Builder#setDefaultSplitAttributes()SplitPlaceholderRule.Builder#setDefaultSplitAttributes() API를 사용하여 기본 분할 속성을 설정했습니다.

기본 분할 속성은 상위 컨테이너의 WindowMetricsminWidthDp, minHeightDp, minSmallestWidthDp라는 SplitRule 크기 요구사항을 충족하면 적용됩니다.

기본 분할 속성을 대체하도록 분할 속성 계산기를 설정합니다. 계산기는 방향 변경이나 접기 상태 변경 등 창이나 기기 상태가 변경되면 기존 분할 쌍을 업데이트합니다.

이렇게 하면 개발자가 기기나 창 상태를 알고 세로 모드 및 가로 모드 방향과 탁자 모드 등 여러 시나리오로 다양한 분할 속성을 설정할 수 있습니다.

분할 속성 계산기를 만들 때 플랫폼은 SplitAttributesCalculatorParams 객체를 setSplitAttributesCalculator() 함수에 전달합니다. parentWindowMetrics 속성은 애플리케이션 창 측정항목을 제공합니다.

다음 코드에서 활동은 기본 제약 조건이 충족되는지, 즉 너비가 840dp보다 크고 최소 너비가 600dp보다 큰지 확인합니다. 조건이 충족되면 활동은 이중 창 레이아웃에 삽입되고 앱은 하단 탐색 메뉴가 아닌 탐색 레일을 사용합니다. 충족되지 않으면 활동은 하단 탐색 메뉴와 함께 전체 화면으로 표시됩니다.

ListActivity.kt/onCreate()

SplitController.getInstance(this).setSplitAttributesCalculator {
       params ->

   if (params.areDefaultConstraintsSatisfied) {
       // When default constraints are satisfied, use the navigation rail.
       setWiderScreenNavigation(true)
       return@setSplitAttributesCalculator params.defaultSplitAttributes
   } else {
       // Use the bottom navigation bar in other cases.
       setWiderScreenNavigation(false)
       // Expand containers if the device is in portrait or the width is less than 840 dp.
       SplitAttributes.Builder()
           .setSplitType(SPLIT_TYPE_EXPAND)
           .build()
   }
}

ListActivity.java/onCreate()

SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
   if (params.areDefaultConstraintsSatisfied()) {
       // When default constraints are satisfied, use the navigation rail.
       setWiderScreenNavigation(true);
       return params.getDefaultSplitAttributes();
   } else {
       // Use the bottom navigation bar in other cases.
       setWiderScreenNavigation(false);
       // Expand containers if the device is in portrait or the width is less than 600 dp.
       return new SplitAttributes.Builder()
               .setSplitType(SplitType.SPLIT_TYPE_EXPAND)
               .build();
   }
});

수고하셨습니다. 이제 활동 삽입 앱이 Material Design 탐색 가이드라인을 준수합니다.

8. 축하합니다

잘하셨습니다. 대형 화면의 목록-세부정보 레이아웃에 맞게 활동 기반 앱을 최적화하고 Material Design 탐색을 추가했습니다.

활동 삽입을 구현하는 두 가지 방법을 알아봤습니다.

  • XML 구성 파일 사용
  • Jetpack API 호출
  • 활동 삽입으로 유연한 탐색 구현

그리고 앱의 Kotlin 또는 Java 소스 코드를 다시 작성하지 않았습니다.

활동 삽입을 사용하여 대형 화면에 맞게 프로덕션 앱을 최적화할 준비가 되었습니다.

9. 자세히 알아보기