Compose’daki Layoutlar ve Temel Düzenler

Ömer Akkoyun
KoçSistem
Published in
6 min readNov 27, 2023

Yeni bir yazıyla herkese merhaba 😊
Bu yazıda, compose ile örnek bir ekranı ele alıp bunu adım adım nasıl yapabileceğimize bakacağız ve farklı düzen türlerini nasıl oluşturulacağını göreceğiz.
Not: Compose ile ilgili basic konuları daha önce görmediyseniz, önce bu linkteki öğretici makaleyi okumanızı tavsiye ederim.

Haydi başlayalım…
Öncelikle örnek ekranımıza bakalım;

Örnek tasarım portrait(dikey)
Örnek tasarım landscape(yatay)

Şimdi bu ekranı analiz etmeye başlayalım 😎

🔴 En yüksek soyutlama seviyesinde, bu tasarımı iki parçaya ayırabiliriz:

🔸 Ekranın içeriği
🔹 Alt gezinme çubuğu

Content ve Bottom Navigation

🟣 Şimdi de Ekran İçeriğini analiz edelim;

Ekran içeriği de üç alt parçayı içeriyor bunlar;
🟠 Arama çubuğu bölümü.
🟡 “Align your body” adlı bir bölüm.
🟢 “Favorite Collections” adlı bir bölüm.

Analizlerimizi yaptığımıza göre artık View’ları yapmaya başlayabiliriz.

🟤 İlk olarak SearchBar ile başlayalım, bütün viewları ayrı ayrı oluşturup en sonunda birleştireceğiz.

@Composable
fun SearchBar(modifier: Modifier = Modifier) {
var text by remember { mutableStateOf(TextFieldValue("")) }

TextField(
value = text,
onValueChange = { text = it},
leadingIcon = { Icon(imageVector = Icons.Default.Search, contentDescription = null) },
colors = TextFieldDefaults.colors(
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
focusedContainerColor = MaterialTheme.colorScheme.surface
),
placeholder = { Text(stringResource(R.string.placeholder_search)) },
modifier = modifier
.fillMaxWidth()
.heightIn(min = 56.dp)
)
}

🔵 Şimdi de AlignYourBodyElement item view oluşturalım.

item view
@Composable
fun AlignYourBodyElement(
@DrawableRes drawable: Int,
text: String,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(88.dp)
.clip(CircleShape)
)
Text(
text = text,
modifier = Modifier.paddingFromBaseline(top = 24.dp, bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}

Artık bunu bir liste içinde gösterebiliriz, bunun için LayzRow kullanacağız ;

LayzRow
data class AlignYourBodyItem(val drawable: Int, val text: String)
@Composable
fun AlignYourBodyListRow(
alignYourBodyData : List<AlignYourBodyItem>,
modifier: Modifier = Modifier
) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = 16.dp),
modifier = modifier
) {
items(alignYourBodyData) { item ->
AlignYourBodyElement(drawable = item.drawable, text = item.text)
}
}
}

Burada her item için sabit boşluk vermek için Arrangement.spacedBy() kullandık, bunun yanında farklı Arrangement özelliklerini kullanabilirsiniz.

Row Arrangement
Column Arrangement

🟢 Şimdi de FavoriteCollectionCard item view oluşturalım.

FavoriteCollectionCard item
@Composable
fun FavoriteCollectionCard(
@DrawableRes drawable: Int,
text: String,
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.large,
color = colorResource(id = R.color.bg_favorite),
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.width(255.dp)
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(80.dp)
)
Text(
text = text,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
}

Şimdi de bu view’ı LazyHorizontalGrid içinde liste şeklinde gösterelim;

LayzHorizontalGrid
data class FavoriteCollectionItem(val drawable: Int, val text: String)

@Composable
fun FavoriteCollectionsGrid(
favoriteCollectionsData:List<FavoriteCollectionItem>,
modifier: Modifier = Modifier
) {

LazyHorizontalGrid(
rows = GridCells.Fixed(2),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier.height(168.dp)
) {
items(favoriteCollectionsData.size) { item ->
FavoriteCollectionCard(favoriteCollectionsData[item].drawable, favoriteCollectionsData[item].text, Modifier.height(80.dp))
}
}
}

⚫️ Şimdi sıra bottom navigaiton bar oluşturmakta.

@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
NavigationBar(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
modifier = modifier
) {
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.Home,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_home))
},
selected = true,
onClick = {}
)
NavigationBarItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_profile))
},
selected = false,
onClick = {
}
)
}
}

Şimdi en başta gördüğümüz ekranı yapmak için puzzle gibi tüm parçaları birleştirelim ama önce liste içeriklerinin başlıklarının olduğu view’i oluşturalım.

Bunun için HomeSection adında bir composable oluşturalım ve üstte başlık altta içerik olacak şekilde tasarlayalım.

@Composable
fun HomeSection(
title: String,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Column(modifier) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.paddingFromBaseline(top = 40.dp, bottom = 16.dp)
.padding(horizontal = 16.dp)
)
content()
}
}

Ve sonundaaa tüm parçaları birleştirelim 😎

@Composable
fun HomeScreen(
alignYourBodyData : List<AlignYourBodyItem>,
favoriteCollectionsData:List<FavoriteCollectionItem>,
modifier: Modifier = Modifier) {
Column(
modifier.verticalScroll(rememberScrollState())
) {
Spacer(Modifier.height(16.dp))

SearchBar(Modifier.padding(horizontal = 16.dp))

HomeSection("Align your Body") {
AlignYourBodyListRow(alignYourBodyData)
}

Spacer(Modifier.height(16.dp))

HomeSection("Favorite Collections") {
FavoriteCollectionsGrid(favoriteCollectionsData = favoriteCollectionsData)
}

Spacer(Modifier.height(16.dp))
}
}

Bitti mi ? Hayır 😄 …

Portrait ve Landscape ekranlar için nasıl düzenleme yapabileceğimize de bakalım;

Önce dikey bir navigation view oluşturalım;

Dikey Navigaiton (lanscape mod için)
@Composable
private fun SootheNavigationRail(modifier: Modifier = Modifier) {
NavigationRail(
modifier = modifier.padding(start = 8.dp, end = 8.dp),
containerColor = MaterialTheme.colorScheme.background,
) {
Column(
modifier = modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.Home,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_home))
},
selected = true,
onClick = {}
)
Spacer(modifier = Modifier.height(8.dp))
NavigationRailItem(
icon = {
Icon(
imageVector = Icons.Default.AccountCircle,
contentDescription = null
)
},
label = {
Text(stringResource(R.string.bottom_navigation_profile))
},
selected = false,
onClick = {}
)
}
}
}

Content zaten hazırdı geriye sadece dikey navigaiton ile birleştirmek kalıyor;

@Composable
fun MyAppLandscape(alignYourBodyData : List<AlignYourBodyItem>,
favoriteCollectionsData:List<FavoriteCollectionItem>) {
BasicLayoutsTheme {
Surface(color = MaterialTheme.colorScheme.background) {
Row {
SootheNavigationRail()
HomeScreen(alignYourBodyData, favoriteCollectionsData)
}
}
}
}

Şimdi Portrait dikey ve Landscape yatay modları için kontrol edip ekranı buna göre hazırlayalım. Bu kontrolü yapmak için WindowWidthSizeClass ile ekranın o anki durumunu yakalayabiliriz. Aşağıdaki resimde ekran örneklerini görebilirsiniz.

@Composable
fun MyApp(alignYourBodyData : List<AlignYourBodyItem>, favoriteCollectionsData:List<FavoriteCollectionItem>,windowSize: WindowSizeClass) {
when (windowSize.widthSizeClass) {
WindowWidthSizeClass.Compact -> {
MyAppPortrait(alignYourBodyData,favoriteCollectionsData)
}
WindowWidthSizeClass.Expanded -> {
MyAppLandscape(alignYourBodyData,favoriteCollectionsData)
}
}
}

Ve sonunda tamda istediğimiz gibi oldu 😊

Dikey
Yatay

Son:
Bu yazıda örnek bir ekranın nasıl hazırlandığını, view’ları önce parça parça oluşturup sonra nasıl birleştirildiğini ve ekranların portrait ve landscape görünümleri için nasıl kontrol edildiğini ve buna göre ekranın nasıl hazırlandığını görmüş oldunuz, bir sonraki yazıda görüşmek üzere,

iyi kodlamalar 😊

--

--