Row/ColumnとConstraintLayoutでどの程度レイアウトの作り方が違うのか
こんにちは、tkyです。
※Qiitaにも書いてありますが、こちらにも投稿しておきます。
jetpack composeでかんたんなレイアウトをそれぞれ - Row/Column/Box (※今回Boxは使っていません) - Constraint
で作成した場合、どのような違いが出るのか見てみました。
ConstraintLayoutのUIの作り方とかはこの記事では触れませんがコード見ていただいてもある程度理解できるかなと思います。
作ってみたのはこちら。 [search, share, home, star] というラベルが付いたアイコンを並べる
という部分をそれぞれRow/Columを使って実現したパターンとConstraintLayoutを使って実現したパターンとなります。
@Composable fun RowColumnOrConstraintScreen() { Column( modifier = Modifier.padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { Text(text = "row,column composable") Card(modifier = Modifier.padding(16.dp)) { IconList( modifier = Modifier .padding(8.dp) .fillMaxWidth() ) } Text(text = "constraint composable") Card(modifier = Modifier.padding(16.dp)) { IconListConstraint( modifier = Modifier .padding(8.dp) .fillMaxWidth() ) } } } @Composable @Preview fun RowColumnOrConstraintScreenPreview() { RowColumnOrConstraintScreen() }
こちらがプレビューの内容です
Row/Column
以下を作ります。
- 基本となるComposable関数
IconLabel
- search, share, home, starそれぞれを表すComposable関数
IconLabelSearch
,IconLabelShare
,IconLabelHome
,IconLabelStar
- 一列に並べるComposable関数
IconList
@Composable fun IconLabel(imageVector: ImageVector, label: String, modifier: Modifier) { Column( modifier = modifier.padding(vertical = 8.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Image( imageVector = imageVector, contentDescription = label, modifier = Modifier.padding(horizontal = 16.dp), ) Text(text = label) } } @Composable fun IconLabelSearch(modifier: Modifier = Modifier) { IconLabel(imageVector = Icons.Rounded.Search, label = "search", modifier = modifier) } @Composable fun IconLabelShare(modifier: Modifier = Modifier) { IconLabel(imageVector = Icons.Rounded.Share, label = "share", modifier = modifier) } @Composable fun IconLabelHome(modifier: Modifier = Modifier) { IconLabel(imageVector = Icons.Rounded.Home, label = "home", modifier = modifier) } @Composable fun IconLabelStar(modifier: Modifier = Modifier) { IconLabel(imageVector = Icons.Rounded.Star, label = "star", modifier = modifier) } @Composable fun IconList(modifier: Modifier = Modifier) { Row( modifier = modifier, horizontalArrangement = Arrangement.SpaceEvenly, ) { IconLabelSearch() IconLabelShare() IconLabelHome() IconLabelStar() } } @Composable @Preview fun IconListPreview() { IconList(modifier = Modifier.fillMaxWidth()) }
こちらがプレビューの内容です。
ConstraintLayout
Row/Columnと同様の構成で以下を作ります。
- 基本となるComposable関数
IconLabelConstraint
- search, share, home, starそれぞれを表すComposable関数
IconLabelSearchConstraint
,IconLabelShareConstraint
,IconLabelHomeConstraint
,IconLabelStarConstraint
- 一列に並べるComposable関数
IconListConstraint
@Composable fun IconLabelConstraint(imageVector: ImageVector, label: String, modifier: Modifier = Modifier) { ConstraintLayout( modifier = modifier ) { val (rateVal) = createRefs() Image(imageVector = imageVector, contentDescription = label, modifier = Modifier.constrainAs(rateVal) { top.linkTo(parent.top, margin = 8.dp) start.linkTo(parent.start, margin = 16.dp) end.linkTo(parent.end, margin = 16.dp) }) Text(text = label, modifier = Modifier.constrainAs(createRef()) { top.linkTo(rateVal.bottom) start.linkTo(rateVal.start) end.linkTo(rateVal.end) bottom.linkTo(parent.bottom, margin = 8.dp) }) } } @Composable fun IconLabelSearchConstraint(modifier: Modifier = Modifier) { IconLabelConstraint(imageVector = Icons.Rounded.Search, label = "search", modifier = modifier) } @Composable fun IconLabelShareConstraint(modifier: Modifier = Modifier) { IconLabelConstraint(imageVector = Icons.Rounded.Share, label = "share", modifier = modifier) } @Composable fun IconLabelHomeConstraint(modifier: Modifier = Modifier) { IconLabelConstraint(imageVector = Icons.Rounded.Home, label = "home", modifier = modifier) } @Composable fun IconLabelStarConstraint(modifier: Modifier = Modifier) { IconLabelConstraint(imageVector = Icons.Rounded.Star, label = "star", modifier = modifier) } @Composable fun IconListConstraint(modifier: Modifier = Modifier) { ConstraintLayout( modifier = modifier ) { val (search, share, home, star) = createRefs() IconLabelSearchConstraint( modifier = Modifier.constrainAs(search) { top.linkTo(parent.top) start.linkTo(parent.start) end.linkTo(share.start) }) IconLabelShareConstraint( modifier = Modifier.constrainAs(share) { top.linkTo(parent.top) start.linkTo(search.end) end.linkTo(home.start) }) IconLabelHomeConstraint( modifier = Modifier.constrainAs(home) { top.linkTo(parent.top) start.linkTo(share.end) end.linkTo(star.start) }) IconLabelStarConstraint( modifier = Modifier.constrainAs(star) { top.linkTo(parent.top) start.linkTo(home.end) end.linkTo(parent.end) }) } } @Composable @Preview fun IconListConstraintPreview() { IconListConstraint(modifier = Modifier.fillMaxWidth()) }
こちらがプレビューの内容です。 xmlのプレビューと同様に成約のマージンなどが可視化されているのが良いですね。
どちらも同じレイアウトを実現できたが・・・
コードを見ていかがでしたでしょうか? このレベルのレイアウトならRow/Columnのほうがならべるだけで簡単で良さそうですね。
普段xmlでViewを作っているとついついViewを平たく保つためにConstraintLayoutを使ってしまいがちですが、 実際jetpack composeでConstraintLayoutを使ってみると結構な量で冗長なmodifierを書く必要があり(constrainAsの箇所のことです)、ちょっと読みづらいなと思いました。
Layout Inspectorでも見てみました
ConstraintLayoutとして木構造になっているので階層構造がこのレベルだとあまり深さの違いはわかりませんでした・・・
パフォーマンスについて
公式のドキュメントに言及があります。 ページをそのまま引用させていただきます。
https://developer.android.com/jetpack/compose/layouts/constraintlayout?hl=ja 注: View システムでは、大規模で複雑なレイアウトを作成する場合のおすすめの方法は ConstraintLayout でした。ネストされたビューよりもフラットなビュー階層のほうがパフォーマンスが優れていたためです。しかし、深いレイアウト階層を効率的に扱える Compose では、このような懸念はありません。 注: Compose の特定の UI に ConstraintLayout を使用するかどうかは、デベロッパーの好みによります。Android View システムでは、より高パフォーマンスのレイアウトを作成する方法として ConstraintLayout が使用されていましたが、この点は Compose では問題になりません。選択する必要がある場合は、ConstraintLayout がコンポーザブルの読みやすさと保守性に役立つかどうかを検討します。
ConstraintLayoutはマルチプラットフォームではない
ConstraintLayoutは現在マルチプラットフォーム化はされておらずAndroidで使えるライブラリとなっています。
Row/Column/Boxといった標準UIで構成できるようにしておくことで今後、 jetpack compose for Desktopやfor Webなどにも適用できるような基礎力が身につくのかなと思いました。
まとめ
実際ConstraintLayoutが煩雑になりやすそうなイメージを持ったので - 基本はRow/Column/BoxでUIを作っていく - 局所的に成約を設定したい場合にConstraintLayout使っても良さそう
くらいの認識になりました。