Compose 打造一個Home頁面
一般的APP首頁都是由多個Tab組成蕉毯。在Compose中杜秸,要實現(xiàn)這個會變得異常的簡單埋泵,這個得益于Compose自帶的組合函數(shù)功能砂竖。下面是輕松打造一個Home頁面的過程遂蛀。
BottomNavigationView的實現(xiàn)
由于Compose布局的組合化的靈活谭跨。這里直接實現(xiàn)一個 Image +Text的Tab,通過遍歷數(shù)組進(jìn)行生成即可。拆分步驟如下:
- 一個橫向布局螃宙,嵌套多個豎向Tab
- Tab具備的信息:圖片(選中和未選中)蛮瞄、文本
- 每一個Tab點(diǎn)擊的回調(diào):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),
)
}
}
}
}
模擬數(shù)據(jù)
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"
}
}
預(yù)覽如下
這里為什么有兩個預(yù)覽呢?主要是在模擬函數(shù)返回了兩個source谆扎。
開發(fā)中遇到的問題
資源引用
- 引用字符串 stringResource(id = currentModel.tabName)
- 引用圖片資源 painterResource(id = currentModel.selectedIcon)
預(yù)覽函數(shù)
PreviewParameterProvider<Array<TabModel>>
這個函數(shù)正確使用方式如下
@Preview(showBackground = true)
@Composable
fun TabView(
@PreviewParameter(TabDataSourceMock::class) tabSource: Array<TabModel>,
tagCallback: ((String) -> Unit)?
)
模型抽象的錯誤
開始時挂捅,我對TabModel的抽象是直接使用了 Painter 導(dǎo)致了一個錯誤 Functions which invoke @Composable functions must be marked with the @Composable annotation
這個錯誤已經(jīng)非常明顯,所以不能在非@Composable 函數(shù)下堂湖。盡可能抽象出不依賴Compose的model闲先。
Compose的便捷
通過這個簡單的例子發(fā)現(xiàn)。
- 簡約到極致无蜂。 Compose的便捷不單單在簡約伺糠,而且特別省時間,開發(fā)效率可以說是翻了好幾倍斥季。你想想训桶,如果之前要實現(xiàn)這個,你要多少的封裝酣倾,可能還要使用到Fragment的管理舵揭。真不敢想象。
- 依賴減少灶挟。 開發(fā)這個首頁居然可以0依賴琉朽,沒有什么第三方控件的引入,也沒有去找控件的煩惱稚铣。這里需要注意的是箱叁,很多人擔(dān)心Compose沒有那么多開源框架,其實這個擔(dān)心是多余的惕医。因為Compose簡化了布局的實現(xiàn)耕漱,已經(jīng)不需要那些什么約束布局和線性布局的思路了,也不需要什么UI庫抬伺,萬物皆組合螟够。