Compose 打造一个Home页面
一般的APP首页都是由多个Tab组成。在Compose中,要实现这个会变得异常的简单,这个得益于Compose自带的组合函数功能。下面是轻松打造一个Home页面的过程。
home_preview.pngBottomNavigationView的实现
由于Compose布局的组合化的灵活。这里直接实现一个 Image +Text的Tab,通过遍历数组进行生成即可。拆分步骤如下:
- 一个横向布局,嵌套多个竖向Tab
- Tab具备的信息:图片(选中和未选中)、文本
- 每一个Tab点击的回调:tag->Unit
模型
data class TabModel(
val tagTag: String,
val tabName: Int,
val normalIcon: Int,
val selectedIcon: Int
)
主体代码
@Preview(showBackground = true)
@Composable
fun TabView(
@PreviewParameter(TabDataSourceMock::class) tabSource: Array<TabModel>,
tagCallback: ((String) -> Unit)?
) {
Row(
modifier = Modifier
.fillMaxWidth()
.shadow(4.dp, RectangleShape, false)
.wrapContentHeight(Alignment.CenterVertically)
.background(Color.White)
) {
val imageModifier = Modifier.padding(0.dp, 8.dp, 0.dp, 0.dp)
val tabModifier = Modifier.padding(0.dp, 0.dp, 0.dp, 5.dp)
val selectIndex = rememberSaveable {
mutableStateOf(0)
}
tabSource.forEachIndexed { index, currentModel ->
Column(
modifier = Modifier
.weight(1F, true)
.clickable {
selectIndex.value = index
tagCallback?.invoke(currentModel.tagTag)
},
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Image(
painter = if (index == selectIndex.value) painterResource(
id = currentModel.selectedIcon
) else
painterResource(
id = currentModel.normalIcon
),
contentDescription = stringResource(id = currentModel.tabName),
modifier = imageModifier
)
Text(
stringResource(id = currentModel.tabName),
modifier = tabModifier,
textAlign = TextAlign.Center,
fontSize = 12.sp,
color = if (index == selectIndex.value) Color(0xFF07C160) else Color(0xFFAFB2B0),
)
}
}
}
}
模拟数据
class TabDataSourceMock : PreviewParameterProvider<Array<TabModel>> {
override val values: Sequence<Array<TabModel>>
get() = listOf<Array<TabModel>>(
tabDataAll(),
tabDataWithoutMall()
).asSequence()
}
fun tabDataAll(): Array<TabModel> {
return arrayOf<TabModel>(
TabModel(
TabTags.TAG_HOME,
R.string.tab_home,
R.drawable.icon_home_normal,
R.drawable.icon_home_selected
),
TabModel(
TabTags.TAG_SMART,
R.string.tab_smart,
R.drawable.icon_smart_normal,
R.drawable.icon_smart_selected
),
TabModel(
TabTags.TAG_MALL,
R.string.tab_mall,
R.drawable.icon_mall_normal,
R.drawable.icon_mall_selected
),
TabModel(
TabTags.TAG_MORE,
R.string.tab_more,
R.drawable.icon_me_normal,
R.drawable.icon_me_selected
)
)
}
fun tabDataWithoutMall(): Array<TabModel> {
return arrayOf<TabModel>(
TabModel(
TabTags.TAG_HOME,
R.string.tab_home,
R.drawable.icon_home_normal,
R.drawable.icon_home_selected
),
TabModel(
TabTags.TAG_SMART,
R.string.tab_smart,
R.drawable.icon_smart_normal,
R.drawable.icon_smart_selected
),
TabModel(
TabTags.TAG_MORE,
R.string.tab_more,
R.drawable.icon_me_normal,
R.drawable.icon_me_selected
)
)
}
class TabTags {
companion object {
const val TAG_HOME = "home"
const val TAG_SMART = "smart"
const val TAG_MALL = "mall"
const val TAG_MORE = "more"
}
}
预览如下
mock_preview2.png这里为什么有两个预览呢?主要是在模拟函数返回了两个source。
开发中遇到的问题
资源引用
- 引用字符串 stringResource(id = currentModel.tabName)
- 引用图片资源 painterResource(id = currentModel.selectedIcon)
预览函数
PreviewParameterProvider<Array<TabModel>>
这个函数正确使用方式如下
@Preview(showBackground = true)
@Composable
fun TabView(
@PreviewParameter(TabDataSourceMock::class) tabSource: Array<TabModel>,
tagCallback: ((String) -> Unit)?
)
模型抽象的错误
开始时,我对TabModel的抽象是直接使用了 Painter 导致了一个错误 Functions which invoke @Composable functions must be marked with the @Composable annotation
这个错误已经非常明显,所以不能在非@Composable 函数下。尽可能抽象出不依赖Compose的model。
Compose的便捷
通过这个简单的例子发现。
- 简约到极致。 Compose的便捷不单单在简约,而且特别省时间,开发效率可以说是翻了好几倍。你想想,如果之前要实现这个,你要多少的封装,可能还要使用到Fragment的管理。真不敢想象。
- 依赖减少。 开发这个首页居然可以0依赖,没有什么第三方控件的引入,也没有去找控件的烦恼。这里需要注意的是,很多人担心Compose没有那么多开源框架,其实这个担心是多余的。因为Compose简化了布局的实现,已经不需要那些什么约束布局和线性布局的思路了,也不需要什么UI库,万物皆组合。
网友评论