Jetpack Cmpose 实战之仿微信UI -完成主页(二)

前言

在上一篇文章中,我完成了登陆模块的几个首要页面,在这一篇文章中,我将运用 Jetpack Cmpose 去完成微信的主页的几个tab页首要包含微信,通讯录,发现和我。

页面构成

主页首要由一个Activity和四个组合函数页面,它们的结构如下:

Jetpack Cmpose 实战之仿微信UI -完成主页(二)

页面结构梳理

咱们经过调查发现,主页是经过点击底部的导航栏或许左右滑动切换页面的,在咱们没有运用Compose开发之前,咱们是运用ViewPager + TabLayout的方法完成的,在Compose里有没有这样的组件呢?

底部导航栏的完成

在上一篇文章中,我运用了 Scaffold 脚手架,咱们看看这个脚手架的特点,有没有咱们需求的,Scaffold的特点如下:

Scaffold(
    modifier: Modifier = Modifier,
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable () -> Unit = {},
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    containerColor: Color = MaterialTheme.colorScheme.background,
    contentColor: Color = contentColorFor(containerColor),
    contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
    content: @Composable (PaddingValues) -> Unit
)

因为了解flutter开发,看了这些特点,了解的身影呈现了,没猜错的话就是bottomBar

底部导航栏首要包含称号和图标,接下来我将封装 NavigationItem 目标来运用

/**
 * 主页底部导航栏
 */
data class NavigationItem(
    /**
     * 称号
     */
    val title: String,
    /**
     * 图标
     */
    val icon: ImageVector,
)

这儿的图标我运用的是material自带的,经过 implementation “androidx.compose.material:material-icons-extended:$compose_version” 引入。

接下来界说导航栏的数组

val navList = listOf(
    NavigationItem(
        "微信",
        Icons.Filled.Sms,
    ),
    NavigationItem(
        "通讯录",
        Icons.Filled.Contacts,
    ),
    NavigationItem(
        "发现",
        Icons.Filled.Explore,
    ),
    NavigationItem(
        "我",
        Icons.Filled.Person,
    ),
)

底部导航栏的详细完成

bottomBar = {
    NavigationBar(
        modifier = Modifier.height(60.dp),
        containerColor = Color(ContextCompat.getColor(context, if (selectIndex > 1) R.color.white else R.color.nav_bg)),
    ) {
        navList.forEachIndexed { index, nav ->
            Box(
                contentAlignment = Alignment.Center,
                modifier = Modifier
                    .weight(1f)
                    .fillMaxHeight()
            ) {
                Column(
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Bottom
                ) {
                    Icon(
                        nav.icon, null,
                        modifier = Modifier.size(28.dp),
                        tint = Color(
                            if (selectIndex == index) ContextCompat.getColor(context, R.color.green)
                            else ContextCompat.getColor(context, R.color.gray)
                        )
                    )
                    Text(
                        text = nav.title,
                        fontSize = 12.sp,
                        color = Color(
                            if (selectIndex == index) ContextCompat.getColor(context, R.color.green)
                            else ContextCompat.getColor(context, R.color.gray)
                        )
                    )
                }
            }
        }
    }
},

在这儿边,经过界说 selectIndex 来记录点击的tab,用来切换对应的样式。

看下作用

Jetpack Cmpose 实战之仿微信UI -完成主页(二)

Tab页的完成

经过检查,我并没有看到 ViewPager 这样的组件,可是发现了能够完成类似功用的组件 HorizontalPager,它的结构如下:

HorizontalPager(
    count: Int,
    modifier: Modifier = Modifier,
    state: PagerState = rememberPagerState(),
    reverseLayout: Boolean = false,
    itemSpacing: Dp = 0.dp,
    contentPadding: PaddingValues = PaddingValues(0.dp),
    verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
    flingBehavior: FlingBehavior = PagerDefaults.flingBehavior(
        state = state,
        endContentPadding = contentPadding.calculateEndPadding(LayoutDirection.Ltr),
    ),
    key: ((page: Int) -> Any)? = null,
    userScrollEnabled: Boolean = true,
    content: @Composable PagerScope.(page: Int) -> Unit,
) 

这些特点没有很杂乱的,根本能够经过称号就知道它的作用了,接下来运用 HorizontalPager 来完成咱们的页面切换。

HorizontalPager(
    count = 4,
    state = pageState,
    contentPadding = PaddingValues(horizontal = 0.dp),
    modifier = Modifier.fillMaxSize()
) { page ->
    when(page) {
        0 -> Text(text = "这是微信页")
        1 -> Text(text = "这是通讯录页")
        2 -> Text(text = "这是发现页")
        3 -> Text(text = "这是我页")
    }
}

看下作用

仔细的肯定能够发现,页面切换时底部导航栏没有变化啊,是的,还需求将Tab页和导航栏相关起来。

只需求在点击导航栏时经过 pageState 跳转对应的页面和切换页面时监听当前的页面将咱们之前界说的 selectIndex 赋值即可,详细如下:

点击导航栏

Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
        .weight(1f)
        .fillMaxHeight()
        .click {
            selectIndex = index
            /**
             * 点击底部的tab切换对应的page
             */
            scope.launch {
                pageState.scrollToPage(index)
            }
        }
)

监听当前页面的切换

LaunchedEffect(pageState) {
    snapshotFlow { pageState.currentPage }.collect { page ->
        selectIndex = page
        println("LaunchedEffect currentPage: $page")
    }
}

看下调整后的作用

到这儿,主页的首要结构和交互已经建立完了

HomeScreen的全部完成代码如下:

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
@Composable
fun HomeScreen() {
    var selectIndex by rememberSaveable { mutableStateOf(0) }
    val pageState = rememberPagerState(initialPage = 0)
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    rememberSystemUiController().setStatusBarColor(Color.Transparent, darkIcons = true)
    Surface(
        Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.background)
    ) {
        Scaffold(
            topBar = {
                CenterAlignedTopAppBar(
                    title = {
                        Text(
                            titles[selectIndex],
                            maxLines = 1,
                            fontSize = 16.sp,
                            overflow = TextOverflow.Ellipsis
                        )
                    },
                    actions = {
                        if(selectIndex != 3) {
                            IconButton(
                                onClick = {
                                    /* doSomething() */
                                }) {
                                Icon(
                                    imageVector = Icons.Filled.Search,
                                    contentDescription = null,
                                    modifier = Modifier.size(30.dp),
                                    tint = Color(ContextCompat.getColor(context, R.color.black_10))
                                )
                            }
                            IconButton(onClick = {
                                /* doSomething() */
                            }) {
                                Icon(
                                    imageVector = Icons.Filled.AddCircleOutline,
                                    contentDescription = null,
                                    modifier = Modifier.size(25.dp),
                                    tint = Color(ContextCompat.getColor(context, R.color.black_10))
                                )
                            }
                        }
                    },
                    colors = TopAppBarDefaults.mediumTopAppBarColors(
                        containerColor = Color(ContextCompat.getColor(context, if (selectIndex != 3) R.color.nav_bg else R.color.white)),
                        scrolledContainerColor = Color(ContextCompat.getColor(context, if (selectIndex != 3) R.color.nav_bg else R.color.white)),
                        navigationIconContentColor = Color.White,
                        titleContentColor = Color(ContextCompat.getColor(context, R.color.black_10)),
                        actionIconContentColor = Color(ContextCompat.getColor(context, R.color.black_10)),
                    )
                )
            },
            bottomBar = {
                NavigationBar(
                    modifier = Modifier.height(60.dp),
                    containerColor = Color(ContextCompat.getColor(context, if (selectIndex > 1) R.color.white else R.color.nav_bg)),
                ) {
                    navList.forEachIndexed { index, nav ->
                        Box(
                            contentAlignment = Alignment.Center,
                            modifier = Modifier
                                .weight(1f)
                                .fillMaxHeight()
                                .click {
                                    selectIndex = index
                                    /**
                                     * 点击底部的tab切换对应的page
                                     */
                                    scope.launch {
                                        pageState.scrollToPage(index)
                                    }
                                }
                        ) {
                            Column(
                                horizontalAlignment = Alignment.CenterHorizontally,
                                verticalArrangement = Arrangement.Bottom
                            ) {
                                Icon(
                                    nav.icon, null,
                                    modifier = Modifier.size(28.dp),
                                    tint = Color(
                                        if (selectIndex == index) ContextCompat.getColor(context, R.color.green)
                                        else ContextCompat.getColor(context, R.color.gray)
                                    )
                                )
                                Text(
                                    text = nav.title,
                                    fontSize = 12.sp,
                                    color = Color(
                                        if (selectIndex == index) ContextCompat.getColor(context, R.color.green)
                                        else ContextCompat.getColor(context, R.color.gray)
                                    )
                                )
                            }
                        }
                    }
                }
            },
            content = { innerPadding ->
                Box {
                    HorizontalPager(
                        count = 4,
                        state = pageState,
                        contentPadding = PaddingValues(horizontal = 0.dp),
                        modifier = Modifier.fillMaxSize()
                    ) { page ->
                        when(page) {
                            0 -> ChatSessionScreen(innerPadding)
                            1 -> AddrBookScreen(innerPadding)
                            2 -> FindScreen(innerPadding)
                            3 -> MineScreen(innerPadding)
                        }
                    }
                    LaunchedEffect(pageState) {
                        snapshotFlow { pageState.currentPage }.collect { page ->
                            selectIndex = page
                            println("LaunchedEffect currentPage: $page")
                        }
                    }
                }
            }
        )
    }
}

最终,弥补下对应tab页的内容再看下作用

到这儿,运用Jetpack Compose仿微信主页UI就开发完成了,看起来是不是有模有样了。

总结

在这一期开发中,首要运用 Scaffold 脚手架的 topBarbottomBar 分别完成了标题栏和底部导航栏,运用了 HorizontalPager 完成了页面切换。