Android compose 自定義layout (翻譯)

Create your custom layout

創(chuàng)建您的自定義布局

Compose promotes reusability of composables as small chunks that can be enough for some custom layouts by combining built-in composables such as Column, Row, or Box.

However, you might need to build something unique to your app that requires measuring and laying out children manually. For that, you can use the Layout composable. In fact all higher level layouts like Column and Row are built with this.

Note: In the View system, creating a custom layout required extending ViewGroup and implementing measure and layout functions. In Compose you simply write a function using the Layout composable.

通常來說,通過諸如Column, Row, 或 Box 之類的內(nèi)置compose來自定義某些布局來說已經(jīng)足夠了两曼。
但是皂甘,您可能需要構(gòu)建一些獨(dú)特的應(yīng)用程序,需要手動(dòng)測(cè)量和布置子項(xiàng)悼凑。為此偿枕,您可以使用 Layout。事實(shí)上佛析,所有更高級(jí)別的布局都喜歡用ColumnRow來構(gòu)建益老。

Note:在視圖系統(tǒng)中,創(chuàng)建自定義布局需要擴(kuò)展ViewGroup和實(shí)現(xiàn)measure和layout函數(shù)寸莫。在 Compose 中捺萌,您只需使用Layout composable函數(shù)。

Before diving into how to create custom layouts, we need to know more about the principles of Layouts in Compose.

在深入研究如何創(chuàng)建自定義布局之前膘茎,我們需要更多地了解 Compose 中布局的原理桃纯。

Principles of layouts in Compose

Some composable functions emit a piece of UI when invoked that is added to a UI tree that will get rendered on the screen. Each emission (or element) has one parent and potentially many children. Also, it has a location within its parent: an (x, y) position, and a size: a width and height.

Compose 中的布局原則

一些可組合函數(shù)在調(diào)用時(shí)會(huì)發(fā)出一段 UI,被添加到UI樹中披坏,呈現(xiàn)在屏幕上态坦。每個(gè)發(fā)射物(或元素)都有一個(gè)父級(jí)和潛在的許多子級(jí)。此外棒拂,它在其父級(jí)中有位置-> (x, y) 坐標(biāo)伞梯,和大小->width、height帚屉。

Elements are asked to measure themselves with Constraints that should be satisfied. Constraints restrict the minimum and maximum width and height of an element. If an element has child elements it may measure each of them to help determine its own size. Once an element reports its own size, it has an opportunity to place its child elements relative to itself. This will be further explained when creating the custom layout.

元素需要用Constraints來準(zhǔn)確的測(cè)量自己谜诫。Constraints限制元素的最小和最大width和height。如果一個(gè)元素有子元素攻旦,它可以測(cè)量它們中的每一個(gè)來幫助確定它自己的大小喻旷。一旦一個(gè)元素上報(bào)了它自己的大小,它就有機(jī)會(huì)相對(duì)于自身放置它的子元素牢屋。這將在創(chuàng)建自定義布局時(shí)進(jìn)一步解釋且预。

Compose UI does not permit multi-pass measurement. This means that a layout element may not measure any of its children more than once in order to try different measurement configurations. Single-pass measurement is good for performance, allowing Compose to handle efficiently deep UI trees. If a layout element measured its child twice and that child measured one of its children twice and so on, a single attempt to lay out a whole UI would have to do a lot of work, making it hard to keep your app performing well. However, there are times when you really need additional information on top of what a single child measurement would tell you - for these cases there are ways of doing this, we will talk about them later.

Compose UI 不允許多次測(cè)量槽袄。這意味著布局元素可能不會(huì)多次測(cè)量其任何子元素,以便嘗試不同的測(cè)量架構(gòu)锋谐。單遍測(cè)量有利于提高性能遍尺,使 Compose 能夠有效地處理深層 UI 樹。如果一個(gè)布局元素測(cè)量了它的子元素兩次怀估,而該子元素測(cè)量了它的一個(gè)子元素兩次狮鸭,依此類推合搅,一次嘗試布置整個(gè) UI 將不得不做很多工作多搀,這使得你的應(yīng)用程序很難保持良好的性能。但是灾部,有時(shí)您確實(shí)需要除了單個(gè)子項(xiàng)測(cè)量結(jié)果之外的其他信息 - 對(duì)于這些情況康铭,有一些方法可以做到這一點(diǎn),我們將在稍后討論赌髓。

Using the layout modifier

Use the layout modifier to manually control how to measure and position an element. Usually, the common structure of a custom layout modifier is as follows:

使用布局修飾符

使用layout修改器手動(dòng)控制如何測(cè)量和定位元素从藤。通常,自定義layout修飾符的常見結(jié)構(gòu)如下:

fun Modifier.customLayoutModifier(...) = Modifier.layout { measurable, constraints ->
  ...
})

When using the layout modifier, you get two lambda parameters:

measurable: child to be measured and placed
constraints: minimum and maximum for the width and height of the child
Let's say you want to display a Text on the screen and control the distance from the top to the baseline of the first line of texts. In order to do that, you'd need to manually place the composable on the screen using the layout modifier. See the desired behavior in the next picture where the distance from top to first baseline is 24.dp:

使用layout修飾符時(shí)锁蠕,您將獲得兩個(gè) lambda 參數(shù):

measurable:要測(cè)量和放置的孩子
constraints: 孩子的寬度和高度的最小值和最大值
假設(shè)您想在屏幕上顯示 一個(gè)文本并控制從第一行文本的頂部到基線的距離夷野。為了做到這一點(diǎn),您需要使用layout修飾符手動(dòng)將可組合項(xiàng)放置在屏幕上荣倾。在下一張圖片中查看所需的行為悯搔,其中從頂部到第一個(gè)基線的距離為24.dp:



Let's create a firstBaselineToTop modifier first:
讓我們先創(chuàng)建一個(gè)firstBaselineToTop修飾符:

fun Modifier.firstBaselineToTop(
  firstBaselineToTop: Dp
) = this.then(
    layout { measurable, constraints ->
        ...
    }
)

The first thing to do is measure the composable. As we mentioned in the Principles of Layout in Compose section, you can only measure your children once.

Measure the composable by calling measurable.measure(constraints). When calling measure(constraints), you can pass in the given constraints of the composable available in the constraints lambda parameter or create your own. The result of a measure() call on a Measurable is a Placeable that can be positioned by calling placeRelative(x, y), as we will do later.

For this use case, don't constrain measurement further, just use the given constraints:

首先要做的是衡量可組合。正如我們?cè)?Compose 中的布局原則部分提到的舌仍,您只能測(cè)量您的孩子一次妒貌。

通過調(diào)用測(cè)量可組合measurable.measure(constraints)。調(diào)用measure(constraints)時(shí)铸豁,您可以傳入constraints lambda 參數(shù)中constraints灌曙,也可以自己創(chuàng)建。Measurable 調(diào)用measure()的結(jié)果是Placeable可以通過調(diào)用placeRelative(x, y)來定位节芥,我們稍后會(huì)這樣做在刺。

對(duì)于這個(gè)用例,不要進(jìn)一步約束測(cè)量头镊,只需使用給定的constraints:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = this.then(
    layout { measurable, constraints ->
        val placeable = measurable.measure(constraints)

        ...
    }
)

Now that the composable has been measured, you need to calculate its size and specify it by calling the layout(width, height) method which also accepts a lambda used for placing the content.

In this case, the width of our composable will be the width of the measured composable and the height will be the composable's height with the desired top-to-baseline height minus the first baseline:

既然已經(jīng)測(cè)量了可組合項(xiàng)蚣驼,您需要計(jì)算它的大小并通過調(diào)用layout(width, height)函數(shù)或者 lambda表達(dá)式來實(shí)現(xiàn)它。

在這種情況下拧晕,我們的可組合物的寬度width將是測(cè)量結(jié)果的寬度隙姿,高度height期望值是top-to-baseline 的高度減去first baseline:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = this.then(
    layout { measurable, constraints ->
        val placeable = measurable.measure(constraints)

        // Check the composable has a first baseline
        check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
        val firstBaseline = placeable[FirstBaseline]

        // Height of the composable with padding - first baseline
        val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
        val height = placeable.height + placeableY
        layout(placeable.width, height) {
            ...
        }
    }
)

Now, you can position the composable on the screen by calling placeable.placeRelative(x, y). If you don't call placeRelative, the composable won't be visible. placeRelative automatically adjusts the position of the placeable based on the current layoutDirection.

Warning: When creating a custom Layout or LayoutModifier, Android Studio will give a warning until the layout function is called.

In this case, the y position of the text corresponds to the top padding minus the position of the first baseline:

現(xiàn)在,您可以通過調(diào)用placeable.placeRelative(x, y). 如果您不調(diào)用placeRelative厂捞,則可組合項(xiàng)將不可見输玷。placeRelative根據(jù)當(dāng)前 自動(dòng)調(diào)整可放置物的位置layoutDirection队丝。

警告:創(chuàng)建自定義Layoutor 時(shí)LayoutModifier,Android Studio 會(huì)發(fā)出警告欲鹏,直到layout調(diào)用該函數(shù)机久。

在這種情況下,y文本的位置對(duì)應(yīng)于頂部填充減去第一個(gè)基線的位置:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = this.then(
    layout { measurable, constraints ->
        ...

        // Height of the composable with padding - first baseline
        val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
        val height = placeable.height + placeableY
        layout(placeable.width, height) {
            // Where the composable gets placed
            placeable.placeRelative(0, placeableY)
        }
    }
)

To verify this works as expected, you can use this modifier on a Text as you saw in the picture above:

要驗(yàn)證這是否按預(yù)期工作赔嚎,您可以在 文本上使用此修飾符如上圖所示:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
  LayoutsCodelabTheme {
    Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
  }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
  LayoutsCodelabTheme {
    Text("Hi there!", Modifier.padding(top = 32.dp))
  }
}

With preview:


Using the Layout composable

Instead of controlling how a single composable gets measured and laid out on the screen, you might have the same necessity for a group of composables. For that, you can use the Layout composable to manually control how to measure and position the layout's children. Usually, the common structure of a composable that uses Layout is as follows:

使用布局可組合

與控制單個(gè)可組合項(xiàng)如何在屏幕上測(cè)量和布局不同膘盖,您可能對(duì)一組可組合項(xiàng)具有相同的必要性。為此尤误,您可以使用 Layout可組合來手動(dòng)控制如何測(cè)量和定位布局的子項(xiàng)侠畔。通常,使用的可組合的常見結(jié)構(gòu)Layout如下:

@Composable
fun CustomLayout(
    modifier: Modifier = Modifier,
    // custom layout attributes 
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
    }
}

The minimum required parameters for a CustomLayout are a modifier and content; these parameters are then passed to Layout. In the trailing lambda of Layout (of type MeasurePolicy), you get the same lambda parameters as you get with the layout modifier.

To show Layout in action, let's start implementing a very basic Column using Layout to understand the API. Later, we'll build something more complex to showcase flexibility of the Layout composable.

CustomLayout 所需的最小參數(shù)是 modifier和content; 然后將這些參數(shù)傳遞給Layout. 在Layout(類型MeasurePolicy)的 lambda 尾部损晤,layout 的
modifier 與lambda表達(dá)式中的參數(shù)相同软棺。

為了展示Layout實(shí)際效果,讓我們來用Layout來實(shí)現(xiàn)一個(gè)非秤妊基本的Column來理解 API喘落。稍后,我們將構(gòu)建一些更復(fù)雜的東西來展示Layout composable 的靈活性最冰。

Implementing a basic Column

Our custom implementation of Column lays out items vertically. Also, for simplicity, our layout occupies as much space as it can in its parent.

Create a new composable called MyOwnColumn and add the common structure of a Layout composable:

實(shí)現(xiàn)一個(gè)基本的列

我們的自定義實(shí)現(xiàn)Column 垂直布局項(xiàng)目瘦棋。此外,為簡(jiǎn)單起見暖哨,我們的布局在其父級(jí)中占據(jù)盡可能多的空間赌朋。

創(chuàng)建一個(gè)名為的MyOwnColumn的composable函數(shù)并添加可組合的通用結(jié)構(gòu)Layout:

@Composable
fun MyOwnColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
    }
}

As before, the first thing to do is measure our children that can only be measured once. Similarly to how the layout modifier works, in the measurables lambda parameter, you get all the content that you can measure by calling measurable.measure(constraints).

For this use case, you won't constrain our child views further. When measuring the children, you should also keep track of the width and the maximum height of each row to be able to place them correctly on the screen later:

和以前一樣,首先要做的是測(cè)量我們只能測(cè)量一次的孩子鹿蜀。與布局修飾符的工作方式類似箕慧,在lambda 函數(shù)中的measurables中,您可以通過調(diào)用measurable.measure(constraints)獲取所有的可測(cè)量的content

對(duì)于這個(gè)用例茴恰,您不會(huì)進(jìn)一步限制我們的子視圖颠焦。在測(cè)量孩子時(shí),您還應(yīng)該跟蹤每行width的最大值和最大值height往枣,以便以后能夠?qū)⑺鼈冋_地放置在屏幕上:

@Composable
fun MyOwnColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->

        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each child
            measurable.measure(constraints)
        }
    }
}

Now that you have the list of measured children in our logic, before positioning them on the screen, you need to calculate the size of our version of Column. As you're making it as big as its parent, the size of it is the constraints passed in by the parent. Specify the size of our own Column by calling the layout(width, height) method, which also gives you the lambda used for placing the children:

現(xiàn)在您在我們的邏輯中已經(jīng)有了可測(cè)量子項(xiàng)的列表伐庭,在將它們定位到屏幕上之前,您需要計(jì)算我們版本的Column. 當(dāng)你讓它和它的父級(jí)一樣大時(shí)分冈,它的大小就是父級(jí)傳入的約束圾另。Column通過調(diào)用layout(width, height)方法指定我們自己的大小,該方法還為您提供用于放置子項(xiàng)的 lambda:

@Composable
fun MyOwnColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Measure children - code in the previous code snippet
        ...

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Place children
        }
    }
}

Lastly, we position our children on the screen by calling placeable.placeRelative(x, y). In order to place the children vertically, we keep track of the y coordinate we have placed children up to. The final code of MyOwnColumn looks like this:

最后雕沉,我們通過調(diào)用將我們的孩子定位在屏幕上placeable.placeRelative(x, y)集乔。為了垂直放置子項(xiàng),我們跟蹤y放置子項(xiàng)的坐標(biāo)坡椒。最終代碼MyOwnColumn如下所示:

@Composable
fun MyOwnColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each child
            measurable.measure(constraints)
        }

        // Track the y co-ord we have placed children up to
        var yPosition = 0

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

MyOwnColumn in action

Let's see MyOwnColumn on the screen by using it in the BodyContent composable. Replace the content inside BodyContent with the following:

MyOwnColumn 的行為
讓我們?cè)贐odyContent中使用MyOwnColumn來看它在屏幕上如何顯示扰路。將 BodyContent 中的內(nèi)容替換為以下內(nèi)容:

@Composable
fun BodyContent(modifier: Modifier = Modifier) {
    MyOwnColumn(modifier.padding(8.dp)) {
        Text("MyOwnColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

With preview:


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尤溜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子汗唱,更是在濱河造成了極大的恐慌宫莱,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哩罪,死亡現(xiàn)場(chǎng)離奇詭異授霸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)际插,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門碘耳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腹鹉,你說我怎么就攤上這事藏畅》蠊瑁” “怎么了功咒?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绞蹦。 經(jīng)常有香客問我力奋,道長(zhǎng),這世上最難降的妖魔是什么幽七? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任景殷,我火速辦了婚禮,結(jié)果婚禮上澡屡,老公的妹妹穿的比我還像新娘猿挚。我一直安慰自己,他們只是感情好驶鹉,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布绩蜻。 她就那樣靜靜地躺著,像睡著了一般室埋。 火紅的嫁衣襯著肌膚如雪办绝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天姚淆,我揣著相機(jī)與錄音孕蝉,去河邊找鬼。 笑死腌逢,一個(gè)胖子當(dāng)著我的面吹牛降淮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搏讶,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼佳鳖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼纳本!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腋颠,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤繁成,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后淑玫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巾腕,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年絮蒿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尊搬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡土涝,死狀恐怖佛寿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情但壮,我是刑警寧澤冀泻,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站蜡饵,受9級(jí)特大地震影響弹渔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜溯祸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一肢专、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧焦辅,春花似錦博杖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仆抵,卻和暖如春跟继,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背镣丑。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工舔糖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人莺匠。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓金吗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子摇庙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

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