Android Jetpack Compose – Staggered Grid Tipli Listeleme

Klasik Android RecyclerView görüntülerinden olan StaggeredGridView ile Pinterest gibi uygulamalarda içerik boyutuna göre gridin yüksekliğinin değişebildiğini görmüşsünüzdür. Yüklenen resimin yüksekliği ne ise, satır yüksekliği o içerik kadar oluyor.

Bunu dilerseniz hazır bir kütüphane ile hızlıca nasıl yapabileceğinizi öğrenmek istiyorsanız, şu kütüphaneye göz atın derim;

LazyStaggeredGrid

Siz de benim gibi tamamen kendiniz anlayarak yazma taraftarıysanız doğru yerdesiniz 🙂 Bu makalede bu işlemi tamamen kendimiz üreteceğiz.

Unsplash üzerinden indirdiğim 10 adet yatay ve dikey fotoğrafı projemin drawable klasörüne atıyorum.

Surface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colors.background) {
                    val images = listOf(
                        R.drawable.p1,
                        R.drawable.p9,
                        R.drawable.p2,
                        R.drawable.p3,
                        R.drawable.p4,
                        R.drawable.p5,
                        R.drawable.p6,
                        R.drawable.p7,
                        R.drawable.p8,
                        R.drawable.img,
                    )
                    StaggeredGridView(images)
}

ardından StaggeredGridView adlı bir fonksiyon oluşturup listeyi argüman olarak bu fonksiyonun içine atıyorum.

@Composable
fun StaggeredGridView(images:List<Int>) { }

bu fonksiyonun içerisine Column üreteceğim.

@Composable
fun StaggeredGridView(images:List<Int>) {
    Column(
        modifier = Modifier
            .fillMaxSize()
    ) {

    }
}

bu Column’ı ana kapsayıcı olarak kullanacağız. Modifier fillMaxSize ile tam sayfa genişliğini kapsamasını istedim. Bu Column’ın içerisinde tekrar bir Column üretiyorum. Bunun görevi ise içeride listelenecek olan verilerin sayfanın dört köşesinden de dış boşluğu olabilmesi için.

@Composable
fun StaggeredGridView(images:List<Int>) {
    Column(
        modifier = Modifier
            .fillMaxSize()
    ) {
         Column(
            modifier = Modifier
                .verticalScroll(rememberScrollState())
                .padding(5.dp)
        ) {

        }
    }
}

Burada verticalScroll ile Column’ın yatay olarak verileri göstereceği ve scroll edebileceğini belirttik rememberScrollState ile de kaldığımız konumu yakalamış oluyoruz. padding ile kenarlardan 5 dp’lik boşluklar vermiş olduk.

Şimdi verileri gösterebilmek için öncelikle StaggeredGrid formatında listeleyebilmek adına bir fonksiyon üreteceğiz.

@Composable
fun StaggeredVerticalGrid(
    modifier: Modifier = Modifier,
    numColumns: Int = 2,
    content: @Composable () -> Unit
) {
// kodlar aralığa yazılacak
}

numColumns adlı değişken gridin yan yana kaç kolon gösterebileceğini belirtebileceğim alandır. Buraya belirtilmezse 2 değerini veriyorum.

Layout(content = content,modifier = modifier) {
 measurable, constraints ->
        // Kolonun genişliğini tutabilmek için değişken üretelim. Bir kolonun genişliği constraint'in kapsadığı maksimum genişliğin, numColumns değerine bölünmesiyle bulunur. 2 kolon gösterecekse numColumns 2 değerini argümandan elde edecektir.
        val columnWidth = (constraints.maxWidth / numColumns)

        // Her elemanın constraintinin maxWidth'ini columnWidth değişken değeri ile güncelliyoruz ki her kolon daha geniş boyuta taşamasın.
        val itemConstraints = constraints.copy(maxWidth = columnWidth)

        // kolon yükseklikleri için de aynı işlemi uyguluyoruz.
        val columnHeights = IntArray(numColumns) { 0 }

        // aşağıda placeable oluşturuyoruz. measurable ile her bir elemanın ölçülebilirliğini sağlıyoruz.
        val placeables = measurable.map { measurable ->
            // bu metodun sonrasında üreteceğimiz testColumn metoduna columnHeights'i iletiyoruz.
            val column = testColumn(columnHeights)
            // itemConstraints'den elde ettiğimiz değeri measurable'a iletiyoruz.
            val placeable = measurable.measure(itemConstraints)

            // aşağıdaki satırda sütun yüksekliğimizi artırıyoruz.
            columnHeights[column] += placeable.height
            placeable
        }

measurable.map scope’u bitişinden sonra devam edelim.

val height =
            columnHeights.maxOrNull()?.coerceIn(constraints.minHeight, constraints.maxHeight)
                ?: constraints.minHeight

Şimdi asıl işin en civcivli kısmına geldik, veri ya ilk kolonda yada ikinci kolonda olacak. Bunun hesaplanabilirliği için yazacağımız bu kodlar hem nesneleri iterate edecek hem de konumlandırılmasını sağlayacak.

   // Aşağıda layout için yükseklik ve genişliğimizi belirtiyoruz.
   layout(width = constraints.maxWidth,height = height) {
            // Aşağıdaki satırda y sütunu pointer'i için değişken oluşturuyoruz.
            val columnYPointers = IntArray(numColumns) { 0 }

            // Aşağıdaki satırda her bir eleman için x ve y koordinatlarını ayarlıyoruz. Bunu placeable nesnesinin place fonksiyonu sağlıyor.
            placeables.forEach { placeable ->
                // aşağıdaki satırda testColumn fonksiyonunu çağırarak column indexini alıyoruz.
                val column = testColumn(columnYPointers)
                placeable.place(
                    x = columnWidth * column,
                    y = columnYPointers[column]
                )
                // aşağıdaki satırda, elde ettiğimiz y pointerini arttırıyoruz.
                columnYPointers[column] += placeable.height
            }
   }
}

yukarıdaki kodlarımız içerisinde de kullandığımız testColumn fonksiyonunu üretiyoruz.

private fun testColumn(columnHeights: IntArray): Int {
    // minimum yükseklik için bir değişken oluşturuyoruz.
    var minHeight = Int.MAX_VALUE
    // columnIndex için bir değişken oluşturuyoruz.
    var columnIndex = 0
    // her bir index için column height'ini ayarlıyoruz.
    columnHeights.forEachIndexed { index, height ->
        if (height < minHeight) {
            minHeight = height
            columnIndex = index
        }
    }
    // en son olarak sütun indeximizi döndürüyoruz.
    return columnIndex
}

Şimdi tekrardan en başlara StaggeredGridView’i oluşturdğumuz kısma dönüp, iç içe yazdığımız 2. Column’ın içine geliyoruz;

@Composable
fun StaggeredGridView(images:List<Int>) {

    Column(
        modifier = Modifier
            .fillMaxSize()
    ) {
        Column(
            modifier = Modifier
                .verticalScroll(rememberScrollState())
                .padding(5.dp)
        ) {
            // Kendi ürettiğimiz StaggeredGrid yapısını çağırıyoruz.
            StaggeredVerticalGrid(
                numColumns = 2,
                modifier = Modifier.padding(5.dp)
            ) {
                // Her kolonun resim göstermesini istiyoruz.
                images.forEach { img ->
                    // daha estetik durabilmesi için card ekliyoruz.
                    Card(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(5.dp),
                        elevation = 10.dp,
                        shape = RoundedCornerShape(10.dp)
                    ) {
                        
                        Column(
                            modifier = Modifier
                                .fillMaxSize()
                                .align(Alignment.CenterHorizontally),
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {
                            // column içerisine Image ekliyoruz, isteğe bağlı text de ekleyebilirsiniz.
                            Image(
                                // resmi drawable klasöründen çağırıyoruz.
                                painterResource(id = img),
                                contentDescription = "images",
                                alignment = Alignment.Center,
                            )
                        }
                    }
                }
            }
        }
    }
}

Hepsi bu kadar, ancak zahmetli 🙂 Bu nedenle bir kenara kaydetmeyi unutmayın kodları, ezbere sürekli yazmak imkansız olacaktır.

Şimdi neden yazır kütüphane değilde bunu kullandık diye düşünenlere yanıt olsun, müdahale edilebilirliğini kodun daha da arttırmış olduk.

İyi çalışmalar dilerim.

Çıktı:

You may also like...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.