This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!

In the first part of Jetpack Compose, we learned how to Compose your app’s layout, custom layout, custom view, animation, and gestures, all of which can be done in one page.

Common navigation

For Navigation in Jetpack Compose, you can use the Navigation component in Jetpack to navigate

Implementation "androidx. Navigation: navigation - compose: 2.4.0 - alpha01"Copy the code

Navigation uses two important objects, NavHost and NavController.

  • NavHost is used to host pages and manage navigation diagrams
  • NavController is used to control navigation and parameter rollback and so on, right

The navigation path is represented as a string, and when navigated to a page using NavController, the page is automatically reorganized within NavHost.

Have a little chestnut to practice

@Composable
fun MainView(a){
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = "first_screen"){
        composable("first_screen"){
            FirstScreen(navController = navController)
        }
        composable("second_screen"){
            SecondScreen(navController = navController)
        }
        composable("third_screen"){
            ThirdScreen(navController = navController)
        }
    }
}
Copy the code
  • throughrememberNavController()The navController method creates a navController object
  • Create a NavHost object, pass in the navController and specify the home page
  • Add pages to and from NavHost via the Composable () method, where the string in the constructor represents the path to the page, followed by the specific page as the second parameter.

Let’s write out the three pages, each with a button to continue the other navigation

@Composable
fun FirstScreen(navController: NavController){
    Column(modifier = Modifier.fillMaxSize().background(Color.Blue),
           verticalArrangement = Arrangement.Center,
           horizontalAlignment = Alignment.CenterHorizontally
        ) {
        Button(onClick = {
            navController.navigate("second_screen")
        }) {
            Text(text = "I am First to go to Second")}}}@Composable
fun SecondScreen(navController: NavController){
    Column(modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally) {
        Button(onClick = {
            navController.navigate("third_screen")
        }) {
            Text(text = "I am Second click me to Third")}}}@Composable
fun ThirdScreen(navController: NavController){
    Column(modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally) {
        Button(onClick = {
            navController.navigate("first_screen")
        }) {
            Text(text = "I am Third click on me to first")}}}Copy the code

Create a new page with @composable (Composable) and only one activity (@composable).

For example, in Compose, you need to upload parameters from one page to another.

Parameter passing must have sender and receiver, navController is the sender and NavHost is the receiver. Configure the parameter placeholder in NavHost and the method to receive the parameter.

@Composable
fun MainView(a){
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = "first_screen"){
        composable("first_screen"){
            FirstScreen(navController = navController)
        }
        composable("second_screen/{userId}/{isShow}".// By default, all arguments are parsed as strings. If not, specify type separately
        arguments = listOf(navArgument("isShow"){type = NavType.BoolType}) ){ backStackEntry -> SecondScreen(navController = navController, backStackEntry.arguments? .getString("userId"), backStackEntry.arguments? .getBoolean("isShow")!! } composable("third_screen? selectable={selectable}",
        arguments = listOf(navArgument("selectable"){defaultValue = "Ha ha ha I am the default for optional arguments"})){ ThirdScreen(navController = navController,it.arguments? .getString("selectable"))
        }
        composable("four_screen"){
            FourScreen(navController = navController)
        }
    }
}
Copy the code

In the code above, Receiving parameters directly in behind the address of the page to add a placeholder similar second_screen / {username} / {isShow}, then receive the arguments by the arguments parameter = listOf (navArgument (” isShow “) {type . = NavType BoolType}). You can also define default values for parameters using defaultValue.

By default, all arguments will be parsed as strings and you need to specify type separately if they are not.

Navigate (“second_screen/12345/true”) to add parameters to the previous page, as in navController.navigate(“second_screen/12345/true”)

@Composable
fun FirstScreen(navController: NavController){
    Column(modifier = Modifier
        .fillMaxSize()
        .background(Color.Blue),
           verticalArrangement = Arrangement.Center,
           horizontalAlignment = Alignment.CenterHorizontally
        ) {
        Button(onClick = {
            navController.navigate("second_screen/12345/true"){
            }
        }) {
            Text(text = "I am First to go to Second")
        }
        Spacer(modifier = Modifier.size(30.dp))
    }
}
@Composable
fun SecondScreen(navController: NavController,userId:String? ,isShow:Boolean){
    Column(modifier = Modifier
        .fillMaxSize()
        .background(Color.Green),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally) {
        Button(onClick = {
            navController.navigate("third_screen? Selectable = Test optional parameter"){
                popUpTo(navController.graph.startDestinationId){saveState = true}
            }
        }) {
            Text(text = "I am Second click me to Third")
        }
        Spacer(modifier = Modifier.size(30.dp))
        Text(text = "arguments ${userId}")
        if(isShow){
            Text(text = "Test Boolean values")}}}@Composable
fun ThirdScreen(navController: NavController,selectable:String?).{
    Column(modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally) {
        Button(onClick = {
            navController.navigate("first_screen")
        }) {
            Text(text = "I am Third click on me to first")
        }
        Spacer(modifier = Modifier.size(30.dp))
        Button(onClick = {
            navController.navigate("four_screen")
        }) {
            Text(text = "I am Third click on me to four") } selectable? .let { Text(text = it) } } }Copy the code

Results the following

The life cycle

Since the new interface doesn’t use activities or fragments anymore, life cycles in activities and fragments can be very useful, such as creating and destroying certain objects. So what is the life cycle of each composite function in Jetpack Compose?

The life cycle of composable items is simpler than the life cycle of views, activities and fragments, typically entering a composition, performing zero or more reorganizations, and exiting a composition. The Composable functions decorated with @composable do not have built-in life cycle functions. To listen to their life cycle, you need to use the Effect API

  • LaunchedEffect: called the first time the Compose function is called
  • DisposableEffect: There is an internal onDispose() function called when the page exits
  • SideEffect: compose calls this method each time the SideEffect: compose function is executed

Let’s try a little example

@Composable
fun LifecycleDemo(a) {
    val count = remember { mutableStateOf(0) }

    Column {
        Button(onClick = {
            count.value++
        }) {
            Text("Click me")
        }

        LaunchedEffect(Unit){
                Log.d("Compose"."onactive with value: " + count.value)
            }
        DisposableEffect(Unit) {
            onDispose {
                Log.d("Compose"."onDispose because value=" + count.value)
            }
        }
        SideEffect {
            Log.d("Compose"."onChange with value: " + count.value)
        }
        Text(text = "You have clicked the button: " + count.value.toString())
    }
}
Copy the code

The effect is as follows:

Now, to modify the previous example slightly, we’ll put LaunchedEffect and DisposableEffect together in an if statement.

@Composable
fun LifecycleDemo(a) {
    val count = remember { mutableStateOf(0) }

    Column {
        Button(onClick = {
            count.value++
        }) {
            Text("Click me")}if (count.value < 3) {
            LaunchedEffect(Unit){
                Log.d("Compose"."onactive with value: " + count.value)
            }
            DisposableEffect(Unit) {
                onDispose {
                    Log.d("Compose"."onDispose because value=" + count.value)
                }
            }
        }
        
        SideEffect {
            Log.d("Compose"."onChange with value: " + count.value)
        }
        Text(text = "You have clicked the button: " + count.value.toString())
    }
}
Copy the code

The lifecycle then is to execute the LaunchedEffect function when you first enter the IF statement, and the DisposableEffect method when you leave the IF statement.

At the bottom of the navigation

When it comes to navigation, we have to talk about bottom navigation and top navigation. Bottom navigation is very simple to implement, using the scaffolding provided by JetPack Compose in conjunction with navController and NavHost

@Composable
fun BottomMainView(a){
    val bottomItems = listOf(Screen.First,Screen.Second,Screen.Third)
    val navController = rememberNavController()
    Scaffold(
        bottomBar = {
            BottomNavigation {
                val navBackStackEntry by navController.currentBackStackEntryAsState()
                valcurrentRoute = navBackStackEntry? .destination?.route bottomItems.forEach{screen -> BottomNavigationItem( icon = { Icon(Icons.Filled.Favorite,"") },
                        label = { Text(stringResource(screen.resourceId)) },
                        selected = currentRoute == screen.route,
                        onClick = {
                            navController.navigate(screen.route){
                                // When the bottom navigation leads to a page that is not on the home page, press the phone's back button to return to the home page
                                popUpTo(navController.graph.startDestinationId){saveState = true}
                                As the name suggests, avoid creating multiple instances at the top of the stack, as in the activity launch mode SingleTop
                                launchSingleTop = true
                                // Save page state while switching state
                                restoreState = true
                            }
                        })
                }

            }
        }
    ){
        NavHost(navController = navController, startDestination = Screen.First.route ){
            composable(Screen.First.route){
                First(navController)
            }
            composable(Screen.Second.route){
                Second(navController)
            }
            composable(Screen.Third.route){
                Third(navController)
            }
        }
    }
}
@Composable
fun First(navController: NavController){
    Column(modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "First",fontSize = 30.sp)
    }
}
@Composable
fun Second(navController: NavController){
    Column(modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Second",fontSize = 30.sp)
    }
}
@Composable
fun Third(navController: NavController){
    Column(modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Third",fontSize = 30.sp)
    }
}
Copy the code

Results the following

At the top of the navigation

The top navigation uses two components, TabRow and ScrollableTabRow, which are composed of Tab components one by one inside. TabRow splits the entire width of the screen, and ScrollableTabRow can go beyond the width of the screen and slide, all using the same method.

@Composable
fun TopTabRow(a){
    var state by remember { mutableStateOf(0)}var titles = listOf("Java"."Kotlin"."Android"."Flutter") Column { TabRow(selectedTabIndex = state) { titles.forEachIndexed{index,title -> run { Tab( selected = state == index,  onClick = { state = index }, text = { Text(text = title) }) } } } Column(Modifier.weight(1f)) {
            when (state){
                0 -> TopTabFirst()
                1 -> TopTabSecond()
                2 -> TopTabThird()
                3 -> TopTabFour()
            }
        }
    }
}
@Composable
fun TopTabFirst(a){
    Column(modifier = Modifier.fillMaxSize(), verticalArrangement=Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Java")}}@Composable
fun TopTabSecond(a){
    Column(modifier = Modifier.fillMaxSize(), verticalArrangement=Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Kotlin")}}@Composable
fun TopTabThird(a){
    Column(modifier = Modifier.fillMaxSize(), verticalArrangement=Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Android")}}@Composable
fun TopTabFour(a){
    Column(modifier = Modifier.fillMaxSize(), verticalArrangement=Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Flutter")}}Copy the code

Click on each Tab to switch to a different page, if we want to achieve a similar effect to the ViewPage+TabLayout in the XML layout

How to implement ViewPage effects in Jetpack, Google’s Github provides a semi-official library called pager: github.com/google/acco…

Implementation "com. Google. Accompanist: accompanist - pager: 0.13.0"Copy the code

The library is currently experimental and subject to API changes, but should currently be used with the @experimentalPagerAPI annotation.

@ExperimentalPagerApi
@Composable
fun TopScrollTabRow(a){
    var titles = listOf("Java"."Kotlin"."Android"."Flutter"."scala"."python")
    val scope = rememberCoroutineScope()
    var pagerState = rememberPagerState(
        pageCount = titles.size, / / the total number of pages
        initialOffscreenLimit = 2.// The number of preloads
        infiniteLoop = true.// Infinite loop
        initialPage = 0.// Initial page
    )
    Column {
        ScrollableTabRow(
            selectedTabIndex = pagerState.currentPage,
            modifier = Modifier.wrapContentSize(),
            edgePadding = 16.dp
        ) {
            titles.forEachIndexed{index,title ->
                run {
                    Tab(
                        selected = pagerState.currentPage == index,
                        onClick = {
                            scope.launch {
                                pagerState.scrollToPage(index)
                            }
                                  },
                        text = {
                            Text(text = title)
                        })
                }
            }
        }
        HorizontalPager(
            state=pagerState,
            modifier = Modifier.weight(1f)
        ) {index ->
            Column(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
                Text(text = titles[index])
            }
        }
    }
}
Copy the code

The pagerState.scrollTopage (index) method controls pager scrolling, but it is a suspend decorated method that needs to be run in a coroutine, Using coroutines in jetpack compose you can use the rememberCoroutineScope() method to get the scope of a coroutine in compose

The effect is as follows:

Banner

Pager library introduces Banner effect by the way, and introduces a new library for displaying network pictures, IST – Coil. For JetPack Compose, there are two first-chair IST-Coils and FIRST-chair Glide for the network frame, and first-chair IST-Coils are used here.

Implementation 'com. Google. Accompanist: accompanist - coil: 0.11.1'Copy the code
@ExperimentalPagerApi
@Composable
fun Third(navController: NavController){
    var pics = listOf("https://wanandroid.com/blogimgs/8a0131ac-05b7-4b6c-a8d0-f438678834ba.png"."https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png"."https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png"."https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png")
    Column(modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Third",fontSize = 30.sp)
        var pagerState = rememberPagerState(
            pageCount = 4./ / the total number of pages
            initialOffscreenLimit = 2.// The number of preloads
            infiniteLoop = true.// Infinite loop
            initialPage = 0.// Initial page
        )
        Box(modifier = Modifier
            .fillMaxWidth()
            .height(260.dp)
            .background(color = Color.Yellow)) {
            HorizontalPager(
                state=pagerState,
                modifier = Modifier.fillMaxSize()
            ) {index ->
                Image(modifier = Modifier.fillMaxSize(),
                    painter = rememberCoilPainter(request = pics[index]),
                    contentScale=ContentScale.Crop,
                    contentDescription = "Picture Description")
            }
            HorizontalPagerIndicator(
                pagerState = pagerState,
                modifier = Modifier
                    .padding(16.dp).align(Alignment.BottomStart),
            )
        }
    }
}
Copy the code

Writing pages with Jetpack Compose feels much simpler than using XML, and I expect that in the future Android will use XML layouts less and less like jquary on the front end.