Compose 打造一個Home頁面

Compose 打造一個Home頁面

一般的APP首頁都是由多個Tab組成。在Compose中,要實現(xiàn)這個會變得異常的簡單,這個得益于Compose自帶的組合函數(shù)功能。下面是輕松打造一個Home頁面的過程。

home_preview.png

BottomNavigationView的實現(xiàn)

由于Compose布局的組合化的靈活。這里直接實現(xiàn)一個 Image +Text的Tab,通過遍歷數(shù)組進行生成即可。拆分步驟如下:

  1. 一個橫向布局,嵌套多個豎向Tab
  2. Tab具備的信息:圖片(選中和未選中)、文本
  3. 每一個Tab點擊的回調(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"
    }
}

預覽如下

mock_preview2.png

這里為什么有兩個預覽呢?主要是在模擬函數(shù)返回了兩個source。

開發(fā)中遇到的問題

資源引用

  1. 引用字符串 stringResource(id = currentModel.tabName)
  2. 引用圖片資源 painterResource(id = currentModel.selectedIcon)

預覽函數(shù)

PreviewParameterProvider<Array<TabModel>> 這個函數(shù)正確使用方式如下

@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
這個錯誤已經(jīng)非常明顯,所以不能在非@Composable 函數(shù)下。盡可能抽象出不依賴Compose的model。

Compose的便捷

通過這個簡單的例子發(fā)現(xiàn)。

  1. 簡約到極致。 Compose的便捷不單單在簡約,而且特別省時間,開發(fā)效率可以說是翻了好幾倍。你想想,如果之前要實現(xiàn)這個,你要多少的封裝,可能還要使用到Fragment的管理。真不敢想象。
  2. 依賴減少。 開發(fā)這個首頁居然可以0依賴,沒有什么第三方控件的引入,也沒有去找控件的煩惱。這里需要注意的是,很多人擔心Compose沒有那么多開源框架,其實這個擔心是多余的。因為Compose簡化了布局的實現(xiàn),已經(jīng)不需要那些什么約束布局和線性布局的思路了,也不需要什么UI庫,萬物皆組合。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容