一般的APP首页都是由多个Tab组成。在Compose中,要实现这个会变得异常的简单,这个得益于Compose自带的组合函数功能。下面是轻松打造一个Home页面的过程。
由于Compose布局的组合化的灵活。这里直接实现一个 Image +Text的Tab,通过遍历数组进行生成即可。拆分步骤如下:
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"
}
}
这里为什么有两个预览呢?主要是在模拟函数返回了两个source。
PreviewParameterProvider
这个函数正确使用方式如下
@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。
通过这个简单的例子发现。