Kullanıcı Arayüz Tasarımı iyi olan bir proje, müşteriyi çekmesinin yanı sıra yazılımcıların proje tasarımını oluşturma süreçlerini kolaylaştırmak ve hızlandırmak da çok önemlidir.
Bu makale de Android UI‘yı Jetpack Compose kullanarak daha az kod, güçlü araçlar, sezgisel Kotlin API’lar ile hızlı bir şekilde nasıl uygulama geliştireceğimizi örnekleyeceğim.
Jetpack Compose ve Avantajları Nedir?
Jetpack Compose, Android Studio Arctic Fox sürümü ile gelen modern bir Android UI araç takımıdır.
Android view hiyerarşisi, bir UI widget ağacı (findViewById() vb. işlevler) olarak temsil edilmiştir. Uygulamanın durumu değiştikçe, mevcut verileri görüntülemek için UI hiyerarşisinin güncellenmesi gerekmektedir. Görünümleri manuel olarak güncellemek hata olasılığını arttırıyordu.
Compose ise temel veriler değiştiğinde sizin için kullanıcı arayüzü hiyerarşisini otomatik olarak güncelleyerek kullanıcı arayüzlerini kolay ve hızlı bir şekilde oluşturmanızı kolaylaştırır. Aynı zamanda tüm hata sınıflarından kurtulup, basit kod düzeniyle kolay bakım sağalabilirsiniz.
Flutter’da kullandığınız gibi yazdığınız kodu anında görüntüleyebilirsiniz. UI ve client kodlarınızı Kotlin ile bir alanda kodlayabilirsiniz. Bu şekilde tasarımlarınızı daha hızlı yapabilirsiniz.
Jetpack Compose ile ilgili konuları adım adım örnekleyerek aşağıda görünümü oluşturacağız.
Compose kullanabilmek için ilk önce Android Studio Arctic Fox veya Arctic Fox sonrasında çıkan en güncel sürümü indirmeniz gerekir. Android Studio’da File > New yolunu takip edip New Project ekranında Empty Compose Activity arayüz taslağını seçmelisiniz.
Composable Fonksiyonlar
Projenizi oluşturduğunuzda MainActivity sınıfında @Composable etiketini kullanan fonksiyonlar göreceksiniz. Greeting fonksiyonu bir örnektir. Bu fonksiyonda verilen parametreden gelen String’i gösteren bir UI hiyerarşisi bölümü üretecektir.
1 2 3 4 |
@Composable private fun Greeting(name: String) { Text(text = "Hello $name!") } |
setContent yardımıyla arayüzde göstermek istediğimiz String değerini göstereceğiz. Fakat eski yöntemdeki gibi bir Xml dosyası kullanmayacağız. Bunun yerine Composable fonksiyonunu kullanacağız.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { JetpackComposeBasicTheme { //surface container arkaplan rengi atamak icin kullanıldı Surface(color = MaterialTheme.colors.background) { Greeting("Android") } } } } } |
JetpackComposeBasicTheme, Composable işlevlerine stil vermenin bir yoludur. Bununla ilgili daha fazla bilgiyi Uygulamanızın Temalarını Oluşturma bölümünde göreceksiniz.
Android Studio önizlemesini kullanmak için herhangi bir parametresiz Composable fonksiyonuna @Preview etiketini ekleyerek projenizi oluşturmanız yeterlidir.
1 2 3 4 5 6 7 |
@Preview(showBackground = true) @Composable fun DefaultPreview() { JetpackComposeBasicTheme { Greeting("Android") } } |
Greeting fonksiyonunda atanan yazının arka plan oluşturmak ve rengini değiştirmek isterseniz Surface kullanmanız gerekir. Örnek,
1 2 3 4 5 6 |
@Composable private fun Greeting(name: String) { Surface(color = MaterialTheme.colors.primary) { Text (text = "Hello $name!") } } |
Modifier
Modifier, Compose UI öğelerini nasıl düzenleyeceğimizi ve görüntüleyeceğimizi sağlayan bir sınıftır. Compose UI öğelerine (rows, column, text veya buttons elementlere) backgrounds, padding ve click event listeners özellikleriyle ilgili değişiklikler sağlar. Örneğin, Text elementine padding özelliği ekleyelim.
1 2 3 4 5 6 7 8 9 10 11 |
import androidx.compose.foundation.layout.padding import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp ... @Composable private fun Greeting(name: String) { Surface(color = MaterialTheme.colors.primary) { Text(text = "Hello $name!", modifier = Modifier.padding(24.dp)) } } |
Çıktısı:
MyApp adında bir Composable oluşturup, Greeting fonksiyonumuzu içinde çağıralım.
1 2 3 4 5 6 |
@Composable private fun MyApp() { Surface(color = MaterialTheme.colors.background) { Greeting("Android") } } |
onCreate fonksiyonunun içinde MyApp composable çağıralım ve Preview da MyApp fonksiyonun içeriğini görüntüleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.padding import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.smality.jetpackcomposebasic.ui.theme.JetpackComposeBasicTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { JetpackComposeBasicTheme { MyApp() } } } } @Composable private fun MyApp() { Surface(color = MaterialTheme.colors.background) { Greeting("Android") } } @Composable private fun Greeting(name: String) { Surface(color = MaterialTheme.colors.primary) { Text(text = "Hello $name!", modifier = Modifier.padding(24.dp)) } } @Preview(showBackground = true) @Composable private fun DefaultPreview() { JetpackComposeBasicTheme { MyApp() } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import androidx.compose.foundation.layout.Column ... @Composable private fun Greeting(name: String) { Surface(color = MaterialTheme.colors.primary) { //Hello ve Android kelimelerini alt alta yerleştirelim. Column(modifier = Modifier.padding(24.dp)) { Text(text = "Hello,") Text(text = name) } } } |
Compose ve Kotlin
Composable fonksiyonlarını Kotlin’deki diğer işlevler gibi kullanabilirsiniz. Örneğin, for döngüsüyle Column özelliğine öğeler ekleyelim. Örnek görüntümüz;
1 2 3 4 5 6 7 8 |
@Composable fun MyApp(names: List<String> = listOf("World", "Compose")) { Column { for (name in names) { Greeting(name = name) } } } |
Henüz Composable boyutuna herhangi bir boyut belirlemedik. Bu yüzden içerik kadar bir alan kapsamaktadır. @Preview bölümünde widthDp özelliği ekleyip alanı genişletelim.
1 2 3 4 5 6 7 |
@Preview(showBackground = true, widthDp = 320) @Composable fun DefaultPreview() { JetpackComposeBasicTheme { MyApp() } } |
Eğer içeriğin bulunduğu alanın genişliği maksimum değeri kadar kapsamasını istiyorsanız Modifier sınıfının fillMaxWidth özelliğini kullanmalısınız. Örnek görüntü;
Örnek görüntünün kodu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Composable fun MyApp(names: List<String> = listOf("World", "Compose")) { Column(modifier = Modifier.padding(vertical = 4.dp)) { for (name in names) { Greeting(name = name) } } } @Composable private fun Greeting(name: String) { Surface( color = MaterialTheme.colors.primary, modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp) ) { //max genişlik alanı kadar yayılma ve padding Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) { Text(text = "Hello, ") Text(text = name) } } } |
Button Ekleme
Bu bölümde Row alanının en sonuna button eklemek istiyorum. Örnek görüntü,
İlk öncelikle Row sonuna button ya da başka nesne eklemek istiyorsak Modifier sınıfının weight özelliğinden faydalanmalıyız. Bu özelliği Column içinde tanımlayacağız. Sonrasında OutlinedButton ile Button görünümü ve tıklama eventini yazıyoruz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Composable private fun Greeting(name: String) { Surface( color = MaterialTheme.colors.primary, modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp) ) { Row(modifier = Modifier.padding(24.dp)) { //Button nesnesini kapsama alanın sonuna eklemek icin weight kullanıyoruz Column(modifier = Modifier.weight(1f)) { Text(text = "Hello, ") Text(text = name) } //Button görünümü ve tıklama event OutlinedButton( onClick = { /* TODO */ } ) { Text("Show more") } } } } |
Şuana kadarki örneklerde statik, etkileşimi olmayan örnekler yaptık. Bu bölümde ise butona tıkladığında genişleyen bir alan sağlayacağız. Bölüm sonundaki örneğin görüntüsü;
İlk önce butona tıklandığı anda buton yazısının Show less olduğu anı oluşturalım. Bu sürece başlarken Tıkladıktan sonra alanı yeniden boyutlandırma işlemine geciş yapacağımızdan butonun Show less anını unutmaması için remember özelliğini başta tanımlamamız gerekir. Örnek görüntü,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Composable private fun Greeting(name: String) { val expanded = remember { mutableStateOf(false) } Surface( color = MaterialTheme.colors.primary, modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp) ) { Row(modifier = Modifier.padding(24.dp)) { Column(modifier = Modifier.weight(1f)) { Text(text = "Hello, ") Text(text = name) }//remember özelliğinin kontrol ederek(expanded değişkeni) tıkladığında show less yazdırıyoruz OutlinedButton( onClick = { expanded.value = !expanded.value } ) { Text(if (expanded.value) "Show less" else "Show more") } } } } |
expanded değişkeni dolu ise yani Show less olduğu anda alanın genişlemesini için extraPadding değişkenine alan genişlik değerini dp cinsinden atayacağız.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@Composable private fun Greeting(name: String) { val expanded = remember { mutableStateOf(false) } val extraPadding = if (expanded.value) 48.dp else 0.dp Surface( color = MaterialTheme.colors.primary, modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp) ) { Row(modifier = Modifier.padding(24.dp)) { //extraPadding değişkenindeki dp değeri kadar padding veriyoruz Column(modifier = Modifier .weight(1f) .padding(bottom = extraPadding) ) { Text(text = "Hello, ") Text(text = name) } OutlinedButton( onClick = { expanded.value = !expanded.value } ) { Text(if (expanded.value) "Show less" else "Show more") } } } } |
1 |
names: List<String> = List(40) { "$it" } |
Bu kodu Greetings fonksiyonuna parametre olarak tanımlıyoruz. Kaydırılabilir bir sütun görüntülemek için bir LazyColumn kullanıyoruz. LazyColumn, ekranda yalnızca görünen öğeleri işleyerek büyük bir liste oluştururken performans kazanımlarına olanak tanır.
1 2 3 4 5 6 7 8 |
@Composable private fun Greetings(names: List<String> = List(40) { "$it" } ) { LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) { items(items = names) { name -> Greeting(name = name) } } } |
Örnek çıktısı:
animateDpAsState, animasyonu özelleştirmenize olanak tanıyan animasyonSpec parametresini alır. Yaylanma etkisi yaratan animasyonu (spring-based animation) ekleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Composable private fun Greeting(name: String) { var expanded by remember { mutableStateOf(false) } val extraPadding by animateDpAsState( if (expanded) 48.dp else 0.dp, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow ) ) //animasyon bitiş Surface( ... Column(modifier = Modifier .weight(1f) .padding(bottom = extraPadding.coerceAtLeast(0.dp)) ... ) } |
Örneğin aşağıdaki resimde numaraların olduğu text alanı boyutu büyümüş ve bold özelliği eklenmiş.
Örnek kod,
1 2 3 4 5 |
Text(text = name, style = MaterialTheme.typography.h4.copy( fontWeight = FontWeight.ExtraBold ) ) |
Tema Rengi Değiştirme
ui dizini içerisinde bulunan Color.kt sınıfında tanımlanmış renkler kullandığınız tema renkleridir. Color.kt sınıfına renk kodu ekleyelim.
1 2 3 4 |
val Navy = Color(0xFF073042) val Blue = Color(0xFF4285F4) val LightBlue = Color(0xFFD7EFFE) val Chartreuse = Color(0xFFEFF7CF) |
Theme.kt adlı tema sınıfında LightColorPalette alanında bu renkleri kullanalım.
1 2 3 4 5 6 |
private val LightColorPalette = lightColors( surface = Blue, onSurface = Color.White, primary = LightBlue, onPrimary = Navy ) |
Tema ve stilleri değişmiş uygulamamızın görüntüsü:
Projemizin kodlarına github linkinden ulaşabilirsiniz.
Kaynak:
https://developer.android.com/codelabs/jetpack-compose-basics#0