Getting Started With JetPack Compose

kchienja

Kah Di Chienja

Posted on May 26, 2021

Getting Started With JetPack Compose

In this tutorial we are going to discuss few concept in the jetpack compose library which a upcoming ui toolkit for faster development of android UI with kotlin managed by google.

What We are going to cover.

1. Creating Custom Staggered Grid.
2. Creating Custom Column With Composed.
3. Generating ListView.
4. Combining Logic to Have full UI.

Imports

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.example.test2.ui.theme.Test2Theme
import com.google.accompanist.coil.rememberCoilPainter
import kotlinx.coroutines.launch
import kotlin.math.max

Enter fullscreen mode Exit fullscreen mode
  1. Creating custom Staggered Grid
@Composable
fun StaggeredGrid(
    modifier: Modifier = Modifier,
    rows: Int = 3,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->

        // Keep track of the width of each row
        val rowWidths = IntArray(rows) { 0 }

        // Keep track of the max height of each row
        val rowHeights = IntArray(rows) { 0 }

        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.mapIndexed { index, measurable ->
            // Measure each child
            val placeable = measurable.measure(constraints)

            // Track the width and max height of each row
            val row = index % rows
            rowWidths[row] += placeable.width
            rowHeights[row] = max(rowHeights[row], placeable.height)

            placeable
        }

        // Grid's width is the widest row
        val width = rowWidths.maxOrNull()
            ?.coerceIn(constraints.minWidth.rangeTo(constraints.maxWidth)) ?: constraints.minWidth

        // Grid's height is the sum of the tallest element of each row
        // coerced to the height constraints
        val height = rowHeights.sumBy { it }
            .coerceIn(constraints.minHeight.rangeTo(constraints.maxHeight))

        // Y of each row, based on the height accumulation of previous rows
        val rowY = IntArray(rows) { 0 }
        for (i in 1 until rows) {
            rowY[i] = rowY[i - 1] + rowHeights[i - 1]
        }

        // Set the size of the parent layout
        layout(width, height) {
            // x co-ord we have placed up to, per row
            val rowX = IntArray(rows) { 0 }

            placeables.forEachIndexed { index, placeable ->
                val row = index % rows
                placeable.placeRelative(
                    x = rowX[row],
                    y = rowY[row]
                )
                rowX[row] += placeable.width
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In order to view the implementation of what we've just created, let's create a preview

This is the column We want to insert into the staggered Grid.

image

The image should be in your drawable folder.


/* Testing custom staggered Grid. */

@Composable
fun GetImageColumn(text: String){
    val typography = MaterialTheme.typography
    MaterialTheme{
        Column (
            modifier = Modifier.padding(16.dp)
        ){
            Card(
                elevation = 10.dp

            ){
                Image(
                    painter = painterResource(R.drawable.header),
                    contentDescription = null,
                    modifier = Modifier
                        .height(180.dp)
                        .fillMaxWidth()
                        .clip(shape = RoundedCornerShape(4.dp)),
                    contentScale = ContentScale.Crop
                )
            }

            Spacer(Modifier.height(16.dp))
            Card(
                elevation = 10.dp,
                modifier = Modifier.fillMaxWidth()
            ) {
                Column (modifier = Modifier.padding(16.dp)){
                    Text(text = text, style = typography.h4)
                    Text("Nairobi Kenya", style = typography.body2)
                    Text("14 th Feb 2021", style = typography.body2)
                }
            }
        }

    }
}
Enter fullscreen mode Exit fullscreen mode

To Create A Preview, Have the below code.

@Preview(showBackground = true)
@Composable
fun Preview1(){
    Scaffold{
        Column(modifier = Modifier
            .padding(16.dp)
            .verticalScroll(rememberScrollState()),
            content = {
                StaggeredGrid (rows = 2){
                    for (topic in topics) {
                        GetImageColumn(text = topic)
                    }
                }
            }
        )
    }

}
Enter fullscreen mode Exit fullscreen mode

Preview Image: With frames.

image
Preview Image: Without frames.
image

Task One Done, Let's now move to task two.

2. Creating Custom Column With Compose.

@Composable
fun GetCustomColumn(modifier: Modifier, content: @Composable () -> Unit){
    Layout(modifier = modifier, content = content){
            measurables, constraints ->

        val placebles = measurables.map { measurable -> measurable.measure(constraints) }

        var yPosition = 0

        layout(constraints.maxWidth, constraints.maxHeight){
            placebles.forEach{
                placeable -> placeable.placeRelative(x = 0, y = yPosition)
                yPosition += placeable.height
            }
        }

    }
}
Enter fullscreen mode Exit fullscreen mode

For testing our column, we can replace all the column composables that we used in creating the preview to our custom staggered view.

@Preview(showBackground = true)
@Composable
fun Preview1(){
    Scaffold{
        /* New  GetCustomColumn we created*/
        GetCustomColumn(modifier = Modifier
            .padding(16.dp)
            .verticalScroll(rememberScrollState()),
            content = {
                StaggeredGrid (rows = 2){
                    for (topic in topics) {
                        GetImageColumn(text = topic)
                    }
                }
            }
        )
    }

}
Enter fullscreen mode Exit fullscreen mode

Some Preview.
image

Now let's move to the next task.

3. Generating ListView.

here is our list.

val topics = listOf(
    "Arts & Crafts", "Beauty", "Books", "Business", "Comics", "Culinary",
    "Design", "Fashion", "Film", "History", "Maths", "Music", "People", "Philosophy",
    "Religion", "Social sciences", "Technology", "TV", "Writing"
)
Enter fullscreen mode Exit fullscreen mode

List Code.

@Composable
fun SimpleList(){
    val listSize = 100
    // We save the scrolling position with this state
    val scrollState = rememberLazyListState()
    // We save the coroutine scope where our animated scroll will be executed
    val coroutineScope = rememberCoroutineScope()

    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceEvenly
    ) {
        Button(
            onClick = {
                coroutineScope.launch {
                    scrollState.animateScrollToItem(0)
                }
            }
        ) {
            Text(text = "Top")
        }
        Button(
            onClick = {
                coroutineScope.launch {
                    scrollState.animateScrollToItem(listSize - 1)
                }
            }
        ) {
            Text(text = "Bottom")
        }

    }
    LazyColumn(state = scrollState) {
        items(listSize){
            PhotographerCard(numberCount = it)
        }
    }

}


@Preview(showBackground = true)
@Composable
fun LayoutsCodeLabPreview(){
    Test2Theme{
        SimpleList()
    }

}



@Composable
fun PhotographerCard(modifier: Modifier = Modifier , numberCount: Number){

    Card(elevation = 10.dp, ) {
        Row(
            modifier
                .padding(6.dp)
                .clip(RoundedCornerShape(4.dp))
                .background(MaterialTheme.colors.surface)
                .clickable(onClick = { /*TODO Implement click */ })
                .padding(16.dp)
                .fillMaxWidth()

        ){
            Surface(
                modifier = Modifier.size(50.dp),
                shape = CircleShape,
                color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f)

            ) {
                Image(
                    painter = rememberCoilPainter(
                        request = "https://developer.android.com/images/brand/Android_Robot.png"
                    ),
                    contentDescription = "Android Logo",
                    modifier = Modifier
                        .size(50.dp)
                        .padding(8.dp)
                )
//                Icon(Icons.Filled.PersonOutline, modifier = Modifier.padding(8.dp), contentDescription = null)
            }

                Column(
                    modifier
                        .padding(start = 8.dp)
                        .align(Alignment.CenterVertically)
                ) {
                    Text("List View. ", fontWeight = FontWeight.Bold)
                    CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                        Text(text = "$numberCount sec.", style = MaterialTheme.typography.body2)
                    }

                }


        }
    }

}
Enter fullscreen mode Exit fullscreen mode

Preview
image

Lastly Let's combine everything to have some simple UI in action.

/* UI  play */
@Preview(showBackground = true)
@Composable
fun LayoutsCodeLab(){
    Scaffold (
        topBar = {
            TopAppBar(
                title = {
                    Text(text = "Design With Composed", style = MaterialTheme.typography.h5)
                },
                actions = {
                    IconButton(onClick = { /*TODO*/ }) {
                        Icon(Icons.Filled.Favorite, contentDescription = null)
                    }
                }
            )
        }
    ){

        innerPadding -> BodyContent(
        Modifier
            .padding(innerPadding)
            .padding(8.dp))

    }
}
@Composable
fun BodyContent(modifier: Modifier = Modifier){
    Spacer(Modifier.height(10.dp))
    GetCustomColumn(modifier = modifier) {
        Row(modifier = modifier
            .fillMaxWidth()
            .padding(16.dp)
            .size(200.dp)
            .horizontalScroll(rememberScrollState()),
            content = {
                StaggeredGrid {
                    for (topic in topics) {
                        Chip(modifier = Modifier.padding(8.dp), text = topic)
                    }
                }
            }
        )
        PreviewListView1()
    }

}
@Composable
fun PreviewListView1(){
    Scaffold{
        /* New  GetCustomColumn we created*/
        // We save the scrolling position with this state
        val scrollState = rememberLazyListState()

        LazyColumn(state = scrollState) {
            for (topic in topics) {
                items(topics.size) {
                    GetImageColumn(text = topic)
                }
            }
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

Preview.
image



kotlin_test1

Jetpack Compose is a modern toolkit for building native Android UI. It's based on the declarative programming model, so you can simply describe what your UI should look like, and Compose takes care of the rest—as app state changes, your UI automatically updates.








Kah Di Chienja's DEV Community Profile
💖 💪 🙅 🚩
kchienja
Kah Di Chienja

Posted on May 26, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Getting Started With JetPack Compose
jepackcompsed Getting Started With JetPack Compose

May 26, 2021