뷰에서 Compose 사용

뷰 기반 디자인을 사용하는 기존 앱에 Compose 기반 UI를 추가할 수 있습니다.

전적으로 Compose에 기반하는 새로운 화면을 생성하려면 활동에서 setContent() 메서드를 호출하고 원하는 구성 가능한 함수를 전달하도록 합니다.

class ExampleActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent { // In here, we can call composables!
            MaterialTheme {
                Greeting(name = "compose")
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

이 코드는 Compose 전용 앱에서 찾을 수 있는 코드와 비슷합니다.

ComposeView ViewCompositionStrategy

ViewCompositionStrategy는 컴포지션을 언제 폐기해야 하는지 정의합니다. 기본값인 ViewCompositionStrategy.Default는 기본 ComposeView가 창에서 분리될 때 컴포지션을 삭제합니다. 단, RecyclerView와 같은 풀링 컨테이너의 일부가 아닙니다. 단일 활동 Compose 전용 앱에서는 이 기본 동작이 바람직한 것이지만, 코드베이스에 Compose를 점진적으로 추가하는 경우 일부 시나리오에서 이 동작으로 인해 상태 손실이 발생할 수 있습니다.

ViewCompositionStrategy를 변경하려면 setViewCompositionStrategy() 메서드를 호출하고 다른 전략을 제공합니다.

아래 표에는 ViewCompositionStrategy을 사용할 수 있는 다양한 시나리오가 요약되어 있습니다.

ViewCompositionStrategy 설명 및 상호 운용성 시나리오
DisposeOnDetachedFromWindow 기본 ComposeView가 창에서 분리되면 컴포지션이 삭제됩니다. 이후 DisposeOnDetachedFromWindowOrReleasedFromPool로 대체되었습니다.

상호 운용성 시나리오:

* ComposeView가 뷰 계층 구조의 유일한 요소인지 아니면 혼합된 뷰/Compose 화면의 컨텍스트 (프래그먼트가 아님)에 있는지 여부입니다.
DisposeOnDetachedFromWindowOrReleasedFromPool (기본값) DisposeOnDetachedFromWindow와 마찬가지로 컴포지션이 풀링 컨테이너(예: RecyclerView)에 있지 않은 경우 풀링 컨테이너에 있는 경우 풀링 컨테이너 자체가 창에서 분리되거나 항목이 삭제될 때 (즉, 풀이 가득 찼을 때) 삭제됩니다.

상호 운용성 시나리오:

* ComposeView 뷰 계층 구조의 유일한 요소인지 또는 혼합된 뷰/Compose 화면의 컨텍스트에 있는지 (프래그먼트가 아님) 여부입니다.
* ComposeView: RecyclerView와 같은 풀링 컨테이너의 항목
DisposeOnLifecycleDestroyed 제공된 Lifecycle가 소멸되면 컴포지션이 삭제됩니다.

상호 운용성 시나리오

* Fragment 뷰의 ComposeView
DisposeOnViewTreeLifecycleDestroyed 뷰가 연결된 다음 창의 ViewTreeLifecycleOwner.get에서 반환된 LifecycleOwner가 소유한 Lifecycle가 소멸되면 컴포지션이 삭제됩니다.

상호 운용성 시나리오:

* 프래그먼트의 뷰에 있는 ComposeView.
* 수명 주기가 아직 알려지지 않은 뷰의 ComposeView

프래그먼트의 ComposeView

Compose UI 콘텐츠를 프래그먼트 또는 기존 뷰 레이아웃에 통합하려면 ComposeView를 사용하고 setContent() 메서드를 호출합니다. ComposeView는 Android View입니다.

다음과 같이 다른 View와 마찬가지로 ComposeView를 XML 레이아웃에 배치할 수 있습니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <TextView
      android:id="@+id/text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />

  <androidx.compose.ui.platform.ComposeView
      android:id="@+id/compose_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
</LinearLayout>

Kotlin 소스 코드에서 XML로 정의된 레이아웃 리소스의 레이아웃을 확장합니다. 그런 다음 XML ID를 사용하여 ComposeView를 가져오고, 호스트 View에 가장 적합한 컴포지션 전략을 설정한 다음, setContent()를 호출하여 Compose를 사용합니다.

class ExampleFragmentXml : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val view = inflater.inflate(R.layout.fragment_example, container, false)
        val composeView = view.findViewById<ComposeView>(R.id.compose_view)
        composeView.apply {
            // Dispose of the Composition when the view's LifecycleOwner
            // is destroyed
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                // In Compose world
                MaterialTheme {
                    Text("Hello Compose!")
                }
            }
        }
        return view
    }
}

또는 뷰 결합을 사용하여 XML 레이아웃 파일의 생성된 결합 클래스를 참조하여 ComposeView 참조를 가져올 수도 있습니다.

class ExampleFragment : Fragment() {

    private var _binding: FragmentExampleBinding? = null

    // This property is only valid between onCreateView and onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentExampleBinding.inflate(inflater, container, false)
        val view = binding.root
        binding.composeView.apply {
            // Dispose of the Composition when the view's LifecycleOwner
            // is destroyed
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                // In Compose world
                MaterialTheme {
                    Text("Hello Compose!")
                }
            }
        }
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

위, 아래로 배치되어 있으며 서로 약간 다른 두 텍스트 요소

그림 1. 뷰 UI 계층 구조에서 Compose 요소를 추가하는 코드의 출력을 보여줍니다. 'Hello Android!' 텍스트는 TextView 위젯에 의해 표시됩니다. 'Hello Compose!' 텍스트는 Compose 텍스트 요소에 의해 표시됩니다.

또한 전체 화면이 Compose를 사용하여 빌드된 경우 프래그먼트에 ComposeView를 직접 포함할 수도 있습니다. 이렇게 하면 전적으로 XML 레이아웃 파일을 사용하지 않아도 됩니다.

class ExampleFragmentNoXml : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            // Dispose of the Composition when the view's LifecycleOwner
            // is destroyed
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                MaterialTheme {
                    // In Compose world
                    Text("Hello Compose!")
                }
            }
        }
    }
}

동일한 레이아웃의 여러 ComposeView 인스턴스

동일한 레이아웃에 ComposeView 요소가 여러 개 있다면 savedInstanceState가 작동하기 위해서는 각 요소에 고유 ID가 있어야 합니다.

class ExampleFragmentMultipleComposeView : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View = LinearLayout(requireContext()).apply {
        addView(
            ComposeView(requireContext()).apply {
                setViewCompositionStrategy(
                    ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
                )
                id = R.id.compose_view_x
                // ...
            }
        )
        addView(TextView(requireContext()))
        addView(
            ComposeView(requireContext()).apply {
                setViewCompositionStrategy(
                    ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
                )
                id = R.id.compose_view_y
                // ...
            }
        )
    }
}

다음과 같이 ComposeView ID는 res/values/ids.xml 파일에 정의되어 있습니다.

<resources>
  <item name="compose_view_x" type="id" />
  <item name="compose_view_y" type="id" />
</resources>

Layout Editor에서 컴포저블 미리보기

ComposeView가 포함된 XML 레이아웃의 Layout Editor 내에서 컴포저블을 미리 볼 수도 있습니다. 이렇게 하면 뷰와 Compose 레이아웃이 혼합된 컴포저블이 어떻게 표시되는지 확인할 수 있습니다.

레이아웃 편집기에 다음 구성 가능한 함수를 표시하려고 한다고 가정해 보겠습니다. @Preview로 주석 처리된 컴포저블은 Layout Editor에서 미리보기에 적합합니다.

@Preview
@Composable
fun GreetingPreview() {
    Greeting(name = "Android")
}

이 컴포저블을 표시하려면 tools:composableName 도구 속성을 사용하고 그 값을 레이아웃에서 미리 볼 컴포저블의 정규화된 이름으로 설정합니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <androidx.compose.ui.platform.ComposeView
      android:id="@+id/my_compose_view"
      tools:composableName="com.example.compose.snippets.interop.InteroperabilityAPIsSnippetsKt.GreetingPreview"
      android:layout_height="match_parent"
      android:layout_width="match_parent"/>

</LinearLayout>

Layout Editor 내에 표시된 컴포저블

다음 단계

뷰에서 Compose를 사용하는 상호 운용성 API에 관해 알아보았으니 이제 Compose의 뷰의 사용법을 알아보세요.