前言
在上一篇文章中,我实现了登陆模块的几个主要页面,在这一篇文章中,我将使用 Jetpack Cmpose 去实现微信的首页的几个tab页主要包括微信,通讯录,发现和我。
页面构成
首页主要由一个Activity和四个组合函数页面,它们的结构如下:
页面结构梳理
我们通过观察发现,首页是通过点击底部的导航栏或者左右滑动切换页面的,在我们没有使用Compose开发之前,我们是使用ViewPager + TabLayout的方式实现的,在Compose里有没有这样的组件呢?
底部导航栏的实现
在上一篇文章中,我使用了 Scaffold 脚手架,我们看看这个脚手架的属性,有没有我们需要的,Scaffold的属性如下:
less
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 对象来使用
arduino
/**
* 首页底部导航栏
*/
data class NavigationItem(
/**
* 名称
*/
val title: String,
/**
* 图标
*/
val icon: ImageVector,
)
这里的图标我使用的是material自带的,通过 implementation "androidx.compose.material:material-icons-extended:$compose_version" 引入。
接下来定义导航栏的数组
less
val navList = listOf(
NavigationItem(
"微信",
Icons.Filled.Sms,
),
NavigationItem(
"通讯录",
Icons.Filled.Contacts,
),
NavigationItem(
"发现",
Icons.Filled.Explore,
),
NavigationItem(
"我",
Icons.Filled.Person,
),
)
底部导航栏的具体实现
ini
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,用来切换对应的样式。
看下效果
Tab页的实现
通过查看,我并没有看到 ViewPager 这样的组件,但是发现了可以实现类似功能的组件 HorizontalPager,它的结构如下:
less
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 来实现我们的页面切换。
ini
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 赋值即可,具体如下:
点击导航栏
ini
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.click {
selectIndex = index
/**
* 点击底部的tab切换对应的page
*/
scope.launch {
pageState.scrollToPage(index)
}
}
)
监听当前页面的切换
scss
LaunchedEffect(pageState) {
snapshotFlow { pageState.currentPage }.collect { page ->
selectIndex = page
println("LaunchedEffect currentPage: $page")
}
}
看下调整后的效果
到这里,首页的主要结构和交互已经搭建完了
HomeScreen的全部实现代码如下:
ini
@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 脚手架的 topBar 和 bottomBar 分别实现了标题栏和底部导航栏,使用了 HorizontalPager 实现了页面切换。