QRCode 스캔하는 ZXing 라이브러리를 AndroidView를 이용해 ComposeApp에서 사용해보는 예제
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)
// 1: layout.xml inflate
val root = layoutInflater.inflate(R.layout.layout, null)
// 2: barcodeView 초기화
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
}
}
// 3: 지속적인 스캔 실시
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
)
}
}
}
class ComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel: MyViewModel by viewModels() // ViewModel 초기화
viewModel.setSliderValue(intent.getFloatExtra(KEY, 0F)) // Slider 값 초기화
setContent {
ViewIntegrationDemo(viewModel) {
// onClick
// ComposeActivity -> ViewActivity
val i = Intent(
this,
ViewActivity::class.java
)
i.putExtra(KEY, viewModel.sliderValue.value) // 현재 sliderValue
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 사용
AndroidViewBinding(
modifier = Modifier.fillMaxWidth(),
factory = CustomBinding::inflate
) {
// custom.xml 안에 있는 textView, button
textView.text = sliderValueState.value.toString()
button.setOnClickListener {
onClick()
}
}
}
}
}
<?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>
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 초기화
viewModel.setSliderValue(intent.getFloatExtra(KEY, 0F)) // Slider 값 초기화
viewModel.sliderValue.observe(this) {// ViewModel 내 sliderValue 값 observing
binding.slider.value = it
}
// slider 값 ViewModel 내 sliderValue에 반영
binding.slider.addOnChangeListener { _, value, _ -> viewModel.setSliderValue(value) }
// layout에서 ComposeView 사용
binding.composeView.run {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
setContent {
val sliderValue = viewModel.sliderValue.observeAsState()
sliderValue.value?.let {
// ViewActivity -> ComposeActivity
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, // 버튼 클릭 시 ComposeActivity 띄우기
modifier = Modifier.padding(top = 16.dp)
) {
Text(text = stringResource(id = R.string.compose_activity))
}
}
}
<?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>
// 1. View -> ViewModel
binding.slider.addOnChangeListener { _, value, _ -> viewModel.setSliderValue(value) }
// layout에서 ComposeView 사용
binding.composeView.run {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
setContent {
// 2. ViewModel -> Composable
val sliderValue = viewModel.sliderValue.observeAsState()
sliderValue.value?.let {
ComposeDemo(it) {
val i = Intent(
context,
ComposeActivity::class.java
)
i.putExtra(KEY, it)
startActivity(i)
}
}
}
}
val sliderValueState = viewModel.sliderValue.observeAsState()
// ...
// AndroidViewBinding 사용
AndroidViewBinding(
modifier = Modifier.fillMaxWidth(),
factory = CustomBinding::inflate
) {
// Compose -> ViewModel 내부의 sliderValueState -> View
textView.text = sliderValueState.value.toString()
button.setOnClickListener {
onClick()
}
}
이미 위에서 ComposeView에 대해 보고 왔다.
androidx.compose.ui.platform.ComposeView
를 통해 가능하다.
setViewCompositionStrategy(): Composition(구성) 호출 전략 설정 ViewCompositionStrategy
교재에서 정리해준 레이아웃에 Compose Layer 구조를 포함시키기 위해 알아야 하는 단계는 아래와 같다.
androidx.compose.ui.platform.ComposeView
추가setter
를 호출해 ViewModel을 갱신한다.
좋은 글 감사합니다~