当前位置: 首页 > news >正文

Android,jetpack Compose模仿QQ侧边栏

SwipeMainActivity代码如下:

在这里插入图片描述

package com.example.myapplicationimport android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.myapplication.ui.SwipeMenuListclass SwipeMainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {val context = LocalContext.current // 提前获取 contextMaterialTheme {Surface(color = Color(0xFFF5F5F5)) {Column {Text("高仿QQ侧滑菜单",modifier = Modifier.fillMaxWidth().padding(16.dp),fontSize = 20.sp,fontWeight = FontWeight.Bold)SwipeMenuList (items = List(20) { "联系人 ${it + 1}" },modifier = Modifier.fillMaxWidth(),onItemTop = { Toast.makeText(context, "置顶: $it", Toast.LENGTH_SHORT).show() },onItemUnread = { Toast.makeText(context, "标为未读: $it", Toast.LENGTH_SHORT).show() },onItemDelete = { Toast.makeText(context, "删除: $it", Toast.LENGTH_SHORT).show() })}}}}}@Preview(showBackground = true)@Composablefun SwipeMenuPreview() {MaterialTheme {Surface(color = Color(0xFFF5F5F5)) {Column {Text("高仿QQ侧滑菜单",modifier = Modifier.fillMaxWidth().padding(16.dp),fontSize = 20.sp,fontWeight = FontWeight.Bold)SwipeMenuList(items = List(5) { "联系人 ${it + 1}" },modifier = Modifier.fillMaxWidth(),onItemTop = {},onItemUnread = {},onItemDelete = {})}}}}
}

SwipeMenuItem代码如下:

// ui/components/SwipeMenuItem.kt
package com.example.myapplication.ui.componentsimport androidx.compose.animation.core.animateIntOffsetAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
import com.example.myapplication.utils.SwipeState@Composable
fun SwipeMenuItem(modifier: Modifier = Modifier,content: @Composable () -> Unit,onTop: () -> Unit,onUnread: () -> Unit,onDelete: () -> Unit,swipeState: SwipeState
) {val scope = rememberCoroutineScope()// 动画偏移:主内容跟随手指val targetOffset = IntOffset(swipeState.offsetX, 0)val animatedOffset by animateIntOffsetAsState(targetValue = targetOffset, label = "contentOffset")Box(modifier = modifier.clip(RoundedCornerShape(12.dp)).shadow(2.dp).background(Color.White)// ✅ 使用 detectHorizontalDragGestures,仅处理水平滑动手势.pointerInput(swipeState) {detectHorizontalDragGestures(onDragStart = { },onHorizontalDrag = { change, dragAmount ->val newOffset = swipeState.offsetX + dragAmount.toInt()if (dragAmount < 0) {// 向左滑:打开菜单swipeState.updateOffset(newOffset)} else if (dragAmount > 0 && swipeState.isOpen) {// 向右滑:关闭菜单swipeState.updateOffset(newOffset)}change.consume() // ✅ 消费事件,防止传递给父布局},onDragEnd = {scope.launch {if (swipeState.offsetX < -SwipeState.menuWidth / 2) {swipeState.open()} else {swipeState.close()}}},onDragCancel = {scope.launch {if (swipeState.offsetX < -SwipeState.menuWidth / 2) {swipeState.open()} else {swipeState.close()}}})}) {// ========== 右侧操作按钮(从右向左滑入)==========if (swipeState.offsetX < 0) {Row(modifier = Modifier.fillMaxSize(),horizontalArrangement = Arrangement.End) {// 删除Box(modifier = Modifier.width(90.dp).fillMaxHeight().background(Color(0xFFEE6363)).clickable {scope.launch {swipeState.close()onDelete()}},contentAlignment = Alignment.Center) {Text("删除", color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Bold)}// 标记为未读Box(modifier = Modifier.width(90.dp).fillMaxHeight().background(Color(0xFFFFC125)).clickable {scope.launch {swipeState.close()onUnread()}},contentAlignment = Alignment.Center) {Text("标记为未读", color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Bold)}// 置顶Box(modifier = Modifier.width(90.dp).fillMaxHeight().background(Color(0xFF0099FF)).clickable {scope.launch {swipeState.close()onTop()}},contentAlignment = Alignment.Center) {Text("置顶", color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Bold)}}}// ========== 主内容层(联系人)==========Box(modifier = Modifier.offset { animatedOffset }.fillMaxSize().padding(horizontal = 16.dp),contentAlignment = Alignment.CenterStart) {content()}}
}

SwipeMenuList代码如下

// ui/SwipeMenuList.kt
package com.example.myapplication.uiimport androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.myapplication.ui.components.SwipeMenuItem
import com.example.myapplication.utils.SwipeState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch@Composable
fun SwipeMenuList(items: List<String>,modifier: Modifier = Modifier,onItemTop: (String) -> Unit,onItemUnread: (String) -> Unit,onItemDelete: (String) -> Unit
) {val openStates = remember { mutableStateMapOf<String, SwipeState>() }val states by remember(items) {derivedStateOf {items.associateWith { item ->openStates.getOrPut(item) { SwipeState() }}}}// ✅ 新增:获取所有打开的 SwipeStateval openSwipeStates = remember { mutableStateListOf<SwipeState>() }// 获取当前协程作用域val coroutineScope = rememberCoroutineScope()LazyColumn(modifier = modifier.fillMaxSize()) {items(items) { item ->val state = states[item]!!// ✅ 更新:监听 isOpen 变化,同步到 openSwipeStatesLaunchedEffect(state.isOpen) {if (state.isOpen) {// 当前打开 → 內部处理关闭其他coroutineScope.launch {openSwipeStates.forEach { it.close() }openSwipeStates.clear()openSwipeStates.add(state)}} else {// 当前关闭 → 从列表移除openSwipeStates.remove(state)}}// ✅ 为每个 item 添加点击监听:点击即关闭所有打开的菜单val itemModifier = Modifier.fillMaxWidth().height(70.dp).clickable(onClick = {// 点击任意 item → 关闭所有打开的菜单if (openSwipeStates.isNotEmpty()) {// 使用协程作用域来调用 suspend 函数coroutineScope.launch {openSwipeStates.forEach { it.close() }openSwipeStates.clear()}}})SwipeMenuItem(modifier = itemModifier,swipeState = state,onTop = { onItemTop(item) },onUnread = { onItemUnread(item) },onDelete = { onItemDelete(item) },content = {Row(modifier = Modifier.fillMaxWidth(),verticalAlignment = Alignment.CenterVertically) {Text(item, fontSize = 16.sp, fontWeight = FontWeight.Medium)Spacer(modifier = Modifier.weight(1f))Text("左滑←←←", color = Color.Gray, fontSize = 14.sp)}})}}
}

SwipeState代码如下:

// utils/SwipeState.kt
package com.example.myapplication.utilsimport androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlinx.coroutines.delay/*** 侧滑菜单状态管理类(右侧滑出菜单)*/
class SwipeState(private val onOpened: () -> Unit = {},private val onClosed: () -> Unit = {}
) {// offsetX: 0 = 关闭, 负值 = 向左滑出右侧菜单var offsetX by mutableStateOf(0)private setvar isOpen by mutableStateOf(false)private setcompanion object {const val menuWidth = 270 // 90 * 3}/*** 安全更新偏移量,限制在 [-menuWidth, 0]*/fun updateOffset(newOffset: Int) {val clamped = newOffset.coerceIn(-menuWidth, 0)if (clamped != offsetX) {offsetX = clamped}}/*** 动画打开菜单(滑出右侧按钮)*/suspend fun open() {if (isOpen) returnwhile (offsetX > -menuWidth) {offsetX -= 20.coerceAtMost(offsetX + menuWidth)delay(16)}offsetX = -menuWidthisOpen = trueonOpened()}/*** 动画关闭菜单*/suspend fun close() {if (!isOpen && offsetX == 0) returnwhile (offsetX < 0) {offsetX += 20.coerceAtMost(-offsetX)delay(16)}offsetX = 0isOpen = falseonClosed()}
}

最终效果:
请添加图片描述


文章转载自:

http://jiDbeUel.mwqbp.cn
http://L5C6YeP8.mwqbp.cn
http://GU7f4bsy.mwqbp.cn
http://UtBusU3F.mwqbp.cn
http://YfMGiwgt.mwqbp.cn
http://5JsOKg0w.mwqbp.cn
http://P2FIcK6h.mwqbp.cn
http://ujWgsZh1.mwqbp.cn
http://FlPAqMou.mwqbp.cn
http://rnFMDmO0.mwqbp.cn
http://VuZKIK2h.mwqbp.cn
http://Zl86lutc.mwqbp.cn
http://NffO504g.mwqbp.cn
http://VKm4SA4l.mwqbp.cn
http://WYU2JnYj.mwqbp.cn
http://5BHi9UmM.mwqbp.cn
http://s8E3Xker.mwqbp.cn
http://vHydSRUB.mwqbp.cn
http://azVeNRSV.mwqbp.cn
http://vfM5VhCa.mwqbp.cn
http://hqZAfofe.mwqbp.cn
http://uje3MkK2.mwqbp.cn
http://HYYaXWMH.mwqbp.cn
http://IIesvYK6.mwqbp.cn
http://aqwHAdlX.mwqbp.cn
http://AmBBiUft.mwqbp.cn
http://YTc4XxOo.mwqbp.cn
http://OkkLTSOG.mwqbp.cn
http://DNELzXPN.mwqbp.cn
http://BqmiCIWG.mwqbp.cn
http://www.dtcms.com/a/368934.html

相关文章:

  • 让语言模型自我进化:探索 Self-Refine 的迭代反馈机制
  • Kubernetes(k8s) po 配置持久化挂载(nfs)
  • 支持二次开发的代练App源码:订单管理、代练监控、安全护航功能齐全,一站式解决代练护航平台源码(PHP+ Uni-app)
  • proble1111
  • Ubuntu 24.04.2安装k8s 1.33.4 配置cilium
  • nextcyber——暴力破解
  • Process Explorer 学习笔记(第三章3.2.3):工具栏与参考功能
  • C++两个字符串的结合
  • c51串口通信原理及实操
  • Java垃圾回收算法详解:从原理到实践的完整指南
  • MongoDB 6.0 新特性解读:时间序列集合与加密查询
  • IAR借助在瑞萨RH850/U2A MCU MCAL支持,加速汽车软件开发
  • 状压 dp --- 棋盘覆盖问题
  • 机器学习周报十二
  • 力扣:2322. 从树中删除边的最小分数
  • 人工智能常见分类
  • C++ 音视频开发常见面试题及答案汇总
  • C/C++ Linux系统编程:线程控制详解,从线程创建到线程终止
  • swoole 中 Coroutine\WaitGroup 和channel区别和使用场景
  • HDFS架构核心
  • Python的语音配音软件,使用edge-tts进行文本转语音,支持多种声音选择和语速调节
  • 每周资讯 | 中国游戏市场将在2025年突破500亿美元;《恋与深空》收入突破50亿元
  • 别再手工缝合API了!开源LLMOps神器LMForge,让你像搭积木一样玩转AI智能体!
  • 问卷系统项目自动化测试
  • 事务管理的选择:为何 @Transactional 并非万能,TransactionTemplate 更值得信赖
  • React Fiber 风格任务调度库
  • Sentinel和Cluster,到底该怎么选?
  • 紧固卓越,智选固万基——五金及紧固件一站式采购新典范
  • android 四大组件—Activity源码详解
  • B树,B+树,B*树(无代码)