具有动态操作的 Jetpack Compose TopAppBar

问题描述 投票:0回答:3
@Composable
fun TopAppBar(
    title: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    navigationIcon: @Composable (() -> Unit)? = null,
    actions: @Composable RowScope.() -> Unit = {},
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    elevation: Dp = AppBarDefaults.TopAppBarElevation
)

动作:@Composable RowScope.() -> Unit = {}

使用场景: 使用 Compose Navigation 切换到不同的“屏幕”,因此 TopAppBar 操作将相应更改。例如。内容屏幕的共享按钮,列表屏幕的过滤器按钮

尝试将状态作为状态传递给 TopAppBar 的操作参数,但无法为

remember
函数保存 lambda 块。

val (actions, setActions) = rememberSaveable { mutableStateOf( appBarActions ) }

想要动态更改应用栏操作内容。有什么办法吗?

android kotlin navigation android-jetpack-compose
3个回答
12
投票

这是我使用的方法,但我对撰写还很陌生,所以我不能确定这是正确的方法。

假设我有 2 个屏幕:ScreenA 和 ScreenB 它们由 MainActivity 屏幕处理。 这是我们的主要活动:

@ExperimentalComposeUiApi
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CoolDrinksTheme {
                val navController = rememberNavController()

                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    var appBarState by remember {
                        mutableStateOf(AppBarState())
                    }

                    Scaffold(
                        topBar = {
                            SmallTopAppBar(
                                title = {
                                    Text(text = appBarState.title)
                                },
                                actions = {
                                    appBarState.actions?.invoke(this)
                                }
                            )
                        }
                    ) { values ->
                        NavHost(
                            navController = navController,
                            startDestination = "screen_a",
                            modifier = Modifier.padding(
                                values
                            )
                        ) {
                            composable("screen_a") {
                                ScreenA(
                                    onComposing = {
                                        appBarState = it
                                    },
                                    navController = navController
                                )
                            }
                            composable("screen_b") {
                                ScreenB(
                                    onComposing = {
                                        appBarState = it
                                    },
                                    navController = navController
                                )
                            }
                        }
                    }
                }
            }
        }
    }
}

正如您所看到的,我正在使用一个类的可变状态,它表示我们的 MainActivity 的状态(TopAppBar 在这里声明和组成),在这个例子中有我们的 TopAppBar 的标题和操作。

这个可变状态是通过在每个屏幕的组合内部调用的回调函数来设置的。

在这里你可以看到屏幕A

@Composable
fun ScreenA(
    onComposing: (AppBarState) -> Unit,
    navController: NavController
) {

    LaunchedEffect(key1 = true) {
        onComposing(
            AppBarState(
                title = "My Screen A",
                actions = {
                    IconButton(onClick = { }) {
                        Icon(
                            imageVector = Icons.Default.Favorite,
                            contentDescription = null
                        )
                    }
                    IconButton(onClick = { }) {
                        Icon(
                            imageVector = Icons.Default.Filter,
                            contentDescription = null
                        )
                    }
                }
            )
        )
    }


    Column(
        modifier = Modifier
            .fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Screen A"
        )
        Button(onClick = {
            navController.navigate("screen_b")
        }) {
            Text(text = "Navigate to Screen B")
        }
    }
}

屏幕B

@Composable
fun ScreenB(
    onComposing: (AppBarState) -> Unit,
    navController: NavController
) {

    LaunchedEffect(key1 = true) {
        onComposing(
            AppBarState(
                title = "My Screen B",
                actions = {
                    IconButton(onClick = { }) {
                        Icon(
                            imageVector = Icons.Default.Home,
                            contentDescription = null
                        )
                    }
                    IconButton(onClick = { }) {
                        Icon(
                            imageVector = Icons.Default.Delete,
                            contentDescription = null
                        )
                    }
                }
            )
        )
    }


    Column(
        modifier = Modifier
            .fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Screen B"
        )
        Button(onClick = {
            navController.popBackStack()
        }) {
            Text(text = "Navigate back to Screen A")
        }
    }
}

最后这是我们州的数据类:

data class AppBarState(
    val title: String = "",
    val actions: (@Composable RowScope.() -> Unit)? = null
)

通过这种方式,您在主要活动中声明了一个动态应用栏,但每个屏幕都负责处理应用栏的内容。


0
投票

如果你的导航使用的是compose-navigation,你可以试试这个方法。

  1. 定义组合组件
import androidx.compose.foundation.layout.RowScope
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.compose.LocalOwnersProvider

@Composable
fun ProvideAppBarAction(actions: @Composable RowScope.() -> Unit) {
    if (LocalViewModelStoreOwner.current == null || LocalViewModelStoreOwner.current !is NavBackStackEntry)
        return
    val actionViewModel = viewModel(initializer = { TopAppBarViewModel() })
    SideEffect {
        actionViewModel.actionState = actions
    }
}

@Composable
fun ProvideAppBarTitle(title: @Composable () -> Unit) {
    if (LocalViewModelStoreOwner.current == null || LocalViewModelStoreOwner.current !is NavBackStackEntry)
        return
    val actionViewModel = viewModel(initializer = { TopAppBarViewModel() })
    SideEffect {
        actionViewModel.titleState = title
    }
}

@Composable
fun RowScope.AppBarAction(navBackStackEntry: NavBackStackEntry?) {
    val stateHolder = rememberSaveableStateHolder()
    navBackStackEntry?.LocalOwnersProvider(stateHolder) {
        val actionViewModel = viewModel(initializer = { TopAppBarViewModel() })
        actionViewModel.actionState?.let { it() }
    }
}


@Composable
fun AppBarTitle(navBackStackEntry: NavBackStackEntry?) {
    val stateHolder = rememberSaveableStateHolder()
    navBackStackEntry?.LocalOwnersProvider(stateHolder) {
        val actionViewModel = viewModel(initializer = { TopAppBarViewModel() })
        actionViewModel.titleState?.let { it() }
    }
}


private class TopAppBarViewModel : ViewModel() {

    var titleState by mutableStateOf(null as (@Composable () -> Unit)?, referentialEqualityPolicy())
    var actionState by mutableStateOf(null as (@Composable RowScope.() -> Unit)?, referentialEqualityPolicy())

}
  1. 使用 viewModel 消费来自 appbar 的可组合组件
@Composable
fun MyTopAppBar(navController:NavController) {
    val currentContentBackStackEntry by produceState(
        initialValue = null as NavBackStackEntry?,
        producer = {
            navController.currentBackStackEntryFlow
                .filterNot { it.destination is FloatingWindow }
                .collect{ value = it }
        }
    )
    TopAppBar(
        navigationIcon = {
            val backPressDispatcher = LocalOnBackPressedDispatcherOwner.current
            IconButton(
                onClick = { backPressDispatcher?.onBackPressedDispatcher?.onBackPressed() },
                content = {
                    Icon(
                        imageVector = Icons.Default.ArrowBackIos,
                        contentDescription = "arrowBackIos"
                    )
                }
            )
        },
        title = {
            AppBarTitle(currentContentBackStackEntry)
        },
        actions = {
            AppBarAction(currentContentBackStackEntry)
        }
    )
}
  1. 在可组合目标中使用提供者函数
fun NavGraphBuilder.buildGraph() {
    composable(route = "start1") {
        ProvideAppBarTitle { Text("1") }
        ProvideAppBarAction {
            Button(onClick = { /*TODO*/ }) {
                Text(text = "action1")
            }
            Button(onClick = { /*TODO*/ }) {
                Text(text = "action2")
            }
            Button(onClick = { /*TODO*/ }) {
                Text(text = "action3")
            }
        }
    }
    composable(route = "start2") {
        ProvideAppBarTitle{ Text("2") }
        ProvideAppBarAction{
            Button(onClick = { /*TODO*/ }) {
                Text(text = "action1")
            }
            Button(onClick = { /*TODO*/ }) {
                Text(text = "action2")
            }
            Button(onClick = { /*TODO*/ }) {
                Text(text = "action3")
            }
        }
    }
}
  • 详细说明
  • 可组合的 lamba 由 NavBackStackEntry 控制的 viewModel 存储
  • 如果我们知道 NavController,我们可以轻松访问 NavBackStackEntry
  • Actions 和 title 可以将它们自己的状态恢复到 NavBackStackEntry,Actions 和 Title 可组合项在可组合目的地恢复时恢复。

旁注:您可以用动画包裹 AppBarTitle 和 AppBarAction

@Composable
fun MyTopAppBar(navController:NavController) {
    val currentContentBackStackEntry by produceState(
        initialValue = null as NavBackStackEntry?,
        producer = {
            navController.currentBackStackEntryFlow
                .filterNot { it.destination is FloatingWindow }
                .collect{ value = it }
        }
    )
    TopAppBar(
        navigationIcon = {
            val backPressDispatcher = LocalOnBackPressedDispatcherOwner.current
            IconButton(
                onClick = { backPressDispatcher?.onBackPressedDispatcher?.onBackPressed() },
                content = {
                    Icon(
                        imageVector = Icons.Default.ArrowBackIos,
                        contentDescription = "arrowBackIos"
                    )
                }
            )
        },
        title = {
            Crossfade(targetState = currentContentBackStackEntry, label = "AppBarTitle") {
                if (it != null) {
                    AppBarTitle(it)
                }
            }
        },
        actions = {
            Crossfade(targetState = currentContentBackStackEntry, label = "AppBarActions") {
                if (it != null) {
                    Row{
                        AppBarAction(currentContentBackStackEntry)
                    }
                }
            }
        }
    )
}

-1
投票

首先你需要在你的 jetpack compose 项目上添加导航依赖。

您可以从这里阅读文档https://developer.android.com/jetpack/compose/navigation

def nav_version = "2.4.1"
implementation "androidx.navigation:navigation-compose:$nav_version"

然后在密封类中定义屏幕:

sealed class Screen(var icon: ImageVector, var route: String) {
    object ContentScreen: Screen(Icons.Default.Home, "home")
    object ListingScreen: Screen(Icons.Default.List, "list")
}

这是导航功能的样子

@Composable
fun Navigation(paddingValues: PaddingValues, navController: NavHostController) {
    NavHost(navController, startDestination = Screen.ContentScreen.route, modifier = Modifier.padding(paddingValues)) {
        composable(Screen.ContentScreen.route) {
            //your screen content
        }
        composable(Screen.ListingScreen.route) {
            //your listing screen here
        }
    }
}

终于在您的主要活动课程中

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            TestAppTheme {
                val navController = rememberNavController()
                val navBackStackEntry by navController.currentBackStackEntryAsState()
                val currentRoute = navBackStackEntry?.destination?.route
                Scaffold(
                    topBar = {
                        TopAppBar(title = { Text(text = "main screen") }, actions = {
                            if (currentRoute == Screen.ContentScreen.route) {
                                //your share button action here
                            } else if (currentRoute == Screen.ListingScreen.route) {
                                //your filter button here
                            } else {
                                //other action
                            }
                        })
                    }
                ) {
                    Navigation(paddingValues = it, navController = navController)
                }
            }
        }
    }

如果解释排序,我很抱歉,因为我的英语有限

© www.soinside.com 2019 - 2024. All rights reserved.