Compose with android view
y— title: Compose with Android View date: 2023-07-11T20:30:00+09:00 author: Seungmin Yang layout: post tag: [Android, Compose] —
Compose with Android View
Compose는 앱에서 compose로 migration을 진행 중 기존 Android View System과 상호 운용할 수 있도록 API를 제공한다.
ComposeView
Activity에서 Compose를 기반으로 하는 view를 만드려면 ComponentActivity
의 setContent
메소드를 호출하여 Composable을 전달한다.
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!")
}
ComponentActivity
더 높은 수준의 기능(Component)을 구현한 액티비티 클래스(여기서 더 높은 수준의 기능은 Activity Result API, Jetpack Compose등 AndroidX에서 제공하는 여러 기능들)기존 Android View 시스템에서는 AppCompatActivity를 상속하여 사용했는데, AppCompatActivity는 아래의 상속 구조를 따른다.
Activity
->ComponentActivity
->FragmentActivity
->AppCompatActivity
FragmentActivity에서 Activity에 Fragment를 추가하기 위한 API를 포함하고 있으며 AppCompatActivity는 구버전 안드로이드와의 호환성을 보장해주는 Activity이다.
Compose를 기반으로 하는 액티비티는
ComponentActivity
를 상속하여 구현하게 되는데, 이는 Compose는 Fragment라는 개념을 사용하지 않으며 특정 안드로이드 버전에 의존하는 기능(대표적으로 ActionBar와 Toolbar)와 같은 기능을 사용하지 않음을 방증할 수 있다.
setContent
는 확장 함수로 구현되어 있으며 ComposeView를 통해 뷰를 생성 후 setContentView
를 통해 화면에 보여줄 수 있도록 하는 기능을 포함하고 있다.
Compose in fragment
프래그먼트에서 Compose를 사용하려면 동일하게 ComposeView를 사용하고 setContent
를 호출한다.
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
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
}
ComposeView를 return하여 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가 있으면 savedInstanceState
가 작동하기 위해서는 각 요소에 고유한 ID가 있어야 한다.
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
// ...
}
)
ViewCompositionStrategy
기본적으로 Compose는 뷰가 window에서 분리되면 Composition을 삭제한다(DisposeOnDetachedFromWindowOrReleasedFromPool
). 이런 구조가 바람직하지 않을 수 있으며 이 경우에는 Custom strategy를 만들 수 있다.
View in Compose
@Composable
fun CustomView() {
var selectedItem by remember { mutableStateOf(0) }
// Adds view to Compose
AndroidView(
modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
factory = { context ->
// Creates view
MyView(context).apply {
// Sets up listeners for View -> Compose communication
setOnClickListener {
selectedItem = 1
}
} },
update = { view ->
// View's been inflated or state read in this block has been updated
// Add logic here if necessary
// As selectedItem is read here, AndroidView will recompose // whenever the state changes // Example of Compose -> View communication view.selectedItem = selectedItem
}
)
}
@Composable
fun ContentExample() {
Column(Modifier.fillMaxSize()) {
Text("Look at this CustomView!")
CustomView()
}
}
Compose에서 Android View를 사용하려면 AndroidView
Composable을 사용한다. 이 방법은 NaverMap
과 같은 아직 사용할 수 없는 경우 유용하다.
View
를 return하는 람다를 전달하며 뷰가 확장될 때 호출되는 update
콜백을 제공한다. AndroidView는 update
콜백 내의 state가 변경될 때마다 recomposition이 일어난다.
androidx.compose.ui:ui-viewbinding
라이브러리의 AndroidViewBinding
을 사용하여 뷰 바인딩을 사용할 수 있다. 또한 이를 이용하여 Fragment를 추가할 수 있다.
Android Framework in Compose
Compose는 Activity, Fragment와 같은 Android View 클래스에서 호출되며, Context, 리소스, Service, BR같은 안드로이드의 프레임워크 클래스를 호출할 수도 있다.
CompositionLocal
은 Android Framework의 인스턴스를 전달하는 데에도 사용할 수 있다. LocalContext
, LocalConfiguration
, LocalView
와 같은 방법을 제공한다.
@Composable
fun ToastGreetingButton(greeting: String) {
val context = LocalContext.current
Button(onClick = {
Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
}) {
Text("Greet")
}
}