You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 08:51:01