色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Jetpack Compose基礎知識科普

谷歌開發者 ? 來源:Android 開發者 ? 作者:Android 開發者 ? 2022-04-02 13:38 ? 次閱讀

Jetpack Compose 是用于構建原生 Android 界面的新工具包。它可簡化并加快 Android 上的界面開發,使用更少的代碼、強大的工具和直觀的 Kotlin API,快速讓應用生動而精彩。Compose 使用全新的組件——可組合項 (Composable) 來布局界面,使用修飾符 (Modifier) 來配置可組合項。

本文會為您講解由可組合項和修飾符提供支持的組合布局模型,并深入探究其背后的工作原理以及它們的功能,讓您更好地了解所用布局和修飾符的工作方式,和應如何以及在何時構建自定義布局,從而實現滿足確切應用需求的設計。

布局模型

Compose 布局系統的目標是提供易于創建的布局,尤其是自定義布局。這要求布局系統具備強大的功能,使開發者能創建應用所需的任何布局,并且讓布局具備優異的性能。接下來,我們來看看 Compose 的布局模型是如何實現這些目標的。

Jetpack Compose 可將狀態轉換為界面,這個過程分為三步:組合、布局、繪制。組合階段執行可組合函數,這些函數可以生成界面,從而創建界面樹。例如,下圖中的 SearchResult 函數會生成對應的界面樹:

Jetpack Compose基礎知識科普

可組合函數生成對應的界面樹

可組合項中可以包含邏輯和控制流,因此可以根據不同的狀態生成不同的界面樹。在布局階段,Compose 會遍歷界面樹,測量界面的各個部分,并將每個部分放置在屏幕 2D 空間中。也就是說,每個節點決定了其各自的寬度、高度以及 x 和 y 坐標。在繪制階段,Compose 將再次遍歷這棵界面樹,并渲染所有元素。 本文將深入探討布局階段。布局階段又細分為兩個階段: 測量和放置。這相當于 View 系統中的 onMeasure 和 onLayout。但在 Compose 中,這兩個階段會交叉進行,因此我們把它看成一個布局階段。將界面樹中每個節點布局的過程分為三步:每個節點必須測量自身的所有子節點,再決定自身的尺寸,然后放置其子節點。如下例,單遍即可對整個界面樹完成布局。

Jetpack Compose基礎知識科普

布局過程

其過程簡述如下:

  1. 測量根布局 Row;
  2. Row 測量它的第一個子節點 Image;
  3. 由于 Image 是一個不含子節點的葉子節點,它會測量自身尺寸并加以報告,還會返回有關如何放置其子節點的指令。Image 的葉子節點通常是空節點,但所有布局都會在設置其尺寸的同時返回這些放置指令;
  4. Row 測量它的第二個子節點 Column;
  5. Column 測量其子節點,首先測量第一個子節點 Text;
  6. Text 測量并報告其尺寸以及放置指令;
  7. Column 測量第二個子節點 Text;
  8. Text 測量并報告其尺寸以及放置指令;
  9. Column 測量完其子節點,可以決定其自身的尺寸和放置邏輯;
  10. Row 根據其所有子節點的測量結果決定其自身尺寸和放置指令。
測量完所有元素的尺寸后,將再次遍歷界面樹,并且會在放置階段執行所有放置指令。

Layout 可組合項

我們已經了解這個過程涉及的步驟,接下來看一下它的實現方式。先看看組合階段,我們采用 Row、Column、Text 等更高級別的可組合項來表示界面樹,每個高級別的可組合項實際上都是由低級別的可組合項構建而成。以 Text 為例,可以發現它由若干更低級別的基礎構建塊組成,而這些可組合項都會包含一個或多個 Layout 可組合項。
Jetpack Compose基礎知識科普

每個可組合項都包含一個或多個 Layout

Layout 可組合項是 Compose 界面的基礎構建塊,它會生成 LayoutNode。在 Compose 中,界面樹,或者說組合 (composition) 是一棵 LayoutNode 樹。以下是 Layout 可組合項的函數簽名:
@Composablefun Layout(    content: @Composable () -> Unit,    modifier: Modifier = Modifier,    measurePolicy: MeasurePolicy) {}
Layout 可組合項的函數簽名

其中,content 是可以容納任何子可組合項的槽位,出于布局需要,content 中也會包含子 Layout。modifier 參數所指定的修飾符將應用于該布局,這在下文中會詳細介紹。measurePolicy 參數是 MeasurePolicy 類型,它是一個函數式接口,指定了布局測量和放置項目的方式。一般情況下,如需實現自定義布局的行為,您要在代碼中實現該函數式接口:
@Composablefun MyCustomLayout(    modifier: Modifier = Modifier,    content: @Composable () -> Unit) {    Layout(         modifier = modifier,         content = content    ) { measurables: List,         constraints: Constraints ->        // TODO 測量和放置項目   }}
實現 MeasurePolicy 函數式接口

在 MyCustomLayout 可組合項中,我們調用 Layout 函數并以 Trailing Lambda 的形式提供 MeasurePolicy 作為參數,從而實現所需的 measure 函數。該函數接受一個 Constraints 對象來告知 Layout 它的尺寸限制。Constraints 是一個簡單類,用于限制 Layout 的最大和最小寬度與高度:

class Constraints {    val minWidth: Int    val maxWidth: Int    val minHeight: Int    val maxHeight: Int}

Constraints

measure 函數還會接受 List 作為參數,這表示的是傳入的子元素。Measurable 類型會公開用于測量項目的函數。如前所述,布局每個元素需要三步:每個元素必須測量其所有子元素,并以此判斷自身尺寸,再放置其子元素。其代碼實現如下:

@Composablefun MyCustomLayout(    content: @Composable () -> Unit,    modifier: Modifier = Modifier) {    Layout(         modifier = modifier,         content = content    ) { measurables: List,         constraints: Constraints ->        // placeables 是經過測量的子元素,它擁有自身的尺寸值        val placeables = measurables.map { measurable ->            // 測量所有子元素,這里不編寫任何自定義測量邏輯,只是簡單地            // 調用 Measurable 的 measure 函數并傳入 constraints            measurable.measure(constraints)        }        val width = // 根據 placeables 計算得出        val height = // 根據 placeables 計算得出        // 報告所需的尺寸        layout (width, height) {            placeables.foreach { placeable ->                // 通過遍歷將每個項目放置到最終的預期位置                placeable.place(                    x = …                    y = …                )            }        }   }}
布局每個元素的代碼示例

上述代碼中使用了 Placeable 的 place 函數,它還有一個 placeRelative 函數可用于從右到左的語言設置中,當使用該函數時,它會自動對坐標進行水平鏡像。 請注意,API 在設計上可阻止您嘗試放置未經測量的元素,place 函數只適用于 Placeable,也就是 measure 函數的返回值。在 View 系統中,調用 onMeasure 以及 onLayout 的時機由您決定,而且調用順序沒有強制要求,但這會產生一些微妙的 bug 以及行為上的差異。

自定義布局示例

MyColumn 示例


Jetpack Compose基礎知識科普

Column

Compose 提供一個 Column 組件用于縱向排布元素。為了理解這個組件背后的工作方式及其使用 Layout 可組合項的方式,我們來實現自己的一個 Column。暫且將其命名為 MyColumn,其實現代碼如下:
@Composablefun MyColumn(    modifier: Modifier = Modifier,    content: @Composable () -> Unit) {    Layout(         modifier = modifier,         content = content    ) { measurables, constraints ->        // 測量每個項目并將其轉換為 Placeable        val placeables = measurables.map { measurable ->            measurable.measure(constraints)        }        // Column 的高度是所有項目所測得高度之和        val height = placeables.sumOf { it.height }        // Column 的寬度則為內部所含最寬項目的寬度        val width = placeables.maxOf { it.width }        // 報告所需的尺寸        layout (width, height) {            // 通過跟蹤 y 坐標放置每個項目            var y = 0            placeables.forEach { placeable ->                placeable.placeRelative(x = 0, y = y)                // 按照所放置項目的高度增加 y 坐標值                y += placeable.height            }        }    }}

自定義 Column

VerticalGrid 示例

Jetpack Compose基礎知識科普

VerticalGrid

我們再來看另一個示例:構建常規網格。其部分代碼實現如下:
@Composablefun VerticalGrid(    modifier: Modifier = Modifier,    columns: Int = 2,    content: @Composable () -> Unit) {    Layout(        content = content,         modifier = modifier    ) { measurables, constraints ->        val itemWidth = constraints.maxWidth / columns        // 通過 copy 函數保留傳遞下來的高度約束,但設置確定的寬度約束        val itemConstraints = constraints.copy (            minWidth = itemWidth,            maxWidth = itemWidth,        )                // 使用這些約束測量每個項目并將其轉換為 Placeable        val placeables = measurables.map { it.measure(itemConstraints) }    }}

自定義 VerticalGrid

在該示例中,我們通過 copy 函數創建了新的約束。這種為子節點創建新約束的概念就是實現自定義測量邏輯的方式。創建不同約束來測量子節點的能力是此模型的關鍵,父節點與子節點之間并沒有協商機制,父節點會以 Constraints 的形式傳遞其允許子節點的尺寸范圍,只要子節點從該范圍中選擇了其尺寸,父節點必須接受并處理子節點。

這種設計的優點在于我們可以單遍測量整棵界面樹,并且禁止執行多個測量循環。這是 View 系統中存在的問題,嵌套結構執行多遍測量過程可能會讓葉子視圖上的測量次數翻倍,Compose 的設計能夠防止發生這種情況。實際上,如果您對某個項目進行兩次測量,Compose 會拋出異常:

Jetpack Compose基礎知識科普

重復測量某個項目時 Compose 會拋出異常

布局動畫示例

由于具備更強的性能保證,Compose 提供了新的可能性,例如為布局添加動畫。Layout composable 不僅可以創建通用布局,還能創建出符合應用設計需求的專用布局。以 Jetsnack 應用中的自定義底部導航為例,在該設計中,如果某項目被選中,則顯示標簽;如果未被選中,則只顯示圖標。而且,設計還需要讓項目的尺寸和位置根據當前選擇狀態執行動畫。Jetpack Compose基礎知識科普

Jetsnack 應用中的自定義底部導航

我們可以使用自定義布局來實現該設計,從而對布局變化的動畫處理進行精確控制:
@Composablefun BottomNavItem(    icon: @Composable BoxScope.() -> Unit,    text: @Composable BoxScope.() -> Unit,    @FloatRange(from = 0.0, to = 1.0) animationProgress: Float) {    Layout(        content = {            // 將 icon 和 text 包裹在 Box 中            // 這種做法能讓我們為每個項目設置 layoutId            Box(                modifier = Modifier.layoutId(“icon”)                content = icon            )            Box(                modifier = Modifier.layoutId(“text”)                content = text            )        }    ) { measurables, constraints ->        // 通過 layoutId 識別對應的 Measurable,比依賴項目的順序更可靠        val iconPlaceable = measurables.first {it.layoutId == “icon” }.measure(constraints)        val textPlaceable = measurables.first {it.layoutId == “text” }.measure(constraints)         // 將放置邏輯提取到另一個函數中以提高代碼可讀性        placeTextAndIcon(            textPlaceable,            iconPlaceable,            constraints.maxWidth,            constraints.maxHeight,            animationProgress        )    }} fun MeasureScope.placeTextAndIcon(    textPlaceable: Placeable,    iconPlaceable: Placeable,    width: Int,    height: Int,    @FloatRange(from = 0.0, to = 1.0) animationProgress: Float): MeasureResult {     // 根據動畫進度值放置文本和圖標    val iconY = (height - iconPlaceable.height) / 2    val textY = (height - textPlaceable.height) / 2     val textWidth = textPlaceable.width * animationProgress    val iconX = (width - textWidth - iconPlaceable.width) / 2    val textX = iconX + iconPlaceable.width     return layout(width, height) {        iconPlaceable.placeRelative(iconX.toInt(), iconY)        if (animationProgress != 0f) {            textPlaceable.placeRelative(textX.toInt(), textY)        }    }}
自定義底部導航

使用自定義布局的時機


希望以上示例能幫助您了解自定義布局的工作方式以及這些布局的應用理念。標準布局強大而靈活,但它們也需要適應很多用例。有時,若您知道具體的實現需求,使用自定義布局可能更加合適。

當您遇到以下場景時,我們推薦使用自定義布局:

  • 難以通過標準布局實現的設計。雖然可以使用足夠多的 Row 和 Column 構建大部分界面,但這種實現方式有時難以維護和升級;

  • 需要非常精確地控制測量和放置邏輯;

  • 需要實現布局動畫。我們正在開發可對放置進行動畫處理的新 API,未來可能不必自行編寫布局就能實現;

  • 需要完全控制性能。下文會詳細介紹這一點。

修飾符

至此,我們了解了 Layout 可組合項以及構建自定義布局的方式。如果您使用 Compose 構建過界面,就會知道修飾符在布局、配置尺寸和位置方面發揮著重要作用。通過前文的示例可以看到,Layout 可組合項接受修飾符鏈作為參數。修飾符會裝飾它們所附加的元素,可以在布局自身的測量和放置操作之前參與測量和放置。接下來我們來看看它的工作原理。

修飾符分很多不同的類型,可以影響不同的行為,例如繪制修飾符 (DrawModifier)、指針輸入修飾符 (PointerInputModifier) 以及焦點修飾符 (FocusModifier)。本文我們將重點介紹布局修飾符 (LayoutModifier),該修飾符提供了一個 measure 方法,該方法的作用與 Layout 可組合項基本相同,不同之處在于,它只作用于單個 Measurable 而不是 List,這是因為修飾符的應用對象是單個項目。在 measure 方法中,修飾符可以修改約束或者實現自定義放置邏輯,就像布局一樣。這表示您并不總是需要編寫自定義布局,如果只想對單個項目執行操作,則可以改用修飾符。

以 padding 修飾符為例,該工廠函數以修飾符鏈為基礎,創建能夠捕獲所需 padding 值的 PaddingModifier 對象。

fun Modifier.padding(all: Dp) =    this.then(PaddingModifier(            start = all,            top = all,            end = all,            bottom = all        )    ) private class PaddingModifier(    val start: Dp = 0.dp,    val top: Dp = 0.dp,    val end: Dp = 0.dp,    val bottom: Dp = 0.dp) : LayoutModifier { override fun MeasureScope.measure(        measurable: Measurable,        constraints: Constraints    ): MeasureResult {        val horizontal = start.roundToPx() + end.roundToPx()        val vertical = top.roundToPx() + bottom.roundToPx()         // 按 padding 尺寸收縮外部約束來修改測量        val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))         val width = constraints.constrainWidth(placeable.width + horizontal)        val height = constraints.constrainHeight(placeable.height + vertical)        return layout(width, height) {                // 按所需的 padding 執行偏移以放置內容                placeable.placeRelative(start.roundToPx(), top.roundToPx())        }    }}
padding 修飾符的實現除了通過上例中的方式覆寫 measure 方法實現測量,您也可以使用 Modifier.layout,在無需創建自定義布局的情況下直接通過修飾符鏈向任意可組合項添加自定義測量和放置邏輯,如下所示:
Box(Modifier            .background(Color.Gray)            .layout { measurable, constraints ->                // 通過修飾符在豎直方向添加 50 像素 padding 的示例                val padding = 50                val placeable = measurable.measure(constraints.offset(vertical = -padding))                layout(placeable.width, placeable.height + padding) {                    placeable.placeRelative(0, padding)                }            }        ) {            Box(Modifier.fillMaxSize().background(Color.DarkGray))}

使用 Modifier.layout 實現布局

雖然 Layout 接受單個 Modifier 參數,該參數會建立一個按順序應用的修飾符鏈。我們通過示例來了解它與布局模型的交互方式。我們將分析下圖修飾符的效果及其工作原理:Jetpack Compose基礎知識科普 ?
修飾符鏈的效果示例

首先,我們為 Box 設置尺寸并將其繪制出來,但這個 Box 放置在了父布局的左上角,我們可以使用 wrapContentSize 修飾符將 Box 居中放置。wrapContentSize 允許內容測量其所需尺寸,然后使用 align 參數放置內容,align 參數的默認值為 Center,因此可以省略這個參數。但我們發現,Box 還是在左上角。這是因為大多數布局都會根據其內容自適應調整尺寸,我們需要讓測量尺寸占據整個空間,以便讓 Box 在空間內居中。因此,我們在 wrapContentSize 前面添加 fillMaxSize 布局修飾符來實現這個效果。eff2ea32-b246-11ec-aa7f-dac502259ad0.gif

修飾符鏈的應用過程
我們來看一下這些修飾符是如何實現此效果的。您可以借助下圖動畫來輔助理解該過程:

f007d3de-b246-11ec-aa7f-dac502259ad0.gif
修飾符鏈的工作原理

假設這個 Box 要放入最大尺寸為 200*300 像素的容器內,容器會將相應的約束傳入修飾符鏈的第一個修飾符中。fillMaxSize 實際上會創建一組新約束,并設置最大和最小寬度與高度,使之等于傳入的最大寬度與高度以便填充到最大值,在本例中是 200*300 像素。這些約束沿著修飾符鏈傳遞以測量下一個元素,wrapContentSize 修飾符會接受這些參數,它會創建新的約束來放寬對傳入約束的限制,從而讓內容測量其所需尺寸,也就是寬 0-200,高 0-300。這看起來只像是對 fillMax 步驟的反操作,但請注意,我們是使用這個修飾符實現項目居中的效果,而不是重設項目的尺寸。這些約束沿著修飾符鏈傳遞到 size 修飾符,該修飾符創建具體尺寸的約束來測量項目,指定尺寸應該正好是 50*50。最后,這些約束傳遞到 Box 的布局,它執行測量并將解析得到的尺寸 (50*50) 返回到修飾符鏈,size 修飾符因此也將其尺寸解析為 50*50,并據此創建放置指令。然后 wrapContent 解析其大小并創建放置指令以居中放置內容。因為 wrapContent 修飾符知道其尺寸為 200*300,而下一個元素的尺寸為 50*50,所以使用居中對齊創建放置指令,以便將內容居中放置。最后,fillMaxSize 解析其尺寸并執行放置操作。

修飾符鏈的執行方式與布局樹的工作方式非常相像,差異在于每個修飾符只有一個子節點,也就是鏈中的下一個元素。約束會向下傳遞,以便后續元素用其測量自身尺寸,然后返回解析得到的尺寸,并創建放置指令。該示例也說明了修飾符順序的重要性。通過使用修飾符對功能進行組合,您可以很輕松地將不同的測量和布局策略組合在一起。

高級功能

接下來將介紹布局模型的一些高級功能,雖然您不一定總是需要這些功能,但它們能夠幫助您構建更高級的功能。 固有特性測量 (Intrinsic Measurement)前文提到過,Compose 使用單遍布局系統。這個說法并不完全正確,布局并不總是能通過單遍操作就得以完成,有時我們也需要了解有關子節點尺寸的信息才能最終確定約束。 以彈出式菜單為例。假設有一個包含五個菜單項的 Column,如下圖所示,它的顯示基本上是正常的,但是可以看到,每個菜單項的尺寸卻不相同。Jetpack Compose基礎知識科普
菜單項的尺寸不相同
我們很容易想到,讓每個菜單項都占用允許的最大尺寸即可
:
Jetpack Compose基礎知識科普
每個菜單項都占有允許的最大尺寸

但這么做也沒能完全解決問題,因為菜單窗口會擴大到其最大尺寸。有效的解決方法是使用最大固有寬度來確定尺寸:Jetpack Compose基礎知識科普

使用最大固有寬度來確定尺寸

這里確定了 Column 會盡力為每個子節點提供所需的空間,對 Text 而言,其寬度是單行渲染全部文本所需的寬度。在確定固有尺寸后,將使用這些值設置 Column 的尺寸,然后,子節點就可以填充 Column 的寬度了。

如果使用最小值而非最大值,又會發生什么呢?

Jetpack Compose基礎知識科普
使用最小固有寬度來確定尺寸

它將確定 Column 會使用子節點的最小尺寸,而 Text 的最小固有寬度是每行一個詞時的寬度。因此,我們最后得到一個按詞換行的菜單。 如需詳細了解固有特性測量,請參閱 Jetpack Compose 中的布局 Codelab 中的 "固有特性"部分。

ParentData

到目前為止,我們看到的修飾符都是通用修飾符,也就是說,它們可以應用于任何可組合項。有時,您的布局提供的一些行為可能需要從子節點獲得一些信息,這便要用到 ParentDataModifier

我們回到前面那個在父節點中居中放置藍色 Box 的示例。這一次,我們將這個 Box 放在另一個 Box 中。Box 中的內容在一個稱為 BoxScope 的接收器作用域內排布。BoxScope 定義了只在 Box 內可用的修飾符,它提供了一個名為 Align 的修飾符。這個修飾符剛好能夠提供我們要應用到藍色 Box 的功能。因此,如果我們知道藍色 Box 位于另一個 Box 內,就可以改用 Align 修飾符來定位它。

Jetpack Compose基礎知識科普

在 BoxScope 中可以改用 Align 修飾符來定位內容

Align 是一個 ParentDataModifier 而不是我們之前看到的那種布局修飾符,因為它只是向其父節點傳遞一些信息,所以如果不在 Box 中,該修飾符便不可用。它包含的信息將提供給父 Box,以供其設置子布局。

您也可以為自己的自定義布局編寫 ParentDataModifier,從而允許子節點向父節點告知一些信息,以供父節點在布局時使用。 對齊線 (Alignment Lines)我們可以使用對齊線根據布局頂部、底部或中心以外的標準來設置對齊。最常用的對齊線是文本基線。假設需要實現這樣一個設計:Jetpack Compose基礎知識科普

需要實現設計圖中的圖標和文本對齊



我們很自然就能想到這樣來實現它:
Row {    Icon(modifier = Modifier        .size(10. dp)        .align(Alignment.CenterVertically)    )    Text(modifier = Modifier        .padding(start = 8.dp)        .align(Alignment.CenterVertically)    )}

有問題的對齊實現

仔細觀察,會發現圖標并沒有像設計稿那樣對齊在文本的基線上。
Jetpack Compose基礎知識科普
圖標和文本居中對齊,圖標底部沒有落在文本基線上

我們可以通過以下代碼進行修復:

Row {    Icon(modifier = Modifier        .size(10. dp)        .alignBy { it.measuredHeight }    )    Text(modifier = Modifier        .padding(start = 8.dp)        .alignByBaseline()    )}
正確的對齊實現

首先,對 Text 使用 alignByBaseline 修飾符。而圖標既沒有基線,也沒有其他對齊線,我們可以使用 alignBy 修飾符讓圖標對齊到我們需要的任何位置。在本例中,我們知道圖標的底部是對齊的目標位置,因此將圖標的底部進行對齊。最終便實現了期望的效果:

Jetpack Compose基礎知識科普

圖標底部與文本基線完美對齊

由于對齊功能會穿過父節點,因此,處理嵌套對齊時,只需設置父節點的對齊線,它會從子節點獲取相應的值。如下例所示:

Jetpack Compose基礎知識科普

未設置對齊的嵌套布局

Jetpack Compose基礎知識科普

通過父節點設置對齊線

您甚至可以在自定義布局中創建自己的自定義對齊,從而允許其他可組合項對齊到它。

BoxWithConstraints

BoxWithConstraints 是一個功能強大且很實用的布局。在組合中,我們可以根據條件使用邏輯和控制流來選擇要顯示的內容,但是,有時候可能希望根據可用空間的大小來決定布局內容。

從前文中我們知道,尺寸信息直到布局階段才可用,也就是說,這些信息一般無法在組合階段用來決定要顯示的內容。此時 BoxWithConstraints 便派上用場了,它與 Box 類似,但它將內容的組合推遲到布局階段,此時布局信息已經可用了。BoxWithConstraints 中的內容在接收器作用域內排布,布局階段確定的約束將通過該作用域公開為像素值或 DP 值。

@Composablefun BoxWithConstraints(        ...        content: @Composable BoxWithConstraintsScope.() -> Unit) // BoxWithConstraintsScope 公開布局階段確定的約束interface BoxWithConstraintsScope : BoxScope {    val constraints: Constraints    val minWidth: Dp    val maxWidth: Dp    val minHeight: Dp    val maxHeight: Dp}

BoxWithConstraints 和 BoxWithConstraintsScope

它內部的內容可以使用這些約束來選擇要組合的內容。例如,根據最大寬度選擇不同的呈現方式:

@Composablefun MyApp(...) {    BoxWithConstraints() { // this: BoxWithConstraintsScope        when {            maxWidth < 400.dp -> CompactLayout()            maxWidth < 800.dp -> MediumLayout()            else -> LargeLayout()        }    }}
在 BoxWithConstraintsScope 中根據最大寬度選擇不同的布局

性能

我們介紹了單遍布局模型如何防止在測量或放置方面花費過多時間,也演示了布局階段兩個不同的子階段: 測量和放置。現在,我們將介紹性能相關的內容。

盡量避免重組

單遍布局模型的設計效果是,任何只影響項目的放置而不影響測量的修改都可以單獨執行。以 Jetsnack 為例:

Jetpack Compose基礎知識科普

Jetsnack 應用中產品詳情頁的協調滾動效果

這個產品詳情頁包含協調滾動效果,頁面上的一些元素根據滾動操作進行移動或縮放。請注意標題區域,這個區域會隨著頁面內容而滾動,最后固定在屏幕的頂部。

@Composablefun SnackDetail(...) {    Box {        val scroll = rememberScrollState(0)        Body(scroll)        Title(scroll = scroll.value)        ...    }} @Composablefun Body(scroll: ScrollState) {    Column(modifier = Modifier.verticalScroll(scroll)) {    }}
詳情頁的大致實現

為了實現此效果,我們將不同元素作為獨立的可組合項疊放在一個 Box 中,提取滾動狀態并將其傳入 Body 組件。Body 會使用滾動狀態進行設置以使內容能夠垂直滾動。在 Title 等其他組件中可以觀察滾動位置,而我們的觀察方式會對性能產生影響。例如,使用最直接的實現,簡單地使用滾動值對內容進行偏移:

@Composablefun Title(scroll: Int) {    Column(        modifier = Modifier.offset(scroll)    ) {    }}
簡單地使用滾動值偏移 Title 的內容

這種方法的問題是,滾動是一個可觀察的狀態值,讀取該值所處的作用域規定了狀態發生變化時 Compose 需要重新執行的操作。在此示例中,我們要讀取組合中的滾動偏移值,然后使用它來創建偏移修飾符。只要滾動偏移值發生變化,Title 組件都需要重新組合,也就需要創建并執行新的偏移修飾符。由于滾動狀態是從組合中讀取的,任何更改都會導致重組,在重組時,還需要進行布局和繪制這兩個后續階段。 不過,我們不是要更改顯示的內容,而是更改內容的位置。我們還可以進一步提高效率,通過修改一下實現,不再接受原始滾動位置,而是傳遞一個可以提供滾動位置的函數:
@Composablefun Title(scrollProvider: () -> Int) {    Column(        modifier = Modifier.offset {            val scroll = scrollProvider()            val offset = (maxOffset - scroll).coerceAtLeast(minOffset)            IntOffset(x = 0, y = offset)        }    ) {    }}

使用提供滾動位置的函數代替原始滾動位置

這時,我們可以在不同的時間只調用此 Lambda 函數并讀取滾動狀態。這里使用了 offset 修飾符,它接受能提供偏移值的 Lambda 函數作為參數。這意味著在滾動發生變化時,不需要重新創建修飾符,只在放置階段才會讀取滾動狀態的值。所以,當滾動狀態變化時我們只需要執行放置和繪制操作,不需要重組或測量,因此能夠提高性能。

再回到底部導航的示例,它存在同樣的問題,我們可以用相同方法加以修正:
@Composablefun BottomNavItem(    icon: @Composable BoxScope.() -> Unit,    text: @Composable BoxScope.() -> Unit,    animationProgress: () -> Float) {     val progress = animationProgress()     val textWidth = textPlaceable.width * progress    val iconX = (width - textWidth - iconPlaceable.width) / 2    val textX = iconX + iconPlaceable.width     return layout(width, height) {        iconPlaceable.placeRelative(iconX.toInt(), iconY)        if (animationProgress != 0f) {            textPlaceable.placeRelative(textX.toInt(), textY)        }    }}

△修正后的底部導航

我們使用了能提供當前動畫進度的函數作為參數,因此不需要重組,只執行布局即可。 您需要掌握一個原則: 只要可組合項或修飾符的參數可能頻繁發生更改,都應當保持謹慎,因為這種情況可能導致過度組合。只有在更改顯示內容時,才需要重組,更改顯示位置或顯示方式則不需要這么做。

BoxWithConstraints 可以根據布局執行組合,是因為它會在布局階段啟動子組合。出于性能考慮,我們希望盡量避免在布局期間執行組合。因此,相較于 BoxWithConstraints,我們傾向于使用會根據尺寸更改的布局。當信息類型隨尺寸更改時才使用 BoxWithConstraints。

提高布局性能

有時候,布局不需要測量其所有子節點便可獲知自身大小。舉個例子,有如下構成的卡片:

Jetpack Compose基礎知識科普
△布局卡片示例

圖標和標題構成標題欄,剩下的是正文。已知圖標大小為固定值,標題高度與圖標高度相同。測量卡片時,就只需要測量正文,它的約束就是布局高度減去 48 DP,卡片的高度則為正文的高度加上 48 DP。

Jetpack Compose基礎知識科普
測量過程只測量正文尺寸

系統識別出只測量了正文,因此它是決定布局尺寸的唯一重要子節點,圖標和文本仍然需要測量,但可以在放置過程中執行。Jetpack Compose基礎知識科普

放置過程測量圖標和文本

假設標題是 "Layout",當標題發生變化時,系統不必重新執行布局的測量操作,因此不會重新測量正文,從而省去不必要的工作。Jetpack Compose基礎知識科普

標題發生變化時不必重新測量

總結

在本文中,我們介紹了自定義布局的實現過程,還使用修飾符構建和合并布局行為,進一步降低了滿足確切功能需求的難度。此外,還介紹了布局系統的一些高級功能,例如跨嵌套層次結構的自定義對齊,為自有布局創建自定義 ParentDataModifier,支持自動從右向左設置,以及將組合操作推遲到布局信息已知時,等等。我們還了解如何執行單遍布局模型,如何跳過重新測量以使其只執行重新放置操作的方法,熟練使用這些方法,您將能編寫出通過手勢進行動畫處理的高性能布局邏輯。

對布局系統的理解能夠幫助您構建滿足確切設計需求的布局,從而創建用戶喜愛的優秀應用。

原文標題:深度解析 Jetpack Compose 布局

文章出處:【微信公眾號:谷歌開發者】歡迎添加關注!文章轉載請注明出處。

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Android
    +關注

    關注

    12

    文章

    3935

    瀏覽量

    127341
  • 界面
    +關注

    關注

    0

    文章

    59

    瀏覽量

    15620
  • 工具包
    +關注

    關注

    0

    文章

    46

    瀏覽量

    9529

原文標題:深度解析 Jetpack Compose 布局

文章出處:【微信號:Google_Developers,微信公眾號:谷歌開發者】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    C語言基礎知識科普

    C語言是單片機開發中的必備基礎知識,本文列舉了部分STM32學習中比較常見的一些C語言基礎知識
    發表于 07-21 10:58 ?1890次閱讀

    科普一下CAN總線的基礎知識

    CAN總線是一種常用的總線,對于剛開始接觸CAN總線的,面對著各式各樣的資料,可能不知道從何看起,今天科普一下CAN總線的基礎知識。CAN2.0協議分為A版本和B版本,A版本協議為11位標識符(標準幀),B版本在兼容11位ID標識符的同時,向上擴展到29位ID標識符。
    發表于 05-16 09:49 ?3252次閱讀
    <b class='flag-5'>科普</b>一下CAN總線的<b class='flag-5'>基礎知識</b>

    compose的使用技巧是什么?

    compose的使用技巧是什么?
    發表于 11-15 07:27

    五分鐘讀懂WiFi基礎知識

    家1、嵌入式技術常識科普【物聯網】WiFi基礎知識五分鐘讀懂TCP/IP;協議STM32開發 -- Keil基本使用如何看懂時序圖(以SPI/I2C為例)ESP8266配網思路(不使用...
    發表于 12-01 06:36

    通信基礎知識教程

    通信基礎知識 1、電信基礎知識2、通信電源技術3、配線設備結構、原理與防護4、防雷基礎知識5、EMC基礎知識6、防腐蝕原理與技術7、產品安
    發表于 03-04 16:48 ?33次下載

    科普】卷積神經網絡基礎知識

    本文的主要目的,是簡單介紹時下流行的深度學習算法的基礎知識,本人也看過許多其他教程,感覺其中大部分講的還是太過深奧,于是便有了寫一篇科普文的想法。博主也是現學現賣,文中如有不當之處,請各位指出
    發表于 11-10 14:49 ?1718次閱讀
    【<b class='flag-5'>科普</b>】卷積神經網絡<b class='flag-5'>基礎知識</b>

    電源管理基礎知識電源管理基礎知識電源管理基礎知識

    電源管理基礎知識電源管理基礎知識電源管理基礎知識
    發表于 09-15 14:36 ?76次下載
    電源管理<b class='flag-5'>基礎知識</b>電源管理<b class='flag-5'>基礎知識</b>電源管理<b class='flag-5'>基礎知識</b>

    詳解Jetpack Compose 1.1版本的新功能

    我們一如既往地搭建產品路線圖,現在已經發布了 Jetpack Compose 的 1.1 版本,這是 Android 的現代原生界面工具包。此版本新增了一些功能,比如經過優化的焦點處理、觸摸目標值
    的頭像 發表于 03-11 10:14 ?1422次閱讀

    Jetpack Compose 更新一覽 | 2022 Android 開發者峰會

    作者 /?Android 開發者關系工程師 Jolanda Verhoef 去年我們發布了 Jetpack Compose ,此后一直在進行優化。我們已添加了新的功能并創造出功能更強大的工具,幫助
    的頭像 發表于 11-23 17:55 ?1113次閱讀

    Google計劃用Jetpack Compose來重建Android系統中的設置應用

    上周,Google 發布了 Android 14 的首個開發者預覽版,除了那些最新的功能以外,Google 似乎還正在默默醞釀一個新的計劃 —— 用更現代的 Jetpack Compose 來逐步
    的頭像 發表于 02-18 11:16 ?1650次閱讀

    Compose for Wear OS 1.1 推出穩定版: 了解新功能!

    為 Wear OS 構建出色的響應式應用。 ? Compose for Wear OS?1.1 版本 https://developer.android.google.cn/jetpack
    的頭像 發表于 02-22 01:30 ?911次閱讀

    Kotlin聲明式UI框架Compose Multiplatform支持iOS

    ,基于 Kotlin 和?Jetpack Compose?打造,由 JetBrains 和開源貢獻者開發。 Jetpack Compose 是 Google 為構建原生 UI 打造的
    的頭像 發表于 04-24 09:12 ?1292次閱讀
    Kotlin聲明式UI框架<b class='flag-5'>Compose</b> Multiplatform支持iOS

    電氣基本知識科普

    電氣基本知識科普
    的頭像 發表于 09-09 10:23 ?6119次閱讀
    電氣基本<b class='flag-5'>知識</b><b class='flag-5'>科普</b>

    科普|電源管理知識

    科普|電源管理知識
    的頭像 發表于 10-17 16:31 ?604次閱讀
    <b class='flag-5'>科普</b>|電源管理<b class='flag-5'>知識</b>

    Jetpack Compose和設備類型的三大重要更新

    2024 年 Google I/O 大會上我們分享了大量更新和公告,幫助開發者提升工作效率。了解 2024 年 Google I/O 大會上有關 Jetpack Compose 和設備類型的三大重要更新。
    的頭像 發表于 08-09 17:07 ?665次閱讀
    主站蜘蛛池模板: 国产区在线不卡视频观看| 9277高清在线观看视频| 69久久国产精品热88人妻| 国产-第1页-浮力影院| 美女尿口羞羞视频| 亚洲精品第一综合99久久| 99视频在线精品免费观看18| 国产人妻午夜无码AV天堂| 欧美尤物射精集锦| 伊人色综合久久天天网| 国产精品资源网站在线观看| 嗯啊快拔出来我是你老师视频| 亚洲视频网站欧美视频网站| 哒哒哒高清视频在线观看| 开心色99xxxx开心色| 亚洲黄色在线| 国产精品你懂得| 亚洲高清无码在线 视频| 成人免费视频在| 欧美在线激情| metart中国撒尿人体欣赏| 久久综合伊人| 又大又硬又爽免费视频| 狠狠狠的在啪线香蕉| 无码乱人伦一区二区亚洲一| 第七色男人天堂| 涩里番app黄版网站| 跪趴式啪啪GIF动态图27报| 少妇的肉体AA片免费| 囯产精品久久久久久久久蜜桃 | 456亚洲人成在线播放网站| 久久久中日AB精品综合| 中文字幕偷乱免费视频在线| 久久涩视频| bdsm中国精品调教ch| 青青草原国产在线| 国产精品久久毛片A片软件爽爽 | 日本乱hd高清videos| 国产99小视频| 伊人影院香蕉久在线26| 久久中文骚妇内射|