Jetpack Compose 核心概念(二)

7. Compose 的渲染

7.1 Compose 渲染過程

對于任意一個 composable 的渲染主要分為三個階段:

  1. Composition,在這一階段決定哪些 composable 會被渲染并顯示出來悯舟。
  2. Layout看疗,在這一階段會進行測量和布局绿聘,也就是確認 composable 的大小和擺放的位置惰爬。
  3. Drawing金刁,在這一階段主要是完成繪制工作循签,將要展示的 composable 繪制到 canvas 上级乐。
image

Composition

Composition 分為 initial composition 和 recomposition 兩個過程。初次加載 Compose 結(jié)構(gòu)樹的過程主要是決定哪些 composable 會被顯示出來县匠,以及完成 composable 與 state 對象的綁定工作风科。

Recomposition 是當 UI 已經(jīng)顯示出來后撒轮,由于 composable 持有的 state 在與用戶交互過程中,發(fā)生了變化贼穆,而引起 UI 局部刷新的過程题山。這個局部刷新主要是以持有 state 狀態(tài)發(fā)生變化的 composabe 為根,根據(jù)子 composable 的輸入是否改變來向下遞歸的進行 UI 的刷新故痊。

@Composable
fun CompositionExample() {
    Log.d("composition", "CompositionExample")
    var inputState by remember { mutableStateOf("") }

    Column {
        HomeScreen(inputState) {
            inputState = it
        }

        HomeBottom()
    }
}

@Composable
fun HomeBottom() {
    Log.d("composition", "HomeBottom")
    Text(text = "This is the bottom")
}

@Composable
fun HomeScreen(value: String, textChanged: (String) -> Unit) {
    Log.d("composition", "HomeScreen")
    TextField(value = value, onValueChange = { textChanged(it) })
}

上面的代碼在完成 initial composition 的渲染后顶瞳,如果在 TextFiled 輸入框中輸入新的內(nèi)容,會引起 state 狀態(tài)的變化愕秫,進而引發(fā) recomposition 的刷新操作慨菱。

當在進行 recomposition 的刷新時,首先戴甩,直接持有 state 狀態(tài)對象的 composable 會進行刷新符喝,打印出 CompositionExample 日志;當 CompositionExample 在刷新的過程中甜孤,執(zhí)行到 HomeScreen composable 時协饲,發(fā)現(xiàn)其輸入?yún)?shù)發(fā)生了變化,會遞歸到 HomeScreen 中進行刷新课蔬,此時囱稽,HomeScreen 日志會被打印二跋;HomeScreen 執(zhí)行完成后战惊,執(zhí)行到 HomeBottom composable 時,由于其沒有輸入?yún)?shù)扎即,意味著此 composable 的輸入沒有發(fā)生改變吞获,所以,不會執(zhí)行 HomeBottom composable 及其子 composable谚鄙。(這里 HomeScreen 和 HomeBottom 不一定是順序調(diào)用各拷,兩者可能是并發(fā)同時在不同的線程被調(diào)用)

Layout

Layout 包含了兩個步驟:測量和布局,也就是測量 composable 的大小及確定擺放的位置闷营。

Layout 階段主要包含三個步驟:

  1. Measure children(測量子 composable 節(jié)點的大锌臼颉)
  2. Measure own size(測量自己的大小)
  3. Place children(為子 composable 指定擺放位置)

這三個步驟使用的是深度優(yōu)先的遞歸策略執(zhí)行的傻盟,如下圖所示:

image

以 Box1 的 layout 過程為例:

  1. Box1 先測量其所有子 composable 的大兴偃铩;
  2. Box1 測量自己的大心锔啊规哲;
  3. Box1 為其所有子 composable 指定其擺放的位置。

MeasureScope.measure 主要是負責測量的操作诽表;MeasureScope.layout 主要負責指定擺放位置的操作唉锌。

測量(measure)和定位(place)的獨立性:

  1. 測量和定位兩個步驟的操作是相互獨立的隅肥;
  2. 如果在測量過程中,讀取了 state 狀態(tài)袄简,由于測量通常都發(fā)生在自定義 Layout 過程中腥放,而測量后,緊接著就是定位的操作痘番,所以捉片,當測量過程中讀取的 state 狀態(tài)發(fā)生變化時平痰,會同時觸發(fā)測量和定位兩個操作汞舱。而當定位過程中,讀取了 state 狀態(tài)宗雇,由于定位可以直接在 composable 的 modifier 進行配置(如:Modifier.offset{....})昂芜,當其內(nèi)部引用的 state 狀態(tài)發(fā)生變化時,只會執(zhí)行定位的操作赔蒲,而不會觸發(fā)測量的執(zhí)行泌神。
var offsetX by remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    modifier = Modifier.offset {
        // 當 offsetX 狀態(tài)發(fā)生變化時,只會觸發(fā)定位邏輯 (layout)的執(zhí)行
        IntOffset(offsetX.roundToPx(), 0)
    }
)

Drawing

繪制通常使用兩種方式實現(xiàn)舞虱,一種方式是直接創(chuàng)建 Canvas 對象欢际,并調(diào)用 drawXXX 相關(guān)方法來進行自定義繪制;另一種方式是調(diào)用 Modifer.drawBehind{...} 或者 Modifier.drawContent{...} 來進行自定義繪制矾兜。

image

在進行自定義繪制過程中损趋,如果引用了 state 狀態(tài)對象,當 state 狀態(tài)對象發(fā)生變化時椅寺,只會觸發(fā)繪制階段的邏輯浑槽,而不會觸發(fā)測量或者定位階段的邏輯。

var color by remember { mutableStateOf(Color.Red) }
Canvas(modifier = modifier) {
    // 當 color 狀態(tài)發(fā)生變化時返帕,只會導(dǎo)致 drawRect 方法的再次執(zhí)行
    drawRect(color)
}

7.2 State 與 Layout 階段的關(guān)系

image
  1. 如果 Composable function 或者 composable lambda 綁定的 state 發(fā)生了變化后桐玻,會觸發(fā) composition 來刷新 UI;在 composition 過程中荆萤,如果內(nèi)容發(fā)生了變化镊靴,會執(zhí)行對應(yīng) composable 的 layout 操作;在 layout 的過程中链韭,如果 composable 的大小或者位置發(fā)生了變化偏竟,則會執(zhí)行對應(yīng) composable 的 drawing 操作。
  2. 如果自定義 Layout 中或者 Modifier.offset 綁定的 state 發(fā)生了變化后梧油,會觸發(fā) Layout 的操作對相應(yīng)的 composable 進行測量和定位的操作苫耸;如果測量或者定位過程中,對應(yīng)的大小或者位置發(fā)生了變化儡陨,則會觸發(fā) Drawing 的操作褪子。
  3. 如果自定義繪制 Canvas量淌、Modifier.drawBehind 或者 Modifer.drawContent 所綁定的 state 對象發(fā)生了變化,則會觸發(fā)對應(yīng) composable drawing 階段的操作嫌褪。

8. Modifier 呀枢、作用及其執(zhí)行順序

典型 Modifier 作為參數(shù)的用法及說明

@Composable
fun show(modifier: Modifier = Modifier) {
    Box(modifier.background(Color.Red)) {

    }
}

基本上來說,一般在定義一個 composable 的時候笼痛,都會將 modifier 作為參數(shù)傳入裙秋,且會給一個默認值 Modifier 對象。= 左右兩邊的 Modifier 分別代表什么呢缨伊?

= 左邊的 Modifier 實際上是一個 Modifier 接口摘刑,代表當前 modifier 的類型。

= 右邊的 Modifier 實際上是一個 Modifier 接口的默認實現(xiàn)類刻坊,該默認實現(xiàn)其實什么也沒有做枷恕,只是一個空的實現(xiàn)。

這種寫的好處是:

每一個 Composable 都能接收到外部 Composable 對其的約束谭胚。
當外部對當前 Composable 沒有任何要求時徐块,會使用默認的 Modifier 對象,不會改變?nèi)魏晤A(yù)期灾而。

Modifier 的執(zhí)行順序

多個 Modifier 鏈式調(diào)用在編譯過程中會被編譯成一個嵌套的關(guān)系胡控,其嵌套的原則是鏈頭部分在嵌套的最外層,鏈尾部分在嵌套結(jié)構(gòu)的最里層旁趟。

show(modifier = Modifier.padding(20.dp).size(80.dp))

[圖片上傳失敗...(image-f9d7e-1641956813507)]

當編譯過后的執(zhí)行過程是從嵌套的最內(nèi)層依次往外層進行執(zhí)行的昼激,也就是從鏈的最右邊為執(zhí)行的起點,依次執(zhí)行轻庆,直到鏈最左邊的調(diào)用被執(zhí)行完為止癣猾。

如上面 show 函數(shù)中的 modifier 的執(zhí)行過程是:

先執(zhí)行 .size(80.dp),再執(zhí)行 .paading(20.dp)余爆,其執(zhí)行結(jié)果為:

image

針對 size纷宇、width 和 height 等 布局相關(guān)的配置,如果同樣的配置被重復(fù)配置蛾方,且值是不同的像捶,則前面執(zhí)行的配置會被后面的配置所覆蓋。

show(modifier = Modifier.padding(20.dp).size(80.dp).size(10.dp))

由于先執(zhí)行了 .size(10.dp) 后桩砰,再執(zhí)行的 .size(80.dp)拓春,后面的將前面的配置覆蓋了,所以亚隅,默認情況下硼莽,只有 .size(80.dp) 的配置才會被生效。

針對 size煮纵、width 和 height 等的配置懂鸵,可以通過 requiredXXX 來改變其默認的執(zhí)行結(jié)果

show(
    modifier = Modifier
        .padding(20.dp)
        .background(Color.Green)
        .size(80.dp)
        .requiredSize(10.dp)
        .background(Color.Red)
)

上面代碼的執(zhí)行結(jié)果為:

image

從上圖可以看出偏螺,使用了 requiredSize 后,其配置顯示出來了匆光,但他影響的只是在它前面執(zhí)行(也就是鏈后)的配置套像。在其它后面執(zhí)行的 .size(80.dp) (背景為綠色部分)還是正常顯示,并沒有任何影響终息。

針對 size夺巩、width 和 height 等的配置,如果 requiredSize 的值比后面執(zhí)行的 Size 的值要大周崭,也會被 Size 給約束芳誓,如果有 padding 值的話毁欣,會變成 requiredSize 的一部分

image

9. Jetpack Compose 架構(gòu)怠噪,各層的作用及如何添加依賴

9.1 Jetpack Compose 五層架構(gòu)

依賴庫包各層作用說明:

image

依賴關(guān)系圖:

image

為什么 Button 會在 Material 庫里面郑趁,而不是在 foundation 庫里面奢人?

因為在 Compose 中 Button 的組件的組成是非常靈活的丹泉,里面需要指定不同的組件及排列方式(如:Text()淹禾、Icon()畔乙、Column() 等等)柴墩。我們所使用的 Button 之所以放在 Material 包里面忙厌,是因為在 Material 庫中指定了默認的排列順序 Row()。

同一層中的多個包又是什么關(guān)系呢江咳?

一般來說逢净,我們只需要引入同一層中同名的庫包,就會將其所屬的其他包一起加入進來歼指。如:androidx.compose.ui:ui:xxx 包就包含了 androidx.compose.ui:ui-text:xxx爹土、androidx.compose.ui:ui-graphics:xxxandroidx.compose.ui:ui-util:xxx 等庫包。

例外情況:

androidx.compose.ui:ui:xxx 不包含 androidx.compose.ui:ui-tooling:xxx

androidx.compose.material:material:xxx 不包含 androidx.compose.material:material-icons-extended:xxx

9.2 五層架構(gòu)的好處

  1. 靈活控制踩身。層級越高的組件胀茵,使用起來更加簡單,但相對限制更多挟阻;層級越低的組件琼娘,可擴展性超高,但使用起來也相對復(fù)雜附鸽。使用者可以根據(jù)需求靈活選擇使用哪一層級的組件脱拼。
  2. 自定義簡單。自定義高級別組件的時候坷备,可以非常容易的通過組合低級別的組件來完成自定義的工作熄浓。比如:Material 層級的 Button 按鈕就是通過組合 Material、Foundatation省撑、Runtime 層級的組件來完成自定義功能的赌蔑。
image

10. CompositionLocal

CompositionLocal 主要是為了解決 Composable 樹結(jié)構(gòu)中谎柄,多個底層分支依賴上層某個數(shù)據(jù)時,需要將對應(yīng)的值通過函數(shù)參數(shù)不斷向下傳遞的問題惯雳。

使用 CompositionLocal 的流程

  1. 創(chuàng)建 CompositionLocal 對象:通過 staticCompositionLocalOf 或者 compositionLocalOf 兩種方式來創(chuàng)建該對象朝巫。

1.1 staticCompositionLocalOf

val ColorCompositionLocal = staticCompositionLocalOf<Color> {
    error("No Color provided")
}

1.2 compositionLocalOf

val ColorCompositionLocal = compositionLocalOf<Color> {
    error("No Color provided")
}
  1. 通過 CompositionLocalProvider 指定 CompositionLocal 的作用范圍并綁定需要共享的帶狀態(tài)的對象或值
CompositionLocalProvider(LocalActivateUser provides user) {
    UserProfile()
}

這里首先通過 CompositionLocalProvider 指定了 CompositionLocal 的作用范圍為 UserProfile() 及其所有子 composable 函數(shù)。同時石景,綁定了 user 對象(帶狀態(tài))作為共享的值來被 UserProfile() 及其所有子 composable 函數(shù)調(diào)用劈猿。

  1. 在對應(yīng)的 Composable 函數(shù)中調(diào)用 CompositionLocal 共享的帶狀態(tài)的值
@Composable
fun UserProfile() {
    Column {
        Text(text = LocalActivateUser.current.name)
    }
}

Note:CompositionLocal 對象的命名一般以 Local 開頭。

staticCompositionLocalOf 和 compositionLocalOf 的區(qū)別

下面的圖是一個使用 CompositionLocal 的例子潮孽,點擊 click 按鈕后揪荣,會更新帶狀態(tài)的 Color 的值。分別使用 staticCompositionLocalOf 或者 compositionLocalOf 對象來看看帶狀態(tài)的 Color 值變化后往史,兩者的表現(xiàn)有什么區(qū)別仗颈。

image
  1. 使用 staticCompositionLocalOf
image

當帶狀態(tài)的 Color 值發(fā)生變化后,其被包含的所有 composable function 都會觸發(fā) recompose 操作椎例。

  1. 使用 compositionLocalOf

[圖片上傳失敗...(image-e0a720-1641956813508)]

當帶狀態(tài)的 Color 值發(fā)生變化后挨决,只有直接引用了 Color 值的 Composable function 才會觸發(fā) recompose 操作。

var LocalColorComposition = compositionLocalOf<Color> { error("No color provided") }

var stateColor by mutableStateOf(Color.LightGray)

@Composable
fun CompositionLocalAndStaticCompositionLocal() {
    Column {
        Button(onClick = {
            stateColor = if (stateColor == Color.LightGray) Color.Red else Color.LightGray
        }) {
            Text(text = "Update stateColor")
        }

        CompositionLocalProvider(LocalColorComposition provides stateColor) {
//            CoverCompossables()
            CoverCompossables1()
        }
    }
}

@Composable
fun CoverCompossables1() {
    outsideCount++
    MyBox(color = Color.Green, count = outsideCount) {
        centerCount++
        MyBox(color = LocalColorComposition.current, count = centerCount) {
            insideCount++
            MyBox(color = Color.White, count = insideCount) {
            }
        }
    }
}

@Composable
fun MyBox(color: Color, count: Int, content: @Composable BoxScope.() -> Unit) {
    Column(Modifier.background(color)) {
        Text(text = "current value: $count")
        Box(
            modifier = Modifier
                .padding(16.dp)
                .fillMaxSize(),
            content = content
        )
    }
}

Note:

如果這里沒有抽取 MyBox Composable 函數(shù)订歪,而是直接以層級形式直接展開的話脖祈,上面的特性會失效。無論使用 CompositionLocalOf 還是 StaticCompositionLocalOf刷晋,結(jié)果都會全部刷新盖高。

未抽取 MyBox Composable 函數(shù)的代碼:

var LocalColorComposition = compositionLocalOf<Color> { error("No color provided") }

var stateColor by mutableStateOf(Color.LightGray)

@Composable
fun CompositionLocalAndStaticCompositionLocal() {
    Column {
        Button(onClick = {
            stateColor = if (stateColor == Color.LightGray) Color.Red else Color.LightGray
        }) {
            Text(text = "Update stateColor")
        }

        CompositionLocalProvider(LocalColorComposition provides stateColor) {
            CoverCompossables()
//            CoverCompossables1()
        }
    }
}

@Composable
fun CoverCompossables() {
    outsideCount++
    Column(
        Modifier
            .size(1000.dp)
            .background(Color.Green)
    ) {
        Log.d("TAG", "outside")
        Text(text = "current value: $outsideCount")
        Box(
            Modifier
                .padding(16.dp)
                .fillMaxSize(), contentAlignment = Alignment.Center
        ) {
            centerCount++
            Column(
                Modifier
                    .size(800.dp)
                    .background(LocalColorComposition.current)
            ) {
                Log.d("TAG", "center")
                Text(text = "current value: $centerCount")
                Box(
                    Modifier
                        .padding(16.dp)
                        .fillMaxSize(), contentAlignment = Alignment.Center
                ) {
                    insideCount++
                    Column(
                        Modifier
                            .size(600.dp)
                            .background(Color.White)
                    ) {
                        Log.d("TAG", "inside")
                        Text(text = "current value: $insideCount")
                    }
                }
            }
        }
    }
}

作用

解決前的數(shù)據(jù)傳遞鏈路圖:

image

解決 Composable 樹結(jié)構(gòu)中,多個底層分支依賴上層某個數(shù)據(jù)時眼虱,需要將對應(yīng)的值通過函數(shù)參數(shù)不斷向下傳遞的問題喻奥。

解決后的數(shù)據(jù)傳遞鏈路圖:

image

與全局靜態(tài)變量的區(qū)別

通過 CompositionLocal 共享的值,只能在共享該值的結(jié)點及其子結(jié)點才能使用捏悬。其它地方使用會拋異常撞蚕。

CompositionLocalProvider 的實現(xiàn)

@Composable
@OptIn(InternalComposeApi::class)
fun CompositionLocalProvider(
    vararg values: ProvidedValue<*>, 
    content: @Composable () -> Unit) {
    currentComposer.startProviders(values)
    content()
    currentComposer.endProviders()
}

可以看到 CompositionLocalProvider 在 content 執(zhí)行之前開始生效,而在 content 執(zhí)行之后就被釋放了邮破。同時诈豌,可以看到,CompositionLocalProvider 接收多個對象或值的共享抒和。

11. Migration(遷移)

11.1 如何獲取 xml 資源文件的值

  • dimensionResource(id) -> dimens.xml
  • stringResource(id) -> strings.xml
  • XxxResource(id) -> xxx.xml

11.2 Livedata 在 composable 中如何使用

使用 LiveData 的擴展函數(shù) observeAsState 將其轉(zhuǎn)換為 Composable 中的 State<T> 對象矫渔。

@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
    // Observes values coming from the VM's LiveData<Plant> field
    val plant by plantDetailViewModel.plant.observeAsState()

    // If plant is not null, display the content
    plant?.let {
        PlantDetailContent(it)
    }
}

Note:由于 LiveData 可以發(fā)送 null 值,在使用的地方需要判空摧莽。

11.3 Compose 中無法顯示 HTML 格式的文本

使用 AndroidView 來使用傳統(tǒng)的 View System 中的控件并將其顯示在 Compose 中庙洼。在 AndroidView 中有兩個函數(shù)類型的參數(shù),一個 factory 參數(shù)表示在此創(chuàng)建傳統(tǒng) View System 中的控件,當構(gòu)建完成后油够,會回調(diào)到 update 函數(shù)蚁袭,并當 factory 中創(chuàng)建的控件當作函數(shù)參數(shù)傳入,此時石咬,就可以對該控件進行設(shè)值等操作了揩悄。

同時,在 update 回調(diào)中引用的外部的 state 變量(如下面的 htmlDescription 值是一個 mutableStateOf 狀態(tài)對象)變化后鬼悠,update 會重新被調(diào)用删性。

@Composable
fun androidViewDemo() {
    var htmlDescription by remember {
        mutableStateOf(
            HtmlCompat.fromHtml(
                "HTML<br><br>description",
                HtmlCompat.FROM_HTML_MODE_COMPACT
            )
        )
    }

    Column {
        AndroidView(factory = { content ->
            TextView(content)
        }, update = {
            it.text = htmlDescription
        })

        Button(onClick = {
            htmlDescription =
                HtmlCompat.fromHtml("HTML<br><br>update", HtmlCompat.FROM_HTML_MODE_COMPACT)
        }) {
            Text(text = "更改 text 的顯示")
        }
    }
}

Note:在 AndroidView 的 update 回調(diào)中,引用的任何 State 狀態(tài)對象焕窝,只要狀態(tài)對象發(fā)生變化后蹬挺,都會引起 update 回調(diào)方法再次執(zhí)行,類似于 reComposition它掂。

11.4 如何在 Compose 中使用傳統(tǒng) View System 中的 Theme

如果想要在 Compose 中使用傳統(tǒng) View System 中的 Theme巴帮,需要使用 compose-theme-adapter 庫,該庫可以自動將 style 文件中的主題轉(zhuǎn)換成 composable 類型的主題虐秋,并生成以 MdcTheme 固定名稱 composable 主題榕茧。

@Composable
fun MdcTheme(
    context: Context = AmbientContext.current,
    readColors: Boolean = true,
    readTypography: Boolean = true,
    readShapes: Boolean = true,
    setTextColors: Boolean = false,
    content: @Composable () -> Unit
) {
    val key = context.theme.key ?: context.theme

    val themeParams = remember(key) {
        createMdcTheme(
            context = context,
            readColors = readColors,
            readTypography = readTypography,
            readShapes = readShapes,
            setTextColors = setTextColors
        )
    }

    MaterialTheme(
        colors = themeParams.colors ?: MaterialTheme.colors,
        typography = themeParams.typography ?: MaterialTheme.typography,
        shapes = themeParams.shapes ?: MaterialTheme.shapes,
        content = content
    )
}

11.5 如何在傳統(tǒng)的 View System 中使用 Compose

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="hello world" />

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/acv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv" />

</RelativeLayout>
findViewById<ComposeView>(R.id.acv).setContent { 
            Text(text = "ComposeView")
        }

11.6 如何在 Compose 中使用傳統(tǒng)的 View

動態(tài)創(chuàng)建傳統(tǒng) View:

setContent {
    Column {
        Text(text = "top")
        AndroidView(factory = {
            View(it).apply {
                setBackgroundColor(android.graphics.Color.GRAY)
            }
        }, Modifier.size(30.dp)) {
            // update
        }
        Text(text = "bottom")
    }
}

factory 的作用:用于創(chuàng)建由傳統(tǒng) View System 所構(gòu)建的布局。只會被執(zhí)行一次熟妓。

update 的作用:用于界面每次 Recompose 的時候刷新傳統(tǒng) View System 所構(gòu)建的布局雪猪。會拿到一個在 factory 過程中生成的 View 引用對象,來進行操作起愈。

image

11.7 Compose 中內(nèi)部數(shù)據(jù)與 Compose 外部數(shù)據(jù)的交互

Compose 內(nèi)部使用外部非 State 數(shù)據(jù)

  1. LiveData 數(shù)據(jù)更新觸發(fā) Recompose(Compose 使用外部數(shù)據(jù))

// 外部數(shù)據(jù)
val result = MutableLiveData(1)

setContent {
    // Compose 內(nèi)部
    val num = result.observeAsState()

    Text(text = "$num")
}
  1. 協(xié)程 Flow 發(fā)送數(shù)據(jù)觸發(fā) Recompose(Compose 使用外部數(shù)據(jù))
// 外部數(shù)據(jù)
val flowOjb = flow { emit(1) }
setContent {
    // 內(nèi)部數(shù)據(jù)
    val num = flowOjb.collectAsState(initial = 0)
    Text(text = "$num")
}

Compose 外部實現(xiàn)使用 Compose 內(nèi)部數(shù)據(jù)

在 Compose 的內(nèi)部數(shù)據(jù)的主要表現(xiàn)形式是:State<T>,這個對象是無法轉(zhuǎn)換成其它對象(如:LiveData)的译仗,所以抬虽,外部實現(xiàn)是無法使用 Compose 中的數(shù)據(jù)的。

解決方法:如果外部數(shù)據(jù)需要得到 Compose 內(nèi)部數(shù)據(jù)的話纵菌,在一開始設(shè)計的時候阐污,就需要將該數(shù)據(jù)結(jié)構(gòu)定義在外部(如:LiveData)。

12. Intrinsic 固有特性測量

固有特性測量的本質(zhì)就是父組件可在正式測量布局前預(yù)先獲取到每個子組件寬高信息后通過計算來確定自身的固定寬度或高度咱圆,從而間接影響到其中包含的部分子組件布局信息笛辟。

也就是說子組件可以根據(jù)自身寬高信息來確定父組件的寬度或高度,從而影響其他子組件布局序苏。

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier.height(IntrinsicSize.Min)) { // I'm here
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )

        Divider(color = Color.Black, modifier = Modifier.fillMaxHeight().width(1.dp))
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),
            text = text2
        )
    }
}

13. Recompose 優(yōu)化

  1. 無參數(shù)的 Composable 函數(shù)手幢,在 Recompose 時,不會再次執(zhí)行函數(shù)內(nèi)的代碼
setContent {
        var name by remember { mutableStateOf("allen") }

        Column {
            Button(onClick = {
                name = "amy"
            }) {
                Text(text = "更改文字內(nèi)容")
            }
            Text(text = name)
            ComposableMethodWithoutParams()
        }
    }
}

@Composable
private fun ComposableMethodWithoutParams() {
    Log.d("TAG", "composableMethodWithoutParams")
    Text(text = "composableMethodWithoutParams")
}

上面的代碼中忱详,點擊了按鈕后围来,會導(dǎo)致 recompose 操作,但是 ComposableMethodWithoutParams 中的 log 并沒有執(zhí)行,說明监透,當前函數(shù)中代碼并沒有執(zhí)行桶错。

  1. 帶參數(shù)的 Composable 函數(shù),在 Recompose 時胀蛮,如果所有參數(shù)都沒有發(fā)生改變院刁,也不會再次執(zhí)行函數(shù)內(nèi)的代碼
var flag = 1
        setContent {
            var name by remember { mutableStateOf("allen") }

            Column {
                Button(onClick = {
                    name = "amy"
                }) {
                    Text(text = "更改文字內(nèi)容")
                }
                Text(text = name)
                ComposableMethodWithoutParams(flag)
            }
        }
    }

    @Composable
    private fun ComposableMethodWithoutParams(result: Int) {
        Log.d("TAG", "composableMethodWithoutParams")
        Text(text = "composableMethodWithoutParams $result")
    }
  1. Structurial Equality(==):Recompose 執(zhí)行過程中,如果引用的類對象中所有屬性都是使用 val 修辭的話粪狼,使用的是結(jié)構(gòu)性相等黎比,也就是在判斷是否執(zhí)行某個 Composable 函數(shù)中的代碼時,判斷其參數(shù)是否改變使用的是 ==
        var user = User("allen")
        setContent {
            var name by remember { mutableStateOf("allen") }

            Column {
                Button(onClick = {
                    name = "amy"
                    user = User("allen")
                }) {
                    Text(text = "更改文字內(nèi)容")
                }
                Text(text = name)
                ComposableMethodWithoutParams(user)
            }
        }
    }

    data class User(val name: String)

    @Composable
    private fun ComposableMethodWithoutParams(result: User) {
        Log.d("TAG", "composableMethodWithoutParams")
        Text(text = "composableMethodWithoutParams ${result.name}")
    }

當點擊了按鈕后鸳玩,composableMethodWithoutParams 函數(shù)中的 log 并沒有打印阅虫,說明使用的是 ==(equals)來進行判斷的。

  1. 對于不可靠的類不跟,也就是類中變量的聲明使用的是 var 修辭的類颓帝,Recompose 默認使用的是 ===(引用相等)來判斷是否需要重新執(zhí)行的。

為什么對于使用 var 修辭變量的類需要使用 ===(引用相等)呢窝革?

    val user = User("allen")
    val user2 = User("allen")
    var currentUser = user
    setContent {
        Column {
            Button(onClick = {
                currentUser = user2
            }) {
                Text(text = "更改 currentUser 引用")
                currentUser = user2
            }

            showUser(currentUser)
        }

    }
}

private fun showUser(currentUser: User) {
    Log.d("TAG", "currentUser.name = ${currentUser.name}")
}

data class User(var name: String)

看上面的代碼购城,如果這里使用的是 Structural equals 方法的話,當點擊按鈕將 currentUser 的引用指向 user2 時虐译,由于 user1 和 user2 使用 == 進行比較是相等的瘪板,所以,此時 showUser 函數(shù)不會被 Recompose漆诽。

也就是說侮攀,showUser 函數(shù)中引用的還是 user1,但是 currentUser 已經(jīng)指向了 User2厢拭,當后面如果對 user2 進行了修改兰英,如果 showUser 里面由于某種原因被 Recompose 了,而不是通過外部導(dǎo)致的 Recompose 的話供鸠,由于 showUser 函數(shù)中的值引用的還是 user1畦贸,而不會更新,這樣就與我們想要的結(jié)果不符了楞捂。

如果想要對使用了 var 修辭的類也使用 ===(結(jié)構(gòu)性相等)的話薄坏,可以使用 @Stable 關(guān)鍵字對類進行修辭。當然寨闹,上面可能會出現(xiàn)的問題胶坠,就需要碼農(nóng)自己來保證了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鼻忠,一起剝皮案震驚了整個濱河市涵但,隨后出現(xiàn)的幾起案子杈绸,更是在濱河造成了極大的恐慌,老刑警劉巖矮瘟,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞳脓,死亡現(xiàn)場離奇詭異,居然都是意外死亡澈侠,警方通過查閱死者的電腦和手機劫侧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哨啃,“玉大人烧栋,你說我怎么就攤上這事∪颍” “怎么了审姓?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長祝峻。 經(jīng)常有香客問我魔吐,道長,這世上最難降的妖魔是什么莱找? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任酬姆,我火速辦了婚禮,結(jié)果婚禮上奥溺,老公的妹妹穿的比我還像新娘辞色。我一直安慰自己,他們只是感情好浮定,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布相满。 她就那樣靜靜地躺著,像睡著了一般壶唤。 火紅的嫁衣襯著肌膚如雪雳灵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天闸盔,我揣著相機與錄音,去河邊找鬼琳省。 笑死迎吵,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的针贬。 我是一名探鬼主播击费,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼桦他!你這毒婦竟也來了蔫巩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎圆仔,沒想到半個月后垃瞧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡坪郭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年个从,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歪沃。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡嗦锐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沪曙,到底是詐尸還是另有隱情奕污,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布液走,位于F島的核電站碳默,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏育灸。R本人自食惡果不足惜腻窒,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磅崭。 院中可真熱鬧儿子,春花似錦、人聲如沸砸喻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽割岛。三九已至愉适,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間癣漆,已是汗流浹背维咸。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惠爽,地道東北人癌蓖。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像婚肆,于是被迫代替她去往敵國和親租副。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容