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가 재활용되지 않을 때 사용해야 한다. ``LazyRow``나 ``LazyColumn`` 등 컴포넌트를 재사용할 수 있는 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

``onReset``과 ``onRelease`` 람다가 눈에 띈다.

 

``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