Source Code
컴포즈 앱에서 뷰 나타내기
컴포즈 앱에 커스텀 컴포넌트 추가
QRCode 스캔하는 ZXing 라이브러리를 AndroidView를 이용해 ComposeApp에서 사용해보는 예제
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| class ZxingDemoActivity : ComponentActivity() {
private lateinit var barcodeView: DecoratedBarcodeView
private val text = MutableLiveData("")
private val requestPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { barcodeView.resume() } }
@SuppressLint("InflateParams") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val root = layoutInflater.inflate(R.layout.layout, null)
barcodeView = root.findViewById(R.id.barcode_scanner) val formats = listOf(BarcodeFormat.QR_CODE, BarcodeFormat.CODE_39) barcodeView.barcodeView.decoderFactory = DefaultDecoderFactory(formats) barcodeView.initializeFromIntent(intent) val callback = object : BarcodeCallback { override fun barcodeResult(result: BarcodeResult) { if (result.text == null || result.text == text.value) { return } text.value = result.text } }
barcodeView.decodeContinuous(callback)
setContent { val state = text.observeAsState() state.value?.let { ZxingDemo(root, it) } } } }
@Composable fun ZxingDemo(root: View, value: String) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter ) { AndroidView(modifier = Modifier.fillMaxSize(), factory = { root }) if (value.isNotBlank()) { Text( modifier = Modifier.padding(16.dp), text = value, color = Color.White, style = MaterialTheme.typography.h4 ) } } }
|
- factory 블록은 구성될 뷰를 얻기 위해 정확히 한 번만 호출된다.
항상 UI 스레드에서 호출될 것이기 때문에 필요에 따라 View property 설정이 가능하다.
AndroidViewBinding()으로 View Layer 구조 Inflating
- ComposeActivity에서 ViewActivity
- Compose여도 xml 파일과 ViewBinding
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| class ComposeActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
val viewModel: MyViewModel by viewModels() viewModel.setSliderValue(intent.getFloatExtra(KEY, 0F)) setContent { ViewIntegrationDemo(viewModel) { val i = Intent( this, ViewActivity::class.java ) i.putExtra(KEY, viewModel.sliderValue.value) startActivity(i) } } } }
@ExperimentalMaterial3Api @Composable fun ViewIntegrationDemo(viewModel: MyViewModel, onClick: () -> Unit) { val sliderValueState = viewModel.sliderValue.observeAsState() Scaffold(modifier = Modifier.fillMaxSize(), topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.compose_activity)) }) }) { padding -> Column( modifier = Modifier .fillMaxSize() .padding(padding) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Slider( modifier = Modifier.fillMaxWidth(), onValueChange = { viewModel.setSliderValue(it) }, value = sliderValueState.value ?: 0F ) AndroidViewBinding( modifier = Modifier.fillMaxWidth(), factory = CustomBinding::inflate ) { textView.text = sliderValueState.value.toString() button.setOnClickListener { onClick() } } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?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="match_parent">
<com.google.android.material.textview.MaterialTextView android:id="@+id/textView" android:layout_width="0dp" android:layout_height="64dp" android:background="?colorSecondary" android:gravity="center" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="@string/view_activity" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
|
- 반대로 ViewActivity에서는 기존 코드처럼 ViewBinding을 사용하고 ComposeView를 사용해 Composable 함수를 불러온다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| const val KEY = "key"
class ViewActivity : AppCompatActivity() {
private lateinit var binding: LayoutBinding
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = LayoutBinding.inflate(layoutInflater) setContentView(binding.root)
val viewModel: MyViewModel by viewModels() viewModel.setSliderValue(intent.getFloatExtra(KEY, 0F))
viewModel.sliderValue.observe(this) { binding.slider.value = it } binding.slider.addOnChangeListener { _, value, _ -> viewModel.setSliderValue(value) }
binding.composeView.run { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow) setContent { val sliderValue = viewModel.sliderValue.observeAsState() sliderValue.value?.let { ComposeDemo(it) { val i = Intent( context, ComposeActivity::class.java ) i.putExtra(KEY, it) startActivity(i) } } } } } }
@Composable fun ComposeDemo(value: Float, onClick: () -> Unit) { Column( modifier = Modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Box( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.secondary) .height(64.dp), contentAlignment = Alignment.Center ) { Text( text = value.toString() ) } Button( onClick = onClick, modifier = Modifier.padding(top = 16.dp) ) { Text(text = stringResource(id = R.string.compose_activity)) } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?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" android:padding="16dp" tools:context=".ViewActivity">
<com.google.android.material.slider.Slider android:id="@+id/slider" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
<androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/slider" />
</androidx.constraintlayout.widget.ConstraintLayout>
|
View와 Composable Function 간 데이터 공유
- 위 예시 코드에서 보았던 것처럼 ViewModel을 이용해 데이터를 담았다가 state형태로 가져와 사용하면서 View의 데이터를 ViewModel에, ViewModel
데이터를 Composable 함수에 담는 형식으로 구현한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| binding.slider.addOnChangeListener { _, value, _ -> viewModel.setSliderValue(value) }
binding.composeView.run { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow) setContent { val sliderValue = viewModel.sliderValue.observeAsState() sliderValue.value?.let { ComposeDemo(it) { val i = Intent( context, ComposeActivity::class.java ) i.putExtra(KEY, it) startActivity(i) } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| val sliderValueState = viewModel.sliderValue.observeAsState()
AndroidViewBinding( modifier = Modifier.fillMaxWidth(), factory = CustomBinding::inflate ) { textView.text = sliderValueState.value.toString() button.setOnClickListener { onClick() } }
|
뷰 계층 구조에 컴포저블 임베디드
이미 위에서 ComposeView에 대해 보고 왔다.
androidx.compose.ui.platform.ComposeView
를 통해 가능하다.
setViewCompositionStrategy(): Composition(구성) 호출 전략 설정 ViewCompositionStrategy
교재에서 정리해준 레이아웃에 Compose Layer 구조를 포함시키기 위해 알아야 하는 단계는 아래와 같다.
- layout xml에
androidx.compose.ui.platform.ComposeView
추가
- layout이 출력되는 위치에 ViewCompositionStrategy를 결정한다.
- setContent { }를 사용해 컨텐츠를 설정한다.
- viewModels()를 호출해 ViewModel 참조를 얻는다.
- 관련 View에 Listener를 등록하고 변경할 때 ViewModel을 갱신한다.
- Composable 함수에서는 필요에 따라 ViewModel 프로퍼티에서 observeAsState()를 호출해 상태를 생성한다.
- Composable 내부에서는 상황에 맞는
setter
를 호출해 ViewModel을 갱신한다.