前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >安卓软件开发:使用Jetpack Compose和Room开发NimWishApp-上篇

安卓软件开发:使用Jetpack Compose和Room开发NimWishApp-上篇

原创
作者头像
Nimyears
发布2024-10-03 16:08:48
3110
发布2024-10-03 16:08:48
举报
文章被收录于专栏:JetpackCompose M3

2024年已经过半了,我作为聋人独立开发者,我经常会时不时反思:自己这半年到底进步了多少?在这篇文章里,我分享一个用 Jetpack Compose、Material3和 Kotlin 语言实现使用Jetpack Compose和Room开发NimWishApp的案例。无论你有没有开发经验,相信这篇文章对你会非常有所帮助。


一、项目背景

在Demo中,采用了最新的Jetpack ComposeM3技术栈,结合了Room数据库实现数据的持久化存储,提供了一个从UI层到数据层的完整解决方案,展示了从0到1的开发。


二、项目开发

2.1 项目配置

首先要配置依赖项,用Jetpack Compose、Room数据库等技术,依赖库的配置如下:

代码语言:groovy
复制
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"
    ...
}

2.2 主页功能(HomeView)

主页展示用户添加的心愿列表,允许用户通过滑动删除功能删除心愿条目,核心代码如下

代码语言:java
复制
@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)
            }
        }
    }
}

2.2.1 解释代码

SwipeToDismiss组件通过滑动手势实现了删除功能。当用户从右向左滑动时,背景会变红,显示删除图标。LazyColumn则用于动态加载心愿列表。

2.2.2 测试 UI

@Preview写一个模拟添加一些假数据到 wish 列表里

代码语言:java
复制
@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)
}

2.2.3 设计主界面的悬浮按钮功能

代码语言:java
复制
 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)
            }
        },

2.2.4 HomeView的完整代码

代码语言:java
复制
@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)
        }
    }
}

2.3 添加/编辑功能(AddEditDetailView)

可以通过点击主页上的按钮进入添加/编辑心愿的界面,使用OutlinedTextField处理用户输入,核心代码如下:

代码语言:java
复制
@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 "添加")
    }
}

2.3.1 解释代码

根据传入的id判断是添加还是更新心愿item,通过ViewModel保存数据。

2.3.2 测试UI

代码语言:javascript
复制
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) 
@Composable
fun WishTestFieldPrev() {
    WishTextField(label = "文本", value = "文本", onValueChanged = {}, modifier = Modifier.fillMaxWidth(0.8f))
}

2.3.3 AddEditDetailView的完整代码

代码语言:javascript
复制
@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,
        )
    )
}

2.4 顶部应用栏设计(AppBarView)

为每个页面添加了自定义的顶部应用栏和加上返回图标,支持跳转上一级的功能:

代码语言:java
复制
@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)
    )
}

2.4.1 解释代码

CenterAlignedTopAppBar 是 Compose Material3 实现应用栏的组件,支持自定义标题和导航图标。

2.4.2 测试UI

代码语言:javascript
复制
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) 
@Composable
fun PreviewAppBarWithBackNav() {
    AppBarView(
        title = "NimTest",
        onBackNavClicked = {  }
    )
}


未完成更新,明天编写下篇逻辑代码。


原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、项目背景
  • 二、项目开发
    • 2.1 项目配置
      • 2.2 主页功能(HomeView)
        • 2.2.1 解释代码
          • 2.2.2 测试 UI
            • 2.2.3 设计主界面的悬浮按钮功能
              • 2.2.4 HomeView的完整代码
                • 2.3 添加/编辑功能(AddEditDetailView)
                  • 2.3.1 解释代码
                    • 2.3.2 测试UI
                      • 2.3.3 AddEditDetailView的完整代码
                        • 2.4 顶部应用栏设计(AppBarView)
                          • 2.4.1 解释代码
                            • 2.4.2 测试UI
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档