在去年2022年曾發(fā)布一篇關(guān)于腳手架的文章:“Android JetPack Compose組件中Scaffold的應(yīng)用” 。但是Android的版本從12變更到13及以上版本,導(dǎo)致一些細(xì)節(jié)的實(shí)現(xiàn)存在不同。在本文中,將從頭開始介紹整個(gè)腳手架的搭建過程。
一、新建項(xiàng)目模塊
在Android Studio(版本是Graffie)中新建模塊,選擇“Empty Activity",如圖1所示。
圖1
二、定義腳手架Scaffold
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreen(){
Scaffold(
//定義頭部
topBar = {
},
//定義底部導(dǎo)航
bottomBar = {
},
//定義信息提示區(qū)
snackbarHost = {
},
//定義懸浮按鈕
floatingActionButton = {
},
content = {//content定義中心區(qū)
}
)
或也可以定義成如下形式:
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreen(){
Scaffold(
//定義頭部
topBar = {
},
//定義底部導(dǎo)航
bottomBar = {
},
//定義信息提示區(qū)
snackbarHost = {
},
//定義懸浮按鈕
floatingActionButton = {
}
){//content定義中心區(qū)
}
}
與原來:“Android JetPack Compose組件中Scaffold的應(yīng)用” 最大的不同在于現(xiàn)在Android13版本的Scaffold取消了drawerContent的屬性,因此,導(dǎo)致對(duì)于側(cè)滑菜單的定義發(fā)生變化。
三、創(chuàng)建三個(gè)不同界面
首先,定義一個(gè)通用的界面:
@Composable
fun DisplayScreen(title:String, preColor: Color=Color.Black, backgroundColor:Color=colorResource(R.color.teal_200)){
Box(contentAlignment= Alignment.Center,
modifier = Modifier
.fillMaxSize()
.background(backgroundColor)){
Text(text = title,fontSize = 30.sp,color = preColor)
}
}
然后,定義的三個(gè)不同的界面分別調(diào)用上述的DisplayScreen組合函數(shù),代碼分別如下,運(yùn)行效果如圖2所示。
@Composable
fun HomeScreen(){
DisplayScreen(title = "首頁(yè)")
}
@Composable
fun SettingScreen(){
DisplayScreen(title = "配置")
}
@Composable
fun HelpScreen(){
DisplayScreen(title = "幫助和支持")
}
圖2
為了方便后續(xù)對(duì)這三個(gè)界面的切換,定義一個(gè)通用的密封類Screen,代碼如下
/**
* 定義要切換界面的密封類Screen
* @property route String 導(dǎo)航線路名
* @property title String 標(biāo)題
* @property icon ImageVector 圖標(biāo)
* @property loadScreen [@androidx.compose.runtime.Composable] Function0<Unit> 加載動(dòng)作處理
* @constructor
*/
sealed class Screen(val route:String, val title:String, val icon: ImageVector, val loadScreen: @Composable ()->Unit){
object Home:Screen("home","首頁(yè)", Icons.Filled.Home,loadScreen={
HomeScreen()
})
object Setting:Screen("setting","配置",Icons.Filled.Settings, loadScreen = {
SettingScreen()
})
object Help:Screen("help","幫助和支持",Icons.Filled.Info, loadScreen = {
HelpScreen()
})
}
在此前提下定義一個(gè)保存要顯示界面的列表:
val screens = listOf(Screen.Home,Screen.Setting,Screen.Help)
四、定義底部導(dǎo)航欄
@Composable
fun BottomView(currentScreen: MutableState<Screen>){
BottomAppBar {
screens.forEach {
NavigationBarItem(
selected = currentScreen.value.route == it.route,
onClick = {
//定義點(diǎn)擊動(dòng)作
currentScreen.value = it
},
icon = {
Column(horizontalAlignment = Alignment.CenterHorizontally){
Icon(imageVector = it.icon,tint = Color.Blue,contentDescription = it.title)
Text(text = it.title,fontSize = 20.sp)
}
})
}
}
}
然后在Scaffold中進(jìn)行調(diào)用,因?yàn)樾枰4嬉粋€(gè)當(dāng)前屏幕的狀態(tài),因此在MainScreen增加一個(gè)currentScreen的狀態(tài)值,修改MainScreen()如下所示。
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreen(){
val currentState:MutableState<Screen> = remember{mutableStateOf(Screen.Home)}
Scaffold(
//定義頭部
topBar = {
},
//定義底部導(dǎo)航
bottomBar = {
BottomView(currentScreen = currentState)
},
//定義信息提示區(qū)
snackbarHost = {
},
//定義懸浮按鈕
floatingActionButton = {
}
){//content定義中心區(qū)
currentState.value.loadScreen()
}
}
這時(shí)運(yùn)行效果如圖3所示。
圖3
通過選擇底部不同的按鈕,可以切換到不同的界面,如圖3所示。
五、定義頂部欄
定義頂部欄需要解決兩個(gè)問題:(1)需要在頂部欄定義頂部的右側(cè)導(dǎo)航菜單;(2)需要定義頂部的導(dǎo)航按鈕,使得啟動(dòng)側(cè)滑菜單;
1.定義頂部的后側(cè)菜單
@Composable
fun MenuView(currentScreen: MutableState<Screen>, expandedState:MutableState<Boolean>){
DropdownMenu(expanded = expandedState.value,
onDismissRequest = {
expandedState.value = false
}) {
screens.forEach {
DropdownMenuItem(
leadingIcon = {
Icon(imageVector = it.icon,contentDescription = it.title)
},
text = {
Text(text = it.title,fontSize = 20.sp)
}, onClick = {
currentScreen.value = it
})
}
}
}
然后再修改MainScreen,通過一個(gè)狀態(tài)參數(shù)expandedState的值判斷是否打開菜單,這時(shí)修改的MainScreen的代碼如下:
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreen(){
//保存當(dāng)前界面
val currentState:MutableState<Screen> = remember{mutableStateOf(Screen.Home)}
//記錄菜單是否可以擴(kuò)展
val expandedState = remember{mutableStateOf(false)}
Scaffold(
//定義頭部
topBar = {
TopAppBar(
//左側(cè)的文本
title = { /*TODO*/ },
//導(dǎo)航圖標(biāo)
navigationIcon = {
},
//按行處理的交互
actions = {
IconButton(onClick={
expandedState.value = !expandedState.value
}){
Icon(imageVector = Icons.Filled.MoreVert,contentDescription = "More...")
if(expandedState.value)
MenuView(currentState, expandedState)
}
})
},
//定義底部導(dǎo)航
bottomBar = {
BottomView(currentScreen = currentState)
},
//定義信息提示區(qū)
snackbarHost = {
},
//定義懸浮按鈕
floatingActionButton = {
}
){//content定義中心區(qū)
currentState.value.loadScreen()
}
}
這時(shí),代碼的運(yùn)行效果如圖4所示。
圖4
如圖4所示,可以發(fā)現(xiàn)右上角出現(xiàn)了更多的圖標(biāo),點(diǎn)擊該圖標(biāo)會(huì)彈出一個(gè)菜單,通過這個(gè)菜單可以切換不同的界面。
2.定義頂部欄的導(dǎo)航按鈕啟動(dòng)側(cè)滑菜單
定義側(cè)滑菜單的內(nèi)容,代碼如下所示:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DrawerView(currentScreen: MutableState<Screen>, drawerState: DrawerState,scope:CoroutineScope){
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxHeight()
.width(360.dp).background(Color.White)){
screens.forEach {
NavigationDrawerItem(
label = {
Text(it.title,fontSize = 30.sp)
},
icon={
Icon(imageVector = it.icon,tint=Color.Green,contentDescription = null)
},
selected = it.route==currentScreen.value.route,
onClick = {
scope.launch {
currentScreen.value = it
drawerState.close()
}
})
}
}
}) {
currentScreen.value.loadScreen()
}
}
在此基礎(chǔ)上,修改MainScreen,使得點(diǎn)擊頂部欄的導(dǎo)航按鈕可以彈出側(cè)滑菜單:
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreen(){
val currentState:MutableState<Screen> = remember{mutableStateOf(Screen.Home)}
val expandedState = remember{mutableStateOf(false)}
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
Scaffold(
//定義頭部
topBar = {
TopAppBar(
//左側(cè)的文本
title = {
Text("側(cè)滑菜單")
},
//導(dǎo)航圖標(biāo)
navigationIcon = {
IconButton(onClick={
scope.launch {
drawerState.open()
}
}){
Icon(imageVector = Icons.Filled.ArrowForward,contentDescription = "彈出側(cè)滑菜單")
}
},
//按行處理的交互
actions = {
IconButton(onClick={
expandedState.value = !expandedState.value
}){
Icon(imageVector = Icons.Filled.MoreVert,contentDescription = "More...")
if(expandedState.value)
MenuView(currentState, expandedState)
}
})
},
//定義底部導(dǎo)航
bottomBar = {
BottomView(currentScreen = currentState)
},
//定義信息提示區(qū)
snackbarHost = {
},
//定義懸浮按鈕
floatingActionButton = {
}
){ //content定義中心區(qū)
//直接調(diào)用側(cè)滑界面
DrawerView(currentState, drawerState, scope )
}
}
注意在MainScreen中的Scaffold的中心區(qū)修改為調(diào)用drawerView組合函數(shù),并增加DrawerState狀態(tài)值控制側(cè)滑菜單的啟動(dòng)和關(guān)閉,通過調(diào)用drawerState的open函數(shù)和close函數(shù)分別實(shí)現(xiàn)。因?yàn)閐rawerState的open函數(shù)和close函數(shù)均為suspend掛起函數(shù),需要在協(xié)程中運(yùn)行,因此還增加了一個(gè)scope的參數(shù),用它來加載drawerState的open函數(shù)和close函數(shù)。
這時(shí),點(diǎn)擊頂部欄的導(dǎo)航圖標(biāo),運(yùn)行效果如圖5所示。
圖5
六、定義懸浮按鈕
懸浮按鈕定義在Scaffold腳手架的floatingActionButton屬性對(duì)應(yīng)的部分,下列將定義一個(gè)懸浮按鈕,使得點(diǎn)擊該按鈕可以返回到首頁(yè)。代碼如下:
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreen(){
val currentState:MutableState<Screen> = remember{mutableStateOf(Screen.Home)}
val expandedState = remember{mutableStateOf(false)}
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
Scaffold(
......
//定義懸浮按鈕
floatingActionButton = {
FloatingActionButton(onClick = {
currentState.value = Screen.Home
}) {
Icon(imageVector = Icons.Filled.Refresh,contentDescription = "返回")
}
}
){ //content定義中心區(qū)
DrawerView(currentState, drawerState, scope )
}
}
運(yùn)行效果如圖6所示。
圖6
七、定義信息欄
定義一個(gè)信息欄增加一個(gè)狀態(tài)值displayedSnackState,通過它來修改信息欄顯示的控制。代碼示例如下:
@Composable
fun MainScreen(){
val currentState:MutableState<Screen> = remember{mutableStateOf(Screen.Home)}
val expandedState = remember{mutableStateOf(false)}
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
val displayedSnackState = remember { mutableStateOf(false)}
Scaffold(
//定義頭部
topBar = {
TopAppBar(
//左側(cè)的文本
title = {
Text("側(cè)滑菜單")
},
//導(dǎo)航圖標(biāo)
navigationIcon = {
IconButton(onClick={
scope.launch {
drawerState.open()
}
}){
Icon(imageVector = Icons.Filled.ArrowForward,contentDescription = "彈出側(cè)滑菜單")
}
},
//按行處理的交互
actions = {
IconButton(onClick={
expandedState.value = !expandedState.value
}){
Icon(imageVector = Icons.Filled.MoreVert,contentDescription = "More...")
if(expandedState.value)
MenuView(currentState, expandedState)
}
})
},
//定義底部導(dǎo)航
bottomBar = {
BottomView(currentScreen = currentState)
},
//定義信息提示區(qū)
snackbarHost = {
if(displayedSnackState.value){
Snackbar(modifier = Modifier
.fillMaxWidth()
.background(Color.Blue),
) {
Text("提示信息:返回首頁(yè)",fontSize = 24.sp)
}
}
},
//定義懸浮按鈕
floatingActionButton = {
FloatingActionButton(onClick = {
currentState.value = Screen.Home
displayedSnackState.value = !displayedSnackState.value
}) {
Icon(imageVector = Icons.Filled.Refresh,contentDescription = "返回")
}
}
){ //content定義中心區(qū)
DrawerView(currentState, drawerState, scope )
}
}
運(yùn)行結(jié)果如圖7所示:
圖7
八、狀態(tài)優(yōu)化的處理
在上述的處理過程中,可以發(fā)現(xiàn)MainScreen中定義了很多的狀態(tài)值,這些狀態(tài)值往往需要作為函數(shù)的參數(shù)進(jìn)行傳遞,處理過程復(fù)雜,可以對(duì)這些狀態(tài)值做一個(gè)優(yōu)化處理。
首先,定義一個(gè)類,保存各種需要的狀態(tài)。代碼如下:
@OptIn(ExperimentalMaterial3Api::class)
class StateHolder(val currentScreen:MutableState<Screen>,
val expandedState: MutableState<Boolean>,
val drawerState: DrawerState,
val displayedSnackState:MutableState<Boolean>,
val scope:CoroutineScope)
然后再定義一個(gè)組合函數(shù)獲取所有的狀態(tài)值,代碼如下:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun rememberStates(
currentScreen: MutableState<Screen> = remember { mutableStateOf(Screen.Home) },
expandedState: MutableState<Boolean> = remember { mutableStateOf(false) },
drawerState: DrawerState = rememberDrawerState(initialValue = DrawerValue.Closed),
displayedSnackState: MutableState<Boolean> = remember{mutableStateOf(false)},
scope: CoroutineScope = rememberCoroutineScope(),
)=remember(currentScreen,expandedState,drawerState,displayedSnackState,scope){
StateHolder(currentScreen,expandedState,drawerState,displayedSnackState,scope)
}
在此前提的基礎(chǔ)上,修改代碼,這時(shí)以MainScreen為例:
@Composable
fun MainScreen(){
val states = rememberStates()
Scaffold(
//定義頭部
topBar = {
TopAppBar(
//左側(cè)的文本
title = {
Text("側(cè)滑菜單")
},
//導(dǎo)航圖標(biāo)
navigationIcon = {
IconButton(onClick={
states.scope.launch {
states.drawerState.open()
}
}){
Icon(imageVector = Icons.Filled.ArrowForward,contentDescription = "彈出側(cè)滑菜單")
}
},
//按行處理的交互
actions = {
IconButton(onClick={
states.expandedState.value = !states.expandedState.value
}){
Icon(imageVector = Icons.Filled.MoreVert,contentDescription = "More...")
if(states.expandedState.value)
MenuView(states)
}
})
},
//定義底部導(dǎo)航
bottomBar = {
BottomView(states)
},
//定義信息提示區(qū)
snackbarHost = {
if(states.displayedSnackState.value){
Snackbar(modifier = Modifier
.fillMaxWidth()
.background(Color.Blue),
) {
Text("提示信息:返回首頁(yè)",fontSize = 24.sp)
}
}
},
//定義懸浮按鈕
floatingActionButton = {
FloatingActionButton(onClick = {
states.currentScreen.value = Screen.Home
states.displayedSnackState.value = !states.displayedSnackState.value
}) {
Icon(imageVector = Icons.Filled.Refresh,contentDescription = "返回")
}
}
){ //content定義中心區(qū)
DrawerView(states)
}
}
同時(shí)對(duì)MainScreen調(diào)用的MenuView、BottomView和DrawerView中需要傳遞狀態(tài)參數(shù)的函數(shù)進(jìn)行修改,修改的代碼分別是:
MenuView的定義
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MenuView(states:StateHolder){
DropdownMenu(expanded = states.expandedState.value,
onDismissRequest = {
states.expandedState.value = false
}) {
screens.forEach {
DropdownMenuItem(
leadingIcon = {
Icon(imageVector = it.icon,contentDescription = it.title)
},
text = {
Text(text = it.title,fontSize = 20.sp)
}, onClick = {
states.currentScreen.value = it
})
}
}
}
BottomView的定義
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomView(states:StateHolder){
BottomAppBar {
screens.forEach {
NavigationBarItem(
selected = states.currentScreen.value.route == it.route,
onClick = {
//定義點(diǎn)擊動(dòng)作
states.currentScreen.value = it
},
icon = {
Column(horizontalAlignment = Alignment.CenterHorizontally){
Icon(imageVector = it.icon,tint = Color.Blue,contentDescription = it.title)
Text(text = it.title,fontSize = 20.sp)
}
})
}
}
}
DrawerView的定義文章來源:http://www.zghlxwxcb.cn/news/detail-762973.html
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DrawerView(states:StateHolder){
ModalNavigationDrawer(
drawerState = states.drawerState,
drawerContent = {
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxHeight()
.width(360.dp)
.background(Color.White)){
screens.forEach {
NavigationDrawerItem(
label = {
Text(it.title,fontSize = 30.sp)
},
icon={
Icon(imageVector = it.icon,tint=Color.Green,contentDescription = null)
},
selected = it.route==states.currentScreen.value.route,
onClick = {
states.scope.launch {
states.currentScreen.value = it
states.drawerState.close()
}
})
}
}
}) {
states.currentScreen.value.loadScreen()
}
}
通過這樣的方式,單一傳遞狀態(tài)值在不同的組合函數(shù)共享。文章來源地址http://www.zghlxwxcb.cn/news/detail-762973.html
到了這里,關(guān)于Android筆記(七)Android JetPack Compose組件搭建Scaffold腳手架的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!