이동식 저장소

Compose에서 view 사용하기 본문

Primary/Compose

Compose에서 view 사용하기

해스끼 2024. 5. 28. 20:56

Compose는 기존 view 시스템과의 상호 운용을 위해 AndroidView composable을 제공한다. Compose 네이티브 컴포넌트가 없는 WebView 등을 사용할 때 활용할 수 있다. 

 

내부적으로는 composition 트리 안에 view 노드를 직접 만드는 방식으로 구현되어 있다.

오버로딩 1

@Composable
@UiComposable
fun <T : View> AndroidView(
    factory: (Context) -> T,
    modifier: Modifier = Modifier,
    update: (T) -> Unit = NoOpUpdate
)

factory는 view를 만드는 람다이다. factory 블럭은 composition 단계에서 view를 만들기 위해 단 한 번만 실행되며, 항상 UI 스레드에서 실행된다. 따라서 factory 블럭 안에서 view를 초기화하거나 property를 지정하는 작업을 수행해도 된다.

 

공식 문서에서는 remember로 view를 감싸지 말고, factory 블럭 안에서 모든 걸 해결하라고 권장하고 있다.

Note: Prefer to construct a View in the AndroidView factory lambda instead of using remember to hold a View reference outside of AndroidView.

 

update 블럭은 recomposition이 발생할 때 UI 스레드에서 실행되며, view의 속성을 업데이트할 때 사용할 수 있다.  View가 factory에 의해 초기화된 직후에 한번 실행되며, 이후에도 recomposition 때마다 실행될 수 있다.

 

AndroidView는 레이아웃 경계를 넘어가는 UI를 clip하지 않는다. 경계 바깥으로 넘어가는 UI를 자르고 싶다면, View.setClipToOutline 함수를 호출해야 한다.

WebView(context).apply {
    clipToOutline = true
}

AndroidView는 오버로딩이 2개 있다. 이 오버로딩은 view가 재활용되지 않을 때 사용해야 한다. LazyRowLazyColumn 등 컴포넌트를 재사용할 수 있는 container 안에서 사용되더라도, 내부 view는 항상 다시 만들어진다. 즉 AndroidView composable 자체는 재활용되지만, 내부의 view는 재활용되지 않는 것.

 

AndroidView가 재사용될 수 있는 경우에는 아래에서 소개할 다른 오버로딩을 활용해야 한다. View를 만드는 작업은 매우 비싸기 때문에, 아래의 오버로딩을 사용하는 것을 권장한다.

오버로딩 2

@Composable
@UiComposable
fun <T : View> AndroidView(
    factory: (Context) -> T,
    modifier: Modifier = Modifier,
    onReset: ((T) -> Unit)? = null,
    onRelease: (T) -> Unit = NoOpUpdate,
    update: (T) -> Unit = NoOpUpdate
): Unit

onResetonRelease 람다가 눈에 띈다.

 

onReset 람다가 null이 아니라면, composable을 재사용할 수 있는 container 안에서 view가 재사용될 수 있다. 위에서 말했던 LazyRow에서 AndroidView를 재사용하는 경우에서, AndroidView와 내부 view가 모두 재사용될 수 있는 것이다.

 

onReset에서는 view를 초기화하는 로직을 실행하면 된다. 다른 블럭과 마찬가지로 UI 스레드에서 실행된다.

 

물론 상황에 따라 reset된 view가 즉시 재사용되지 않을 수도 있다. 대기 중이던 view가 재사용된다면 update 블럭이 실행되며, 재사용되지 않고 삭제된다면 대기 상태에서 onReset이 호출된 후 view가 삭제된다.

 

View가 composition hierarchy에 붙어 있는지 확인하고 싶다면, View.addOnAttachStateChangeListener를 등록할 수 있다. View의 생명주기가 궁금하다면 findViewTreeLifecycleOwner 함수를 실행하면 된다. 이 함수는  Compose의 LocalLifecycleOwner를 반환한다.

 

View가 composition에서 완전히 제거되는 경우에는 onRelease가 UI 스레드에서 실행된다. onRelease가 return한 후에는 view가 절대로 재사용되지 않는다. 나중에 view가 다시 필요한 경우에도 새 view를 생성하지, release된 view를 재사용하지는 않는다.

View Binding?

View binding이 필요하다면 AndroidViewBinding composable을 사용할 수 있다.

@Composable
fun AndroidViewBindingExample() {
    AndroidViewBinding(ExampleLayoutBinding::inflate) {
        exampleView.setBackgroundColor(Color.GRAY)
    }
}

Example: WebView

Compose에 없는 WebView를 사용해 보자. 먼저 다음과 같이 WebView를 생성하는 createWebView 함수를 정의한다.

private fun createWebView(context: Context, onSetProgress: (Int) -> Unit) = WebView(context).apply {
    layoutParams = ViewGroup.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT
    )
    webChromeClient = object : WebChromeClient() {
        override fun onProgressChanged(view: WebView, newProgress: Int) {
            onSetProgress(newProgress)
            super.onProgressChanged(view, newProgress)
        }
    }
    settings.apply {
        builtInZoomControls = false
        domStorageEnabled = true
        javaScriptEnabled = true
        loadWithOverviewMode = true
        blockNetworkLoads = false
        setSupportZoom(false)
    }
}

 

이제 AndroidView에서 createWebView를 호출하자.

var progress by remember { mutableIntStateOf(0) }

AndroidView(
    factory = { context ->
        createWebView(context) { progress = it }
    },
    update = {
        it.loadUrl(url)
    },
)

공식 문서에서 언급한 대로, view를 remember하지 않고 factory 안에서 만들었다.

 

앱을 실행해 보니 웹뷰가 잘 보인다. 성공!

참고문헌

 

Compose에서 뷰 사용  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose에서 뷰 사용 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Compose UI에 Android 뷰 계층 구조를 포

developer.android.com

 

 

androidx.compose.ui.viewinterop  |  Android Developers

androidx.compose.desktop.ui.tooling.preview

developer.android.com

 

Comments