基于Jetpack Compose实现星巴克式积分进度条功能求助
实现Jetpack Compose版星巴克会员积分功能方案
1. 定义核心数据模型
先把业务涉及的核心数据用Kotlin data class定义清楚,方便后续逻辑处理:
// 回收材料类型及对应积分 data class RecyclingItem( val id: Int, val name: String, // 比如"杯子" val pointPerUnit: Int, // 比如2积分/个 val iconResId: Int // 可选,用于UI展示图标 ) // 可兑换奖励 data class Reward( val id: Int, val name: String, // 比如"冰咖啡" val requiredPoints: Int, // 比如30积分 val iconResId: Int ) // 用户会员数据 data class MemberData( val currentPoints: Int, val recyclingHistory: List<RecyclingRecord>, // 可选,记录回收历史 val rewardHistory: List<RewardRecord> // 可选,记录兑换历史 ) // 回收记录(可选) data class RecyclingRecord( val item: RecyclingItem, val count: Int, val timestamp: Long ) // 兑换记录(可选) data class RewardRecord( val reward: Reward, val timestamp: Long )
2. 积分核心逻辑实现
2.1 积分累加逻辑
提交回收材料时,根据材料类型和数量计算新增积分,更新用户当前积分:
fun calculateAddedPoints(item: RecyclingItem, count: Int): Int { return item.pointPerUnit * count } // 更新用户积分示例(结合状态管理) fun updateMemberPoints(currentData: MemberData, addedPoints: Int): MemberData { return currentData.copy(currentPoints = currentData.currentPoints + addedPoints) }
2.2 奖励兑换逻辑
判断积分是否足够,足够则扣除对应积分并生成兑换记录:
fun canRedeemReward(memberData: MemberData, reward: Reward): Boolean { return memberData.currentPoints >= reward.requiredPoints } fun redeemReward(currentData: MemberData, reward: Reward): MemberData? { return if (canRedeemReward(currentData, reward)) { val newPoints = currentData.currentPoints - reward.requiredPoints val newRewardHistory = currentData.rewardHistory + RewardRecord(reward, System.currentTimeMillis()) currentData.copy(currentPoints = newPoints, rewardHistory = newRewardHistory) } else { null // 积分不足,返回null提示用户 } }
3. 数据持久化
如果需要保存用户数据到本地,推荐使用Jetpack DataStore(轻量场景)或Room(复杂历史记录场景):
DataStore示例
// 定义Preferences DataStore val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "member_prefs") // 保存积分 suspend fun saveCurrentPoints(points: Int) { dataStore.edit { preferences -> preferences[PreferencesKeys.CURRENT_POINTS] = points } } // 读取积分 val currentPointsFlow: Flow<Int> = dataStore.data .map { preferences -> preferences[PreferencesKeys.CURRENT_POINTS] ?: 0 } // 定义键值 private object PreferencesKeys { val CURRENT_POINTS = intPreferencesKey("current_points") }
4. Jetpack Compose UI实现
4.1 积分展示与回收提交界面
@Composable fun RecyclingPointsScreen( memberData: MemberData, recyclingItems: List<RecyclingItem>, onSubmitRecycling: (RecyclingItem, Int) -> Unit ) { Column(modifier = Modifier.padding(16.dp)) { // 当前积分展示 Text(text = "当前积分: ${memberData.currentPoints}", fontSize = 20.sp, fontWeight = FontWeight.Bold) Spacer(modifier = Modifier.height(24.dp)) // 回收材料列表 Text(text = "提交回收材料", fontSize = 18.sp) Spacer(modifier = Modifier.height(12.dp)) recyclingItems.forEach { item -> var count by remember { mutableStateOf(0) } Row( modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Image(painter = painterResource(id = item.iconResId), contentDescription = item.name) Spacer(modifier = Modifier.width(12.dp)) Text(text = "${item.name}: ${item.pointPerUnit}积分/个") Spacer(modifier = Modifier.weight(1f)) Row { IconButton(onClick = { if (count > 0) count-- }) { Icon(Icons.Default.Remove, contentDescription = "减少") } Text(text = count.toString(), modifier = Modifier.padding(horizontal = 8.dp)) IconButton(onClick = { count++ }) { Icon(Icons.Default.Add, contentDescription = "增加") } Button(onClick = { if (count > 0) onSubmitRecycling(item, count) }) { Text("提交") } } } } } }
4.2 奖励兑换界面
@Composable fun RewardRedeemScreen( memberData: MemberData, rewards: List<Reward>, onRedeemReward: (Reward) -> Unit ) { Column(modifier = Modifier.padding(16.dp)) { Text(text = "当前积分: ${memberData.currentPoints}", fontSize = 20.sp, fontWeight = FontWeight.Bold) Spacer(modifier = Modifier.height(24.dp)) Text(text = "可兑换奖励", fontSize = 18.sp) Spacer(modifier = Modifier.height(12.dp)) rewards.forEach { reward -> val canRedeem = canRedeemReward(memberData, reward) Row( modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Image(painter = painterResource(id = reward.iconResId), contentDescription = reward.name) Spacer(modifier = Modifier.width(12.dp)) Column { Text(text = reward.name, fontWeight = FontWeight.Medium) Text(text = "需要${reward.requiredPoints}积分", fontSize = 12.sp, color = Color.Gray) } Spacer(modifier = Modifier.weight(1f)) Button( onClick = { onRedeemReward(reward) }, enabled = canRedeem ) { Text(if (canRedeem) "兑换" else "积分不足") } } } } }
5. 状态管理
使用Compose的remember和mutableStateOf处理UI状态,或者结合ViewModel来管理全局会员数据:
class MemberViewModel : ViewModel() { private val _memberData = mutableStateOf(MemberData(0, emptyList(), emptyList())) val memberData: State<MemberData> = _memberData // 提交回收材料 fun submitRecycling(item: RecyclingItem, count: Int) { val addedPoints = calculateAddedPoints(item, count) val newRecord = RecyclingRecord(item, count, System.currentTimeMillis()) val newData = _memberData.value.copy( currentPoints = _memberData.value.currentPoints + addedPoints, recyclingHistory = _memberData.value.recyclingHistory + newRecord ) _memberData.value = newData // 同步保存到DataStore viewModelScope.launch { saveCurrentPoints(newData.currentPoints) } } // 兑换奖励 fun redeemReward(reward: Reward) { val newData = redeemReward(_memberData.value, reward) newData?.let { _memberData.value = it viewModelScope.launch { saveCurrentPoints(it.currentPoints) } } } // 从DataStore加载初始数据 init { viewModelScope.launch { currentPointsFlow.collect { points -> _memberData.value = _memberData.value.copy(currentPoints = points) } } } }
内容的提问来源于stack exchange,提问作者Ronald Allan
相关产品推荐
相关产品推荐

