iT邦幫忙

2021 iThome 鐵人賽

DAY 17
0
Mobile Development

認真學 Compose - 對 Jetpack Compose 的問題與探索系列 第 17

D17/ 我要用的 View 沒有支援 Compose 怎麼辦? - AndroidView

今天大概會聊到的範圍

  • Android View

前兩天來回進出了公司樓下的 7-11 兩三次,每次都要掃一次實名制的 QR Code。我現在用的 App 又有點笨,每次掃完之後都還要手動選要送 SMS(也是啦,又不是專門為 1922 開發的 App)。不過,作為工程師的好處是,當有件事情無法滿足你的需求,你可以自己開發東西來改變它!於是,我想要自己寫一個 QR Code  Scanner 而且能認得 1922 的格式自動送 SMS。

我想說,QR Code Scanner 之前有試著做過,沒有很複雜。用 CameraX 配上 MLKit 應該就可以達成了吧! 打開 Android Studio,熟悉的建立了一個 Compose App,然後插入 CameraPreview ....

等等!沒有 XML 我要怎麼放 CameraPreview?

在 Compose 中放入 Android view

Compose 中,有一個 Composable 叫做 AndroidView 。在 AndroidView 中,我們可以擺放繼承 View  的元件。

@Composable
fun Screen() {
    AndroidView(
        factory = { context ->
            val view = // create view 
            view
        }
    )
}

舉個例,Compose 的 Text 並沒有直接顯示 html 的功能,但是 android.widget 的 TextView 可以吃轉成 html 的 Spanable。所以可以透過 AndroidView 來使用 TextView 。

@Composable
fun TextViewHtml() {
    
    val html = """
        <h3">Title</h3>
        <hr>
        <ul>
            <li><p>First</p></li>
            <li><p>Second</p></li>
        </ul>
    """.trimIndent()
    
    AndroidView(
        factory = { context ->
            val textView = TextView(context)
    
            textView.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                HtmlCompat.fromHtml(html, 0)
            } else {
                Html.fromHtml(html)
            }
            
            textView
        },
        modifier = Modifier.background(color = Color.White)
    )
}

除了熟悉的 modifier 參數外,AndroidView 需要一個負責產生 view 的 factory lambda,這個 Lambda 中可以取得 context ,透過這個 context 可以產生出 Android 的 View。

除了 factory 外,還可以提供另一個 lambda update,因為 composable 會在 recomposition 時重新繪製,但是每次重新繪製都產生一個新的 android view 太浪費了。所以 factory 其實不會在 recomposition 被觸發。要改變 AndroidView 中的內容的話,需要透過 update 參數可以告訴 AndroidView 我們要使用的 view 如何變動。

@Composable
fun <T : View> AndroidView(
    factory: (Context) -> T,
    modifier: Modifier = Modifier,
    update: (T) -> Unit = NoOpUpdate
) { ... }
@Composable
fun TextViewHtml() {
    
    var data by remember { mutableStateOf(0) }
    
    AndroidView(
        factory = { context ->
            
            val html = """
                    <h3">Title</h3>
                    <br/>
                    <p>$data</p>
                """.trimIndent()
            
            val textView = TextView(context)
            
            textView.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                HtmlCompat.fromHtml(html, 0)
            } else {
                Html.fromHtml(html)
            }
            
            textView
        },
        update = { textView: TextView ->        // <-- update 中可以拿到 factory 建立的 view
    
            val html = """
                    <h3">Title</h3>
                    <br/>
                    <p>$data</p>
                """.trimIndent()
    
            textView.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                HtmlCompat.fromHtml(html, 0)
            } else {
                Html.fromHtml(html)
            }
        },
        modifier = Modifier.fillMaxWidth().background(color = Color.White).clickable { data += 1 }
    )
}

今天的題目比較小,但卻是非常實用的東西。有了 AndroidView 我就可以將 CameraX 放進 Compose 專案了。

但實際上還一些問題沒解,例如如何在取得 camera permission 之後觸發 recomposition 。這些,就等後續分享吧!


上一篇
D16/ 所以到底為什麼 remember 是 composable function? - @Composable 是什麼 part 2
下一篇
D18/ 怎麼在 Compose 中取得 Permission? - rememberLauncherForActivityResult
系列文
認真學 Compose - 對 Jetpack Compose 的問題與探索30

尚未有邦友留言

立即登入留言