2024年已经过半了,我作为聋人独立开发者,我经常会时不时反思:自己这半年到底进步了多少?在这篇文章里,我分享一个用 Jetpack Compose、Material3和 Kotlin 语言实现使用Jetpack Compose和Room开发NimWishApp的案例。无论你有没有开发经验,相信这篇文章对你会非常有所帮助。
在Demo中,采用了最新的Jetpack ComposeM3技术栈,结合了Room数据库实现数据的持久化存储,提供了一个从UI层到数据层的完整解决方案,展示了从0到1的开发。
首先要配置依赖项,用Jetpack Compose、Room数据库等技术,依赖库的配置如下:
dependencies {
implementation "androidx.compose.ui:ui:1.0.0"
implementation "androidx.compose.material3:material3:1.0.0"
implementation "androidx.navigation:navigation-compose:2.4.0-alpha07"
implementation "androidx.room:room-runtime:2.3.0"
kapt "androidx.room:room-compiler:2.3.0"
...
}
主页展示用户添加的心愿列表,允许用户通过滑动删除功能删除心愿条目,核心代码如下
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun HomeView(
navController: NavController,
viewModel: WishViewModel
) {
val wishlist = viewModel.getAllWishes.collectAsState(initial = listOf())
LazyColumn(
modifier = Modifier.fillMaxSize().padding(16.dp)
) {
items(wishlist.value, key = { wish -> wish.id }) { wish ->
val dismissState = rememberDismissState()
SwipeToDismiss(
state = dismissState,
directions = setOf(DismissDirection.EndToStart),
dismissContent = {
WishItem(wish = wish) {
navController.navigate("add/${wish.id}")
}
}
)
if (dismissState.isDismissed(DismissDirection.EndToStart)) {
viewModel.deleteWish(wish)
}
}
}
}
SwipeToDismiss组件通过滑动手势实现了删除功能。当用户从右向左滑动时,背景会变红,显示删除图标。LazyColumn则用于动态加载心愿列表。
用@Preview
写一个模拟添加一些假数据到 wish 列表里
@Preview(showBackground = true)
@Composable
fun PreviewHomeView() {
val viewModel = WishViewModel()
val navController = rememberNavController()
LaunchedEffect(Unit) {
viewModel.addWish(Wish(title = "Nimyears ", description = "这是描述1"))
viewModel.addWish(Wish(title = "NimTest", description = "这是描述2"))
}
HomeView(navController = navController, viewModel = viewModel)
}
floatingActionButton = {
LargeFloatingActionButton(
modifier = Modifier.padding(all = 20.dp),
contentColor = Color.White,
onClick = {
Toast.makeText(context, "打开悬浮按钮", Toast.LENGTH_LONG).show()
navController.navigate(Screen.AddScreen.route + "/0L")
}) {
Icon(imageVector = Icons.Filled.Add, contentDescription = null)
}
},
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun HomeView(
navController: NavController,
viewModel: WishViewModel
) {
val context = LocalContext.current
val scaffoldState = rememberScaffoldState()
Scaffold(
scaffoldState = scaffoldState,
topBar = { AppBarView(title = "@Nim独立开发者") },
floatingActionButton = {
FloatingActionButton(
modifier = Modifier.padding(all = 20.dp),
contentColor = Color.White,
backgroundColor = Color.Black,
onClick = {
Toast.makeText(context, "打开悬浮按钮", Toast.LENGTH_LONG).show()
navController.navigate(Screen.AddScreen.route + "/0L")
}) {
Icon(imageVector = Icons.Default.Add, contentDescription = null)
}
},
backgroundColor = M3Theme.colorScheme.onPrimary
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(M3Theme.colorScheme.secondaryContainer)
.padding(it),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "@Nim独立开发者",
color = M3Theme.colorScheme.onBackground,
style = MaterialTheme.typography.h4,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(20.dp)
)
// ShowList
val wishlist = viewModel.getAllWishes.collectAsState(initial = listOf())
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
) {
items(wishlist.value, key = { wish -> wish.id }) { wish ->
val dismissState = rememberDismissState()
SwipeToDismiss(
state = dismissState,
background = {
val color by animateColorAsState(
if (dismissState.isDismissed(DismissDirection.EndToStart)) Color.Red else Color.Transparent,
label = ""
)
Box(
Modifier
.fillMaxSize()
.background(color)
.padding(horizontal = 20.dp),
contentAlignment = Alignment.CenterEnd
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "删除图标",
tint = Color.Red,
)
}
},
directions = setOf(DismissDirection.EndToStart),
dismissThresholds = { FractionalThreshold(0.5f) },
dismissContent = {
WishItem(wish = wish) {
val id = wish.id
navController.navigate(Screen.AddScreen.route + "/$id")
}
}
)
if (dismissState.isDismissed(DismissDirection.EndToStart)) {
viewModel.deleteWish(wish)
}
}
}
}
}
}
@Composable
fun WishItem(wish: Wish, onClick: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, start = 8.dp, end = 8.dp)
.clickable {
onClick()
},
elevation = 10.dp,
backgroundColor = M3Theme.colorScheme.primary
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = wish.title, fontWeight = FontWeight.ExtraBold)
Text(text = wish.description)
}
}
}
可以通过点击主页上的按钮进入添加/编辑心愿的界面,使用OutlinedTextField处理用户输入,核心代码如下:
@Composable
fun AddEditDetailView(
id: Long,
viewModel: WishViewModel,
navController: NavController
) {
// 输入框
WishTextField(label = "题目", value = viewModel.wishTitleState, onValueChanged = { viewModel.onWishTitleChanged(it) })
Spacer(modifier = Modifier.height(20.dp))
WishTextField(label = "描述", value = viewModel.wishDescriptionState, onValueChanged = { viewModel.onWishDescriptionChanged(it) })
// 提交btn
OutlinedButton(onClick = {
if (viewModel.wishTitleState.isNotEmpty() && viewModel.wishDescriptionState.isNotEmpty()) {
val wish = Wish(id = id, title = viewModel.wishTitleState.trim(), description = viewModel.wishDescriptionState.trim())
if (id != 0L) viewModel.updateWish(wish) else viewModel.addWish(wish)
navController.navigateUp()
}
}) {
Text(text = if (id != 0L) "更新" else "添加")
}
}
根据传入的id判断是添加还是更新心愿item,通过ViewModel保存数据。
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF)
@Composable
fun WishTestFieldPrev() {
WishTextField(label = "文本", value = "文本", onValueChanged = {}, modifier = Modifier.fillMaxWidth(0.8f))
}
@Composable
fun AddEditDetailView(
id: Long,
viewModel: WishViewModel,
navController: NavController
) {
val snackMessage = remember { mutableStateOf("") }
val scope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
if (id != 0L) {
val wish = viewModel.getAWishById(id).collectAsState(initial = Wish(0L, "", ""))
viewModel.wishTitleState = wish.value.title
viewModel.wishDescriptionState = wish.value.description
} else {
viewModel.wishTitleState = ""
viewModel.wishDescriptionState = ""
}
Scaffold(
topBar = {
AppBarView(
title = if (id != 0L) "更新列表" else "添加列表"
) { navController.navigateUp() }
},
scaffoldState = scaffoldState,
modifier = Modifier.background(MaterialTheme.colorScheme.background)
) {
Column(
modifier = Modifier
.padding(it)
.background(MaterialTheme.colorScheme.secondaryContainer)
.fillMaxWidth()
.fillMaxHeight()
.wrapContentSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
WishTextField(
label = "题目",
value = viewModel.wishTitleState,
onValueChanged = { viewModel.onWishTitleChanged(it) },
modifier = Modifier
.fillMaxWidth(0.8f)
.heightIn(min = 50.dp, max = 100.dp)
)
Spacer(modifier = Modifier.height(20.dp))
WishTextField(
label = "描述",
value = viewModel.wishDescriptionState,
onValueChanged = { viewModel.onWishDescriptionChanged(it) },
modifier = Modifier
.fillMaxWidth(0.8f)
.heightIn(min = 50.dp, max = 150.dp)
)
Spacer(modifier = Modifier.height(20.dp))
OutlinedButton(onClick = {
if (viewModel.wishTitleState.isNotEmpty() &&
viewModel.wishDescriptionState.isNotEmpty()
) {
if (id != 0L) {
viewModel.updateWish(
Wish(
id = id,
title = viewModel.wishTitleState.trim(),
description = viewModel.wishDescriptionState.trim()
)
)
} else {
viewModel.addWish(
Wish(
title = viewModel.wishTitleState.trim(),
description = viewModel.wishDescriptionState.trim()
)
)
snackMessage.value = "List已创建"
}
} else {
snackMessage.value = "输入信息创建List"
}
scope.launch {
navController.navigateUp()
}
}) {
Text(
text = if (id != 0L) "更新" else "添加",
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onBackground,
fontSize = 18.sp
)
)
}
}
}
}
@Composable
fun WishTextField(
label: String,
value: String,
onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier
) {
OutlinedTextField(
value = value,
onValueChange = onValueChanged,
label = { Text(text = label, color = MaterialTheme.colorScheme.onBackground) },
modifier = modifier,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
colors = TextFieldDefaults.outlinedTextFieldColors(
textColor = MaterialTheme.colorScheme.onBackground,
focusedBorderColor = MaterialTheme.colorScheme.primary,
unfocusedBorderColor = MaterialTheme.colorScheme.onSurface,
cursorColor = MaterialTheme.colorScheme.primary,
focusedLabelColor = MaterialTheme.colorScheme.primary,
unfocusedLabelColor = MaterialTheme.colorScheme.onSurface,
)
)
}
为每个页面添加了自定义的顶部应用栏和加上返回图标,支持跳转上一级的功能:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppBarView(title: String, onBackNavClicked: () -> Unit = {}) {
CenterAlignedTopAppBar(
title = { Text(text = title) },
navigationIcon = {
IconButton(onClick = { onBackNavClicked() }) {
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
}
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = MaterialTheme.colorScheme.primary)
)
}
CenterAlignedTopAppBar 是 Compose Material3 实现应用栏的组件,支持自定义标题和导航图标。
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF)
@Composable
fun PreviewAppBarWithBackNav() {
AppBarView(
title = "NimTest",
onBackNavClicked = { }
)
}
未完成更新,明天编写下篇逻辑代码。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。