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 。这些,就等後续分享吧!


<<:  LeetCode 双刀流:Stack 与 Queue 的相互实作

>>:  Day17_HTML语法14

Spring Framework X Kotlin Day 15 AOP

GitHub Repo https://github.com/b2etw/Spring-Kotlin...

Day 1 | 在安装之後

这是第一次参加铁人赛,期待自己可以依照书上的教学将Kotlin学好,并具备开发小型系统的能力。 此次...

Day 26 Docker-Compose nginx + flask container with filebeat-another structure

Day 26 Docker-Compose nginx + flask container with...

万事起头难

我对VR的第一印象,就是一个人戴着罩住眼睛的头盔,手拿着摇杆的游戏。但是这个软件到底要怎麽制作,且是...

[Lesson29] Git

// 移动到档案路径 cd "档案路径" // 确认git版本并有无正确安装 g...