<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Jetpack Compose(4)——重組

      上一篇文章講了 Compose 中狀態(tài)管理的基礎(chǔ)知識,本文講解 Compose 中狀重組的相關(guān)知識。

      一、狀態(tài)變化

      1.1 狀態(tài)變化是什么

      根據(jù)上篇文章的講解,在 Compose 我們使用 State 來聲明一個(gè)狀態(tài),當(dāng)狀態(tài)發(fā)生變化時(shí),則會觸發(fā)重組。那么狀態(tài)變化是指什么呢?
      下面我們來看一個(gè)例子:

      @Composable
      fun NumList() {
          val num by remember {
              mutableStateOf(mutableListOf(1, 2, 3))
          }
          Column {
              Button(onClick = {
                  num += (num.last() + 1)
                  Log.d("sharpcj", "num: $num")
              }) {
                  Text(text = "click to add one")
              }
              num.forEach {
                  Text(text = "item --> $it")
              }
          }
      }
      

      這段代碼中,我們定義了一個(gè) State ,其包裹的類型是 MutableList, 并且每次點(diǎn)擊,我們就給該 mutableList 增加一個(gè)元素。運(yùn)行一下:

      我們點(diǎn)擊了按鈕,界面并沒有發(fā)生變化,但是,從日志看到,每次點(diǎn)擊后,list 中的元素的確增加了一個(gè)。

      2024-03-18 20:51:41.472 12574-12574 sharpcj                 com.sharpcj.hellocompose             D  num: [1, 2, 3, 4]
      2024-03-18 20:51:42.411 12574-12574 sharpcj                 com.sharpcj.hellocompose             D  num: [1, 2, 3, 4, 5]
      2024-03-18 20:51:43.347 12574-12574 sharpcj                 com.sharpcj.hellocompose             D  num: [1, 2, 3, 4, 5, 6]
      

      原因是什么呢?其實(shí)狀態(tài)發(fā)生變化,實(shí)際上指的是 State 包裹的對象,進(jìn)行 equals 比較,如果不相等,則認(rèn)為狀態(tài)變化,否則認(rèn)為沒有發(fā)生變化。所以這里就解釋得通了,我們雖然在點(diǎn)擊按鈕后,給 mutableList 增加了元素,但是 mutableList 在進(jìn)行前后比較時(shí),比較的是其引用,對象的引用并沒有發(fā)生變化,所以沒有發(fā)生重組。【這里結(jié)論并不準(zhǔn)確,下面穩(wěn)定類型詳細(xì)解釋說】
      那為了讓其發(fā)生重組,我們稍作修改,每次點(diǎn)擊按鈕時(shí),創(chuàng)建一個(gè)新的 list,然后賦值,看看是不是我們所期待的結(jié)果。

      @Composable
      fun NumList() {
          var num by remember {
              mutableStateOf(mutableListOf(1, 2, 3))
          }
          Column {
              Button(onClick = {
                  val num1 = num.toMutableList()
                  num1 += (num1.last() + 1)
                  num = num1
                  Log.d("sharpcj", "num: $num")
              }) {
                  Text(text = "click to add one")
              }
              num.forEach {
                  Text(text = "item --> $it")
              }
          }
      }
      

      再次運(yùn)行程序:

      結(jié)果符合我們的預(yù)期。那對于 List 類型的數(shù)據(jù)對象,每次狀態(tài)發(fā)生變化,我們創(chuàng)建了一個(gè)新對象,這樣在進(jìn)行 equals 比較時(shí),必定不相等,則會觸發(fā)重組。

      1.2 mutableStateListOf 和 mutableStateMapOf

      上面的問題,我們雖然接解決了, 但是寫法不夠優(yōu)雅,其實(shí) Compose 給我們提供了一個(gè)函數(shù) mutableStateListOf 來解決這類問題,我們看看這個(gè)函數(shù)怎么用,改寫上面的例子

      @Composable
      fun NumList() {
          val num = remember {
              mutableStateListOf(1, 2, 3)
          }
          Column {
              Button(onClick = {
                  num += (num.last() + 1)
                  Log.d("sharpcj", "num: $num")
              }) {
                  Text(text = "click to add one")
              }
              num.forEach {
                  Text(text = "item --> $it")
              }
          }
      }
      

      這樣就可以滿足我們的需求。 mutableStateListOf 返回了一個(gè)可感知內(nèi)部數(shù)據(jù)變化的 SnapshotStateList<T>, 它的內(nèi)部的實(shí)現(xiàn)為了保證不變性,仍然是拷貝元素,只不過它用了更加高效的實(shí)現(xiàn),比我們單純用toMutableList要高效得多。
      由于 SnapshotStateList 繼承了 MutableList 接口,使得 MutableList 中定義的方法,依然可以使用。
      同理,對于 Map 類型的對象, Compose 中提供了 mutableStateMapOf 方法,可以更優(yōu)雅,更高效地進(jìn)行處理。

      思考如下問題:
      假如我定義了一個(gè)類型:data class Hero(var name: String, var age: Int), 然后使用 mutableStateListOf 定義了狀態(tài),其中的元素是自定義的類型 Hero, 當(dāng)改變 Hero 的屬性時(shí), 與該狀態(tài)相關(guān)的 Composable 是否會發(fā)生重組?

      data class Hero(var name: String, var age: Int)
      
      @Composable
      fun HeroInfo() {
          val heroList = remember {
              mutableStateListOf(Hero(name = "安其拉", age = 18), Hero(name = "魯班", age = 19))
          }
      
          Column {
              Button(onClick = {
                  heroList[0].name = "DaJi"
                  heroList[0].age = 22
              }) {
                  Text(text = "test click")
              }
      
              heroList.forEach {
                  Text(text = "student, name: ${it.name}, age: ${it.age} ")
              }
          }
      }
      

      二、重組的特性

      2.1 Composable 重組是智能的

      傳統(tǒng) View 體系通過修改 View 的私有屬性來改變 UI, Compose 則通過重組刷新 UI。 Compose 的重組非常“智能”,當(dāng)重組發(fā)生時(shí),只有狀態(tài)發(fā)生更新的 Composable 才會參與重組,沒有變化的 Composable 會跳過本次重組。

      @Composable
      fun KingHonour() {
          Column {
              var name by remember {
                  mutableStateOf("周瑜")
              }
              Button(onClick = {
                  name = "小喬"
              }) {
                  Text(text = "改名")
              }
              Text(text = "魯班")
              Text(text = name)
      
          }
      }
      

      該例子中,點(diǎn)擊按鈕,改變了 name 的值,觸發(fā)重組,Button 和 Text(text = "魯班"),并不依賴該狀態(tài),雖然在重組時(shí)被調(diào)用了,但是在運(yùn)行時(shí)并不會真正的執(zhí)行。因?yàn)槠鋮?shù)沒有變化,Compose 編譯器會在編譯器插入相關(guān)的比較代碼。只有最后一個(gè) Text 依賴該狀態(tài),會參與真正的重組。

      2.2 Composable 會以任意順序執(zhí)行

      @Composable
      fun Navi() {
          Box {
              FirstScreen()
              SecondScreen()
              ThirdScreen()
          }
      }
      

      在代碼中出現(xiàn)多個(gè) Composable 函數(shù)時(shí),它們并不一定按照在代碼中出現(xiàn)的順序執(zhí)行,比如在一個(gè) Box 中,處于前景的 UI 具有較高優(yōu)先級。所以不要試圖通過外部變量與其它 Composable 產(chǎn)生關(guān)聯(lián)。

      2.3 Composable 會并發(fā)執(zhí)行

      重組中的 Composable 并不一定執(zhí)行在 UI 線程,它們可以在后臺線程中并發(fā)執(zhí)行,這樣利于發(fā)揮多喝處理器的優(yōu)勢。正因?yàn)榇耍残枰紤]線程安全問題。

      2.4 Composable 會反復(fù)執(zhí)行

      除了重組會造成 Composable 的再次執(zhí)行外,在動畫等場景中每一幀的變化都可能引起 Composable 的執(zhí)行。因此 Composable 可能在短時(shí)間內(nèi)多次執(zhí)行。

      2.5 Composable 的執(zhí)行是“樂觀”的

      所謂“樂觀”是指 Composable 最終會依據(jù)最新的狀態(tài)正確地完成重組。在某些場景下,狀態(tài)可能會連續(xù)變化,可能會導(dǎo)致中間態(tài)的重組在執(zhí)行時(shí)被打斷,新的重組會插進(jìn)來,對于被打斷的重組,Compose 不會將執(zhí)行一半的結(jié)果反應(yīng)到視圖樹上,因?yàn)樽詈笠淮蔚臓顟B(tài)總歸是正確的。

      三、重組范圍

      原則:重組范圍最小化。
      只有受到了 State 變化影響的代碼塊,才會參與到重組,不依賴 State 變化的代碼則不參與重組。
      如何確定重組范圍呢?修改上面的例子:

      @Composable
      fun RecompositionTest() {
          Column {
              Box {
                  Log.i("sharpcj", "RecompositionTest - 1")
                  Column {
                      Log.i("sharpcj", "RecompositionTest - 2")
                      var name by remember {
                          mutableStateOf("周瑜")
                      }
                      Button(onClick = {
                          name = "小喬"
                      }) {
                          Log.i("sharpcj", "RecompositionTest - 3")
                          Text(text = "改名")
                      }
                      Text(text = "魯班")
                      Text(text = name)
                  }
              }
              Box {
                  Log.i("sharpcj", "RecompositionTest - 4")
              }
              Card {
                  Log.i("sharpcj", "RecompositionTest - 5")
              }
          }
      }
      

      運(yùn)行,第一次我們看到,打印了如下日志:

      2024-03-22 15:36:15.303 19870-19870 sharpcj                 com.sharpcj.hellocompose             I  RecompositionTest - 1
      2024-03-22 15:36:15.305 19870-19870 sharpcj                 com.sharpcj.hellocompose             I  RecompositionTest - 2
      2024-03-22 15:36:15.326 19870-19870 sharpcj                 com.sharpcj.hellocompose             I  RecompositionTest - 3
      2024-03-22 15:36:15.337 19870-19870 sharpcj                 com.sharpcj.hellocompose             I  RecompositionTest - 4
      2024-03-22 15:36:15.344 19870-19870 sharpcj                 com.sharpcj.hellocompose             I  RecompositionTest - 5
      

      這是正常的,每個(gè)控件范圍內(nèi)都執(zhí)行了。我們點(diǎn)擊,button, 改變了 name 狀態(tài)。打印如下日志:

      2024-03-22 15:37:48.480 19870-19870 sharpcj                 com.sharpcj.hellocompose             I  RecompositionTest - 1
      2024-03-22 15:37:48.480 19870-19870 sharpcj                 com.sharpcj.hellocompose             I  RecompositionTest - 2
      2024-03-22 15:37:48.491 19870-19870 sharpcj                 com.sharpcj.hellocompose             I  RecompositionTest - 4
      

      首先我們 name 這個(gè)狀態(tài)影響的組件時(shí) Text,它所在的作用域應(yīng)該是 Column 內(nèi)部。打印 RecompositionTest - 2 好理解,可為什么連 Column 的上一級作用域 Box 也被調(diào)用了,并且連該 Box 的統(tǒng)計(jì) Box 也被調(diào)用了,但是 Card 卻又沒有被調(diào)用。這個(gè)好像與上面說的原則相悖。其實(shí)不然,我們看看 ColumnBoxCard 源碼就清楚了。

      @Composable
      inline fun Column(
          modifier: Modifier = Modifier,
          verticalArrangement: Arrangement.Vertical = Arrangement.Top,
          horizontalAlignment: Alignment.Horizontal = Alignment.Start,
          content: @Composable ColumnScope.() -> Unit
      ) {
          val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
          Layout(
              content = { ColumnScopeInstance.content() },
              measurePolicy = measurePolicy,
              modifier = modifier
          )
      }
      
      @Composable
      inline fun Box(
          modifier: Modifier = Modifier,
          contentAlignment: Alignment = Alignment.TopStart,
          propagateMinConstraints: Boolean = false,
          content: @Composable BoxScope.() -> Unit
      ) {
          val measurePolicy = rememberBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
          Layout(
              content = { BoxScopeInstance.content() },
              measurePolicy = measurePolicy,
              modifier = modifier
          )
      }
      
      @Composable
      fun Card(
          modifier: Modifier = Modifier,
          shape: Shape = CardDefaults.shape,
          colors: CardColors = CardDefaults.cardColors(),
          elevation: CardElevation = CardDefaults.cardElevation(),
          border: BorderStroke? = null,
          content: @Composable ColumnScope.() -> Unit
      ) {
          Surface(
              modifier = modifier,
              shape = shape,
              color = colors.containerColor(enabled = true),
              contentColor = colors.contentColor(enabled = true),
              tonalElevation = elevation.tonalElevation(enabled = true),
              shadowElevation = elevation.shadowElevation(enabled = true, interactionSource = null).value,
              border = border,
          ) {
              Column(content = content)
          }
      }
      

      不難發(fā)現(xiàn), Column 和 Box 都是使用 inline 修飾的。
      最后簡單了解下 Compose 重組的底層原理。
      經(jīng)過 Compose 編譯器處理后的 Composable 代碼在對 State 進(jìn)行讀取時(shí),能夠自動建立關(guān)聯(lián),在運(yùn)行過程中,當(dāng) State 變化時(shí), Compose 會找到關(guān)聯(lián)的代碼塊標(biāo)記為 Invalid, 在下一渲染幀到來之前,Compose 觸發(fā)重組并執(zhí)行 invalid 代碼塊, invalid 代碼塊即下一次重組的范圍。能夠被標(biāo)記為 Invalid 的代碼必須是非 inline 且無返回值的 Composable 函數(shù)或 lambda。

      需要注意的是,重組的范圍,與只能跳過并不沖突,確定了重組范圍,會調(diào)用對應(yīng)的組件代碼,但是當(dāng)參數(shù)沒有變化時(shí),在運(yùn)行時(shí)不會真正執(zhí)行,會跳過本次重組。

      四、參數(shù)類型的穩(wěn)定性

      4.1 穩(wěn)定和不穩(wěn)定

      前面,Composable 狀態(tài)變化觸發(fā)重組,狀態(tài)變化基于 equals 比較結(jié)果,這是不準(zhǔn)確的。準(zhǔn)確地說:只有當(dāng)比較的狀態(tài)對象,是穩(wěn)定的,才能通過 equals 比較結(jié)果確定是否重組。什么叫穩(wěn)定的?還是看一個(gè)例子:

      data class Hero(var name: String)
      
      val shangDan = Hero("呂布")
      
      @Composable
      fun StableTest() {
          var greeting by remember {
              mutableStateOf("hello, 魯班")
          }
      
          Column {
              Log.i("sharpcj", "invoke --> 1")
              Text(text = greeting)
              Button(onClick = {
                  greeting = "hello, 魯班大師"
              }) {
                  Text(text = "搞錯(cuò)了,是魯班大師")
              }
              ShangDan(shangDan)
          }
      }
      
      @Composable
      fun ShangDan(hero: Hero) {
          Log.i("sharpcj", "invoke --> 2")
          Text(text = hero.name)
      }
      

      運(yùn)行一下,打印

      2024-03-22 17:07:50.248 26973-26973 sharpcj                 com.sharpcj.hellocompose             I  invoke --> 1
      2024-03-22 17:07:50.272 26973-26973 sharpcj                 com.sharpcj.hellocompose             I  invoke --> 2
      

      點(diǎn)擊 Button,再次看到打印:

      2024-03-22 17:07:53.182 26973-26973 sharpcj                 com.sharpcj.hellocompose             I  invoke --> 1
      2024-03-22 17:07:53.191 26973-26973 sharpcj                 com.sharpcj.hellocompose             I  invoke --> 2
      

      問題來了, Shangdan 這個(gè)組件依賴的只依賴一個(gè)參數(shù),并且參數(shù)也沒有改變,為什么確在重組過程中被調(diào)用了呢?
      接下來,我們將 Hero 這個(gè)類做點(diǎn)改變,將其屬性聲明由 var 變成 val

      data class Hero(val name: String)
      

      再次運(yùn)行,

      2024-03-22 17:35:41.435 28561-28561 sharpcj                 com.sharpcj.hellocompose             I  invoke --> 1
      2024-03-22 17:35:41.458 28561-28561 sharpcj                 com.sharpcj.hellocompose             I  invoke --> 2
      

      點(diǎn)擊button:

      2024-03-22 17:35:47.790 28561-28561 sharpcj                 com.sharpcj.hellocompose             I  invoke --> 1
      

      這次,Shangdan 這個(gè) Composable 沒有參與重組了。為什么會這樣呢?

      其實(shí)是在因?yàn)榇饲埃?var 聲明 Hero 類的屬性時(shí),Hero 類被 Compose 編譯器認(rèn)為是不穩(wěn)定類型。即有可能,我們傳入的參數(shù)引用沒有變化,但是屬性被修改過了,而 UI 又確實(shí)需要顯示修改后的最新值。而當(dāng)用 val 聲明屬性了,Compose 編譯器認(rèn)為該對象,只要對象引用不要變,那么這個(gè)對象就不會發(fā)生變化,自然 UI 也就不會發(fā)生變化,所以就跳過了這次重組。
      常用的基本數(shù)據(jù)類型以及函數(shù)類型(lambda)都可以稱得上是穩(wěn)定類型,它們都不可變。反之,如果狀態(tài)是可變的,那么比較 equals 結(jié)果將不再可信。在遇到不穩(wěn)定類型時(shí),Compose 的抉擇是寧愿犧牲一些性能,也總好過顯示錯(cuò)誤的 UI。

      4.2 @Stable 和 @Immutable

      上面講了穩(wěn)定與不穩(wěn)定的概念,然而實(shí)際開發(fā)中,我們經(jīng)常會根據(jù)業(yè)務(wù)自定義 data class, 難道用了 Compose, 雖然 Kotlin 編碼規(guī)范,強(qiáng)調(diào)盡量使用 val, 但是還是要根據(jù)實(shí)際業(yè)務(wù),使用 var 來定義可變屬性。對于這種類型,我們可以為其添加 @Stable 注解,讓編譯器將其視為穩(wěn)定類型。從而發(fā)揮智能重組的作用,提升重組的性能。

      @Stable
      data class Hero(var name: String)
      

      這樣,Hero 即便使用 var 聲明屬性,它作為參數(shù)傳入 Composable 中,只要對象引用沒變,都不會觸發(fā)重組。所以具體什么時(shí)候使用該注解,還需要根據(jù)需求靈活使用。

      除了 @Stable,Compose 還提供了另一個(gè)類似的注解 @Immutable,與 @Stable 不同的是,@Immutable 用來修飾的類型應(yīng)該是完全不可變的。而 @Stable 可以用在函數(shù)、屬性等更多場景。使用起來更加方便,由于功能疊加,未來 @Immutable 有可能會被移除,建議優(yōu)先使用 @Stable

      最后總結(jié)一下:本文接著上篇文章的狀態(tài),講解了重組的一些特性,如何確定重組的范圍,以及重組的中的類型穩(wěn)定性概念,以及如何提升非穩(wěn)定類型在重組過程中的性能。
      下一篇文章將會講解 Composable 的生命周期以及重組的副作用函數(shù)。

      posted @ 2024-04-03 22:55  SharpCJ  閱讀(1820)  評論(2)    收藏  舉報(bào)
      主站蜘蛛池模板: 东京热一区二区三区在线| 在线国产精品中文字幕| 大地资源免费视频观看| 色国产视频| 亚洲av永久无码精品水牛影视| 五月丁香色综合久久4438| 亚洲伊人久久大香线蕉| 中文字幕国产精品av| 漂亮人妻被中出中文字幕| 情欲少妇人妻100篇| 亚洲国产美女精品久久久| 太深太粗太爽太猛了视频| 国产精品成人久久电影| 四虎成人高清永久免费看| 国产片一区二区三区视频| 久久精品久久电影免费理论片| 亚洲欧美自偷自拍视频图片| 老司机精品影院一区二区三区| 天堂av色综合久久天堂| 波多野结衣在线精品视频| 国产成人午夜福利精品| 苍井空一区二区三区在线观看| 国产精品久久露脸蜜臀| 亚洲色大成网站www久久九九| 天天燥日日燥| 2021国产精品视频网站| 国产成人精品一区二区三区无码| 亚洲中文一区二区av| 国产精一区二区黑人巨大| 成人国产精品一区二区网站公司| 肉色丝袜足j视频国产| 日本欧美大码a在线观看| 国产色悠悠在线免费观看| 日本久久高清一区二区三区毛片| 亚洲高潮喷水无码AV电影| 日本不卡码一区二区三区| 免费人成视频在线观看网站| 亚洲欧美偷国产日韩| 亚洲av无在线播放中文| 太保市| 91麻豆精品国产91久|