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 結構樹的過程主要是決定哪些 composable 會被顯示出來,以及完成 composable 與 state 對象的綁定工作。

Recomposition 是當 UI 已經顯示出來后,由于 composable 持有的 state 在與用戶交互過程中,發生了變化,而引起 UI 局部刷新的過程。這個局部刷新主要是以持有 state 狀態發生變化的 composabe 為根,根據子 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 輸入框中輸入新的內容,會引起 state 狀態的變化,進而引發 recomposition 的刷新操作。

當在進行 recomposition 的刷新時,首先,直接持有 state 狀態對象的 composable 會進行刷新,打印出 CompositionExample 日志;當 CompositionExample 在刷新的過程中,執行到 HomeScreen composable 時,發現其輸入參數發生了變化,會遞歸到 HomeScreen 中進行刷新,此時,HomeScreen 日志會被打印;HomeScreen 執行完成后,執行到 HomeBottom composable 時,由于其沒有輸入參數,意味著此 composable 的輸入沒有發生改變,所以,不會執行 HomeBottom composable 及其子 composable。(這里 HomeScreen 和 HomeBottom 不一定是順序調用,兩者可能是并發同時在不同的線程被調用)

Layout

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

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

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

這三個步驟使用的是深度優先的遞歸策略執行的,如下圖所示:

image

以 Box1 的 layout 過程為例:

  1. Box1 先測量其所有子 composable 的大小;
  2. Box1 測量自己的大小;
  3. Box1 為其所有子 composable 指定其擺放的位置。

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

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

  1. 測量和定位兩個步驟的操作是相互獨立的;
  2. 如果在測量過程中,讀取了 state 狀態,由于測量通常都發生在自定義 Layout 過程中,而測量后,緊接著就是定位的操作,所以,當測量過程中讀取的 state 狀態發生變化時,會同時觸發測量和定位兩個操作。而當定位過程中,讀取了 state 狀態,由于定位可以直接在 composable 的 modifier 進行配置(如:Modifier.offset{....}),當其內部引用的 state 狀態發生變化時,只會執行定位的操作,而不會觸發測量的執行。
var offsetX by remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    modifier = Modifier.offset {
        // 當 offsetX 狀態發生變化時,只會觸發定位邏輯 (layout)的執行
        IntOffset(offsetX.roundToPx(), 0)
    }
)

Drawing

繪制通常使用兩種方式實現,一種方式是直接創建 Canvas 對象,并調用 drawXXX 相關方法來進行自定義繪制;另一種方式是調用 Modifer.drawBehind{...} 或者 Modifier.drawContent{...} 來進行自定義繪制。

image

在進行自定義繪制過程中,如果引用了 state 狀態對象,當 state 狀態對象發生變化時,只會觸發繪制階段的邏輯,而不會觸發測量或者定位階段的邏輯。

var color by remember { mutableStateOf(Color.Red) }
Canvas(modifier = modifier) {
    // 當 color 狀態發生變化時,只會導致 drawRect 方法的再次執行
    drawRect(color)
}

7.2 State 與 Layout 階段的關系

image
  1. 如果 Composable function 或者 composable lambda 綁定的 state 發生了變化后,會觸發 composition 來刷新 UI;在 composition 過程中,如果內容發生了變化,會執行對應 composable 的 layout 操作;在 layout 的過程中,如果 composable 的大小或者位置發生了變化,則會執行對應 composable 的 drawing 操作。
  2. 如果自定義 Layout 中或者 Modifier.offset 綁定的 state 發生了變化后,會觸發 Layout 的操作對相應的 composable 進行測量和定位的操作;如果測量或者定位過程中,對應的大小或者位置發生了變化,則會觸發 Drawing 的操作。
  3. 如果自定義繪制 CanvasModifier.drawBehind 或者 Modifer.drawContent 所綁定的 state 對象發生了變化,則會觸發對應 composable drawing 階段的操作。

8. Modifier 、作用及其執行順序

典型 Modifier 作為參數的用法及說明

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

    }
}

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

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

= 右邊的 Modifier 實際上是一個 Modifier 接口的默認實現類,該默認實現其實什么也沒有做,只是一個空的實現。

這種寫的好處是:

每一個 Composable 都能接收到外部 Composable 對其的約束。
當外部對當前 Composable 沒有任何要求時,會使用默認的 Modifier 對象,不會改變任何預期。

Modifier 的執行順序

多個 Modifier 鏈式調用在編譯過程中會被編譯成一個嵌套的關系,其嵌套的原則是鏈頭部分在嵌套的最外層,鏈尾部分在嵌套結構的最里層。

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

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

當編譯過后的執行過程是從嵌套的最內層依次往外層進行執行的,也就是從鏈的最右邊為執行的起點,依次執行,直到鏈最左邊的調用被執行完為止。

如上面 show 函數中的 modifier 的執行過程是:

先執行 .size(80.dp),再執行 .paading(20.dp),其執行結果為:

image

針對 size、width 和 height 等 布局相關的配置,如果同樣的配置被重復配置,且值是不同的,則前面執行的配置會被后面的配置所覆蓋。

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

由于先執行了 .size(10.dp) 后,再執行的 .size(80.dp),后面的將前面的配置覆蓋了,所以,默認情況下,只有 .size(80.dp) 的配置才會被生效。

針對 size、width 和 height 等的配置,可以通過 requiredXXX 來改變其默認的執行結果

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

上面代碼的執行結果為:

image

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

針對 size、width 和 height 等的配置,如果 requiredSize 的值比后面執行的 Size 的值要大,也會被 Size 給約束,如果有 padding 值的話,會變成 requiredSize 的一部分

image

9. Jetpack Compose 架構,各層的作用及如何添加依賴

9.1 Jetpack Compose 五層架構

依賴庫包各層作用說明:

image

依賴關系圖:

image

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

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

同一層中的多個包又是什么關系呢?

一般來說,我們只需要引入同一層中同名的庫包,就會將其所屬的其他包一起加入進來。如:androidx.compose.ui:ui:xxx 包就包含了 androidx.compose.ui:ui-text:xxxandroidx.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 五層架構的好處

  1. 靈活控制。層級越高的組件,使用起來更加簡單,但相對限制更多;層級越低的組件,可擴展性超高,但使用起來也相對復雜。使用者可以根據需求靈活選擇使用哪一層級的組件。
  2. 自定義簡單。自定義高級別組件的時候,可以非常容易的通過組合低級別的組件來完成自定義的工作。比如:Material 層級的 Button 按鈕就是通過組合 Material、Foundatation、Runtime 層級的組件來完成自定義功能的。
image

10. CompositionLocal

CompositionLocal 主要是為了解決 Composable 樹結構中,多個底層分支依賴上層某個數據時,需要將對應的值通過函數參數不斷向下傳遞的問題。

使用 CompositionLocal 的流程

  1. 創建 CompositionLocal 對象:通過 staticCompositionLocalOf 或者 compositionLocalOf 兩種方式來創建該對象。

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 的作用范圍并綁定需要共享的帶狀態的對象或值
CompositionLocalProvider(LocalActivateUser provides user) {
    UserProfile()
}

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

  1. 在對應的 Composable 函數中調用 CompositionLocal 共享的帶狀態的值
@Composable
fun UserProfile() {
    Column {
        Text(text = LocalActivateUser.current.name)
    }
}

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

staticCompositionLocalOf 和 compositionLocalOf 的區別

下面的圖是一個使用 CompositionLocal 的例子,點擊 click 按鈕后,會更新帶狀態的 Color 的值。分別使用 staticCompositionLocalOf 或者 compositionLocalOf 對象來看看帶狀態的 Color 值變化后,兩者的表現有什么區別。

image
  1. 使用 staticCompositionLocalOf
image

當帶狀態的 Color 值發生變化后,其被包含的所有 composable function 都會觸發 recompose 操作。

  1. 使用 compositionLocalOf

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

當帶狀態的 Color 值發生變化后,只有直接引用了 Color 值的 Composable function 才會觸發 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 函數,而是直接以層級形式直接展開的話,上面的特性會失效。無論使用 CompositionLocalOf 還是 StaticCompositionLocalOf,結果都會全部刷新。

未抽取 MyBox Composable 函數的代碼:

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")
                    }
                }
            }
        }
    }
}

作用

解決前的數據傳遞鏈路圖:

image

解決 Composable 樹結構中,多個底層分支依賴上層某個數據時,需要將對應的值通過函數參數不斷向下傳遞的問題。

解決后的數據傳遞鏈路圖:

image

與全局靜態變量的區別

通過 CompositionLocal 共享的值,只能在共享該值的結點及其子結點才能使用。其它地方使用會拋異常。

CompositionLocalProvider 的實現

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

可以看到 CompositionLocalProvider 在 content 執行之前開始生效,而在 content 執行之后就被釋放了。同時,可以看到,CompositionLocalProvider 接收多個對象或值的共享。

11. Migration(遷移)

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

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

11.2 Livedata 在 composable 中如何使用

使用 LiveData 的擴展函數 observeAsState 將其轉換為 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 可以發送 null 值,在使用的地方需要判空。

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

使用 AndroidView 來使用傳統的 View System 中的控件并將其顯示在 Compose 中。在 AndroidView 中有兩個函數類型的參數,一個 factory 參數表示在此創建傳統 View System 中的控件,當構建完成后,會回調到 update 函數,并當 factory 中創建的控件當作函數參數傳入,此時,就可以對該控件進行設值等操作了。

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

@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 回調中,引用的任何 State 狀態對象,只要狀態對象發生變化后,都會引起 update 回調方法再次執行,類似于 reComposition。

11.4 如何在 Compose 中使用傳統 View System 中的 Theme

如果想要在 Compose 中使用傳統 View System 中的 Theme,需要使用 compose-theme-adapter 庫,該庫可以自動將 style 文件中的主題轉換成 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 如何在傳統的 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 中使用傳統的 View

動態創建傳統 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 的作用:用于創建由傳統 View System 所構建的布局。只會被執行一次。

update 的作用:用于界面每次 Recompose 的時候刷新傳統 View System 所構建的布局。會拿到一個在 factory 過程中生成的 View 引用對象,來進行操作。

image

11.7 Compose 中內部數據與 Compose 外部數據的交互

Compose 內部使用外部非 State 數據

  1. LiveData 數據更新觸發 Recompose(Compose 使用外部數據)

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

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

    Text(text = "$num")
}
  1. 協程 Flow 發送數據觸發 Recompose(Compose 使用外部數據)
// 外部數據
val flowOjb = flow { emit(1) }
setContent {
    // 內部數據
    val num = flowOjb.collectAsState(initial = 0)
    Text(text = "$num")
}

Compose 外部實現使用 Compose 內部數據

在 Compose 的內部數據的主要表現形式是:State<T>,這個對象是無法轉換成其它對象(如:LiveData)的,所以,外部實現是無法使用 Compose 中的數據的。

解決方法:如果外部數據需要得到 Compose 內部數據的話,在一開始設計的時候,就需要將該數據結構定義在外部(如:LiveData)。

12. Intrinsic 固有特性測量

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

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

@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 優化

  1. 無參數的 Composable 函數,在 Recompose 時,不會再次執行函數內的代碼
setContent {
        var name by remember { mutableStateOf("allen") }

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

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

上面的代碼中,點擊了按鈕后,會導致 recompose 操作,但是 ComposableMethodWithoutParams 中的 log 并沒有執行,說明,當前函數中代碼并沒有執行。

  1. 帶參數的 Composable 函數,在 Recompose 時,如果所有參數都沒有發生改變,也不會再次執行函數內的代碼
var flag = 1
        setContent {
            var name by remember { mutableStateOf("allen") }

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

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

            Column {
                Button(onClick = {
                    name = "amy"
                    user = User("allen")
                }) {
                    Text(text = "更改文字內容")
                }
                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 函數中的 log 并沒有打印,說明使用的是 ==(equals)來進行判斷的。

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

為什么對于使用 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 函數不會被 Recompose。

也就是說,showUser 函數中引用的還是 user1,但是 currentUser 已經指向了 User2,當后面如果對 user2 進行了修改,如果 showUser 里面由于某種原因被 Recompose 了,而不是通過外部導致的 Recompose 的話,由于 showUser 函數中的值引用的還是 user1,而不會更新,這樣就與我們想要的結果不符了。

如果想要對使用了 var 修辭的類也使用 ===(結構性相等)的話,可以使用 @Stable 關鍵字對類進行修辭。當然,上面可能會出現的問題,就需要碼農自己來保證了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容