Mendalami Constraint Layout pada Jetpack Compose

Veronica Putri Anggraini
8 min readMar 9, 2023

--

Halo folks, sejak lama Constraint Layout menjadi solusi bagi para developer untuk membangun sebuah layout yang complex dengan menghindari hierarki tampilan dan positioning rumit yang terkadang memberi dampak pada app perfomance. Constraint Layout menyederhanakan pembuatan layout kompleks pada aplikasi android, selain itu dukungan android studio juga memungkinkan untuk membangun sebagian besar UI hanya dengan menggunakan editor visual dengan metode drag and drop. Sehingga menurut saya secara personal sangat sulit untuk meninggalkan God of Layout ini. Sehingga sangat menggembirakan bagi para developer android saat diumumkan bahwa Constraint Layout dapat diimplementasikan pada compose. Pada artikel ini saya akan membahas mengenai implementasi sebuah Constraint Layout pada UI yang dibangun menggunakan compose.

Sejauh yang saya ketahui terdapat 2 metode yang bisa digunakan dalam mengimplementasikan Constraint Layout. Keduanya akan coba saya jelaskan pada artikel ini. Namun hal pertama yang harus dilakukan setelah membuat project adalah menambahkan library constraint layout pada dependencies file dan selanjutnya sync gradle file.

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

Selanjutnya buat sebuah composable function dengan nama ConstraintScreen. Lalu panggil function ini pada kelas MainActivity dan jangan lupa untuk membuat preview dari function ini. Kurang lebih codenya aka terlihat seperti dibawah ini.

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PlayingWithComposeTheme {
Surface {
ConstraintScreen()
}
}
}
}
}

@Composable
fun ConstraintScreen() {

}

@Preview
@Composable
fun PreviewConstraintScreen() {

}

Buat sebuah variable yang memiliki value sebagai ConstraintSet yaitu kelas yang digunakan untuk mendefinisikan set dari constraint secara programatically. Kelas ini memungkinkan untuk membuat dan menyimpan constraint, dan menerapkannya pada ConstraintLayout. Buatlah code seperti dibawah ini.

val constraint = ConstraintSet {
val firstRect = createRefFor("firstRect")

constrain(firstRect) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
height = Dimension.value(100.dp)
}
}

Saat mengatur posisi dari sebuah view dengan menggunakan xml, digunakan sourcecode seperti dibawah ini:
app:layout_constraintTop_toTopOf=”parent” app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintBottom_toBottomOf=”parent”

Dengan menggunakan compose, mengatur posisi widget seperti diatas dilakukan dengan menggunakan linkTo.

  • start.linkTo(parent.start), membuat widget firstRect menambatkan bagian start pada parent(screen).
  • top.linkTo(parent.top), membuat widget firstRect menambatkan bagian atas pada parent(screen).
  • end.linkTo(parent.end), membuat widget firstRect menambatkan bagian akhir pada parent(screen).

Saat menggunakan constraint layout untuk mengatur dimensi dari view agar memenuhi space yang tersisa pada screen sering digunakan 0dp. Hal ini juga dapat diimplementasikan pada compose tetapi dengan code yang berbeda yaitu Dimension.fillToConstraints yang memiliki fungsi yang sama dengan 0dp. Lalu untuk mengatuh size dari dimensi widget dalam constraint juga dapat menggunakan Dimension.value(100.dp). Selain itu terdapat juga beberapa komponen lain yang dapat digunakan untuk mengatur ukuran dimensi Dimension.wrapContent untuk wrap content dan Dimension.matchParent untuk match content.

  • width = Dimension.fillToConstraints, mengatur ukuran lebar dari widget firstRect untuk memenuhi lebar dari layar.
  • height = Dimension.value(100.dp), mengatur ukuran tinggi dari widget firstRect menjadi 100 dp.

Selanjutnya panggil ConstraintLayout dan buatlah sebuah Box dengan mendeklarasikan idnya sebagai firstRect widget. Untuk lebih jelasnya perhatikan code dibawah ini.

ConstraintLayout(constraintSet = constraint, modifier = Modifier.fillMaxSize()) {
Box(modifier = Modifier
.background(DarkGreen)
.layoutId("firstRect"))

}

Dan ketika dicompile maka menghasilkan tampilan seperti dibawah ini.

Selanjutnya tambahkan secondRect variable pada ConstraintSet dan posisikan widget ini dibawah firstRect dengan lebar memenuhi layar. Seperti code dibawah ini.

val secondRect = createRefFor("secondRect")
constrain(secondRect) {
top.linkTo(firstRect.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
height = Dimension.value(100.dp)
}

Tambahkan box dengan id secondRect, dengan kode dibawah ini.

Box(
modifier = Modifier
.layoutId("secondRect")
.background(Cream)
)

Dan jika di compile akan menghasilkan tampilan seperti dibawah ini.

Tambahkan satu box disebelah kanan dari secondRect serta buat ukuran lebar dari kedua box terbagi menjadi dua bagian sama rata. Kita bisa melakukan langkah yang sama seperti cara di atas.

  1. Tambahkan variable pada constraintSet seperti dibawah ini
val thirdRect = createRefFor("thirdRect")

2. Tambahkan detail constraint pada variable thirdRect seperti dibawah ini. Bagian start dari thirdRect berada di sebelah kanan/end dari secondRect, selain itu tambahkan 1f untuk lebar dari third agar kedua box terbagi menjadi dua bagian rata.

constrain(thirdRect) {
top.linkTo(firstRect.bottom)
start.linkTo(secondRect.end)
end.linkTo(parent.end)
width = Dimension.percent(1f)
height = Dimension.value(100.dp)
}

3. Lalu buatlah box sebagai komponen dari id thirdRect, seperti dibawah ini.

Box(
modifier = Modifier
.layoutId("thirdRect")
.background(YellowPickerColor)
)

Jika dicompile akan menghasilkan tampilan seperti dibawah ini.

Salah satu fitur yang ditawarkan oleh constraint layout adalah chain. Chain adalah widget yang terhubung satu sama lain dengan batasan posisi dua arah. Fitur ini dapat diimplementasikan saat menggunakan compose dengan tiga style.

  1. Spread, membuat widget didistribusikan secara merata.
  2. SpreadInside, membuat widget awal dan akhir ditempelkan pada batasan di setiap ujung chain dan sisanya didistribusikan secara merata.
  3. Packed, membuat widget disusun secara bersama (setelah margin diperhitungkan). Hal ini juga memungkinkan kita menyesuaikan bias keseluruhan chain (kiri/kanan atau atas/bawah) dengan mengubah bias tampilan head chain.

Untuk lebih jelasnya perhatikan kode dibawah ini:

  1. Tambahkan dua variable pada constraintSet sebagai fourthRect dan fifthRect, seperti dibawah ini.
val fourthRect = createRefFor("fourthRect")
val fifthRect = createRefFor("fifthRect")

2. Selanjutnya atur constraint position dari kedua variable diatas, seperti dibawah ini.

constrain(fourthRect) {
top.linkTo(secondRect.bottom)
start.linkTo(parent.start)
width = Dimension.value(100.dp)
height = Dimension.value(100.dp)
}
constrain(fifthRect) {
top.linkTo(secondRect.bottom)
start.linkTo(fourthRect.end)
end.linkTo(parent.end)
width = Dimension.value(100.dp)
height = Dimension.value(100.dp)
}

3. Lalu buat box sebagai id dari fourthRect dan fifthRect, seperti dibawah ini.

Box(
modifier = Modifier
.layoutId("fourthRect")
.background(GreenPickerColor)
)
Box(
modifier = Modifier
.layoutId("fifthRect")
.background(Purple40)
)

4. Lalu buatlah chain untuk kedua widget diatas, karena kedua widget disusun secara horizontal, gunakan createHorizontalChain() jika widget bersusun secara vertical maka gunakan createVerticalChain(), perhatikan code dibawah ini.

createHorizontalChain(fourthRect, fifthRect, chainStyle = ChainStyle.Spread)

Pada code diatas mengimplementasikan salah satu style dari Chain yaitu spread, maka jika dicompile akan memiliki tampilan seperti dibawah ini.

Spread Style

Selanjutnya coba ganti ChainStyle dengan SpreadInside seperti dibawah ini.

createHorizontalChain(fourthRect, fifthRect, chainStyle = ChainStyle.SpreadInside)
SpreadInside

Yang terakhir coba ganti ChainStyle dengan Packed, seperti dibawah ini.

createHorizontalChain(fourthRect, fifthRect, chainStyle = ChainStyle.Packed)
Packed Style

Selain Chain terdapat satu fitur lain yang menjadi daya tarik dari Constraint Layout yaitu Guideline, fitur ini juga dapat diimplementasikan pada compose. Guideline merupakan sebuah kelas yang berisi objek helper pada ConstraintLayout. Objek helper ini tidak ditampilkan/terlihat di device dan hanya digunakan untuk membantu mengatur tata letak. Terdapat dua tipe guideline yang dapat diimplementasikan

  1. VerticalGuideline, memiliki lebar nol dan tinggi dari ConstraintLayout parent.
  2. HorizontalGuideline, memiliki tinggi nol dan lebar dari ConstraintLayout parent.

Mari coba implementasikan guideline pada halaman yang sebelumnya sudah ditambahkan beberapa box widget.

  1. Tambahkan variable sixRect dan sevenRect pada ConstraintSet seperti dibawah ini.
   val sixRect = createRefFor("sixRect")
val sevenRect = createRefFor("sevenRect")

2. Lalu tambahkan variable guideline dengan value berupa create guideline, seperti dibawah ini.

val guideline = createGuidelineFromBottom(0.1f)

3. Selanjutnya tambahkan detail constraint dari variable sixRect dan sevenRect, jadikan guideline sebagai objek yang mengikat kedua widget tersebut dari bottom, seperti dibawah ini.

constrain(sixRect) {
bottom.linkTo(guideline)
start.linkTo(parent.start)
width = Dimension.value(100.dp)
height = Dimension.value(100.dp)
}
constrain(sevenRect) {
bottom.linkTo(guideline)
start.linkTo(sixRect.end)
end.linkTo(parent.end)
width = Dimension.value(100.dp)
height = Dimension.value(100.dp)
}

4. Selanjutnya buatlah box sebagai widget dari kedua ID di atas.

Box(
modifier = Modifier
.layoutId("sixRect")
.background(Pink40)
)
Box(
modifier = Modifier
.layoutId("sevenRect")
.background(NeonGreen)
)

5. Lalu untuk meningkatkan pemahaman terhadap Chain, implementasikan Chain pada kedua widget seperti dibawah ini.

createHorizontalChain(sixRect, sevenRect, chainStyle = ChainStyle.Spread)

Sehingga keseluruhan kode akan menjadi seperti dibawah ini.

Dan saat dicompile akan menghasilkan tampilan seperti dibawah ini. Untuk detail codenya dapat di lihat pada github repository berikut dan pilih branch basic-constraint.

Mengimplementasikan Constraint Layout pada compose cukup mudah jika sudah memahami Constraint Layout pada view. Untuk meningkatkan kemampuan kita dalam menggunakan Constraint Layout, mari coba mengimplementasikan Constraint untuk membangun sebuah tampilan UI, dibawah ini.

https://pin.it/7hF7h4O

Pada part ini saya akan mencoba menggunakan createRefs() dalam membangun layout diatas dengan menggunakan Constraint Layout. createRefs() adalah sebuah fungsi yang membuat objek ConstraintRef. Objek ConstraintRef ini digunakan untuk mereferensikan tampilan yang akan dibatasi dalam layout.

Namun sebelumnya buatlah package dengan nama widget dan tambahkan sebuat kotlin file dengan nama WidgetCustom. Tambahkan beberapa widget custom yang nantinya akan digunakan untuk membangun layout diatas, dengan code dibawah ini.

@Composable
fun CircleBackground(modifier: Modifier, colors: Color, size: Int) {
Canvas(modifier = modifier.size(size.dp), onDraw = {
drawCircle(color = colors)
})
}

@Composable
fun ImageRounded(modifier: Modifier, image: Int) {
Image(
painter = painterResource(id = image), contentDescription = null, modifier = modifier
.clip(
RoundedCornerShape(20)
)
.width(80.dp)
.height(80.dp), contentScale = ContentScale.Crop
)
}

@Composable
fun ImageCircle(modifier: Modifier, image: Int) {
Image(
painter = painterResource(id = image), contentDescription = null, modifier = modifier
.clip(CircleShape)
.width(70.dp)
.height(70.dp), contentScale = ContentScale.Crop
)
}

Selanjutnya buatlah sebuah Composable function dengan nama HomeScreen dan tambahkan Layout Constraint didalan function tersebut seperti dibawah ini.

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PlayingWithComposeTheme {
Surface {
HomeScreen(Modifier)
}
}
}
}
}

@Composable
fun HomeScreen(modifier: Modifier) {
ConstraintLayout(
modifier = Modifier
.background(color = GreenLightBackground)
.fillMaxSize()
) {

}
}

Untuk membuat tampilan semirip mungkin dengan referensi diatas gunakanan beberapa warna dibawah ini.

val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)

val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

val GreenLightBackground = Color(0xFF2D635A)
val GreenDarkBackground = Color(0xFF25544C)
val OrangePickerColor = Color(0xFFFFA500)

Selain warna terdapat beberapa asset gambar yang dapat di unduh pada link berikut, dan tambahkan pada directory drawable.

Selanjutnya tambahkan variable yang akan mewakili ID dari setiap widget dengan value createRefs(). Jumlah variable yang bisa ditambahkan maksimal adalah 16, sehingga codenya akan seperti dibawah ini.

val (circleFirst, circleSecond, whiteBackground, topLine, drawerIcon, title, bottomLine, firstSpacer, firstImage, secondImage, thirdImage, itemFirstTitle, itemFirstDesc, itemSecondTitle, itemSecondDesc, itemThirdTitle) = createRefs()
val (itemThirdDesc, authorTitle, firstAuthorImage, firstAuthorName, secondAuthorImage, secondAuthorName, thirdAuthorImage, thirdAuthorName) = createRefs()

Dan keseluruhan codenya, seperti dibawah ini.

Dan saat di compile akan menghasilkan tampilan seperti dibawah ini.

Untuk detail project anda dapat mengunjungi github repo berikut, lalu pilih branch handpicked-app.

Sekian untuk series kali ini, semoga bermanfaat dan sampai jumpa di next series!!

--

--