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://www.pswp.cn/diannao/98115.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98115.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/98115.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring DI详解--依赖注入的三种方式及优缺点分析

一、什么是DI&#xff1f;DI&#xff08;Dependency Injection&#xff0c;依赖注入&#xff09;是 IoC&#xff08;控制反转&#xff09; 思想的最典型实现方式&#xff0c;核心目标只有一个&#xff1a;让对象不再自己“找”依赖&#xff0c;而是由外部容器“送”依赖进来&am…

PPT中如何将设置的文本框边距设为默认

通常&#xff0c;在PPT中插入的文本框边距比较窄&#xff0c;线条和填充都为空&#xff0c;我们可以根据自己的需要调整文本框的边距&#xff0c;以及填充颜色、线条颜色和样式等&#xff0c;并且把这个设置为默认的文本框&#xff0c;然后就可以直接插入相同边距和样式的文本框…

疯狂星期四文案网第61天运营日记

网站运营第61天&#xff0c;点击观站&#xff1a; 疯狂星期四 crazy-thursday.com 全网最全的疯狂星期四文案网站 运营报告 今日访问量 今日搜索引擎收录情况 收录好像便正常了&#xff0c;准备加快发布频率了

开源容器管理平台Rancher

Rancher 是一个开源的 容器管理平台&#xff0c;用于简化 Kubernetes 和 Docker 的部署、运维和安全管理。它提供了一套用户友好的工具&#xff0c;帮助开发者和运维团队在企业环境中高效地管理容器化应用。核心功能Kubernetes 管理 支持多集群管理&#xff08;本地、云、边缘等…

AI在目前会议直播系统中应用

AI在目前会议直播系统中有多种使用场景,以下是一些常见的例子: 会议内容实时处理 实时转写与翻译:借助AI语音识别算法,会议直播系统可实现语音的实时转写,支持多种语言和方言,转写准确率达98%以上。同时,部分系统还配备实时翻译功能,将发言语音实时翻译成多种语言字幕,…

网络安全A模块专项练习任务十解析

任务十&#xff1a;Linux操作系统安全配置-3任务环境说明&#xff1a; (Linux)系统&#xff1a;用户名root&#xff0c;密码1234561.设置账户密码有效期&#xff0c;密码最大有效期为30&#xff0c;可修改密码最小天数为5&#xff0c;密码长度为6&#xff0c;密码失效前4天通知…

WorkMagic-AI驱动的营销SaaS服务平台

本文转载自&#xff1a;WorkMagic-AI驱动的营销SaaS服务平台 - Hello123工具导航 ** 一、&#x1f916; WorkMagic&#xff1a;跨境电商的 AI 营销自动化神器 WorkMagic 是一家专注于为跨境电商提供AI 驱动营销自动化解决方案的 SaaS 平台&#xff0c;成立于 2023 年。它通过…

Java 线程重点 面试笔记(线程状态,安全停止线程..)

包括线程状态、Thread.yield()、Thread.join()、线程安全停止、标志位、中断等&#xff0c;都是线程这块秋招的重点。1. 线程状态&#xff08;Thread.State&#xff09;Java 中线程有 6 种状态&#xff1a;状态含义进入条件NEW新建状态Thread t new Thread(...);RUNNABLE可运行…

Zigbee:Polling 终端设备的睡眠机制和功耗

一、Zigbee 设备类型与功耗基础 首先,Zigbee网络中的设备角色决定了其功耗特性。Zigbee定义了三种逻辑设备类型: 协调器 (Coordinator)​​:网络的中心,必须始终供电,不能睡眠。功耗最高。 路由器 (Router)​​:负责中继数据,扩展网络范围。通常也需持续供电,以保持网…

Python迭代协议完全指南:从基础到高并发系统实现

引言&#xff1a;迭代协议的核心价值在Python编程中&#xff0c;迭代协议是构建高效、灵活数据结构的基石。根据2024年Python开发者调查报告&#xff1a;92%的高级数据结构依赖迭代协议85%的数据处理框架基于迭代协议构建78%的并发系统使用自定义迭代器65%的内存优化方案通过迭…

vsan高可用:确保可访问性、全部数据迁移,两种类型权衡

目录1.如果我3台机器&#xff0c;其中有1台机器突然故障&#xff0c;那么走的是保证可用&#xff0c;还是全量数据迁移&#xff1f;这个怎么算&#xff1f;一、先明确&#xff1a;故障场景 vs 维护场景的核心差异二、3台主机故障时&#xff0c;vSAN的具体处理逻辑&#xff08;为…

51单片机1(单片机基础,LED,数码管)

1.嵌入式嵌入式&#xff08;Embedded&#xff09;指的是一种专用计算机系统&#xff0c;它被"嵌入"或内建到一个更大的设备、产品或系统中&#xff0c;作为其核心控制部分&#xff0c;专门用于执行特定的任务或功能。通俗来讲就是以应用为中心&#xff0c;以计算机技…

Aerobits-用于 sUAS 和 UTM/U-Space 的微型 ADS-B 技术(收发器/接收器)和无人机跟踪应答器

Aerobits-用于 sUAS 和 UTM/U-Space 的微型 ADS-B 技术&#xff08;收发器/接收器&#xff09;和无人机跟踪应答器Aerobits 是一家专门为无人机 (UAV) 和无人驾驶飞机开发微型应答器和航空电子系统的公司。我们的硬件和软件解决方案基于专利技术&#xff0c;采用极低 SWaP 封装…

Spring Security资源服务器在高并发场景下的认证性能优化实践指南

Spring Security资源服务器在高并发场景下的认证性能优化实践指南 摘要&#xff1a;本文从原理与实践两个层面&#xff0c;深入解析Spring Security资源服务器在高并发场景下的认证性能优化策略&#xff0c;通过关键源码解读与实际示例&#xff0c;帮助开发者有效提升系统吞吐与…

SQL Server事务隔离级别

SQL Server 提供了多个事务隔离级别&#xff0c;用于控制并发事务如何访问和修改数据时的可见性、锁定行为以及可能遇到的并发问题&#xff08;如脏读、不可重复读、幻读&#xff09;。这些级别在数据一致性、并发性能和锁定开销之间进行权衡。 以下是 SQL Server 支持的主要隔…

DeepSeek R1大模型微调实战-llama-factory的安装与使用

文章目录概要1.安装必要的环境2.安装 PyTorch3.安装 Transformers 和 Datasets4.克隆 LLaMA Factory 仓库和安装LLaMA Factory5.准备数据和模型配置6.运行 LLaMA Factory7.监控和调整8.后续步骤概要 LLaMA Factory 是一个简单易用且高效的大型语言模型训练与微调平台。通过它&…

IDE mac M芯片安装报错:如何解决“InsCode.app 已损坏”,无法打开

IDE mac M芯片安装报错&#xff1a;如何解决“InsCode.app 已损坏”&#xff0c;无法打开 摘要 在 macOS 上安装并运行 InsCode IDE 时&#xff0c;不少开发者会遇到这样的报错&#xff1a; “InsCode.app 已损坏&#xff0c;无法打开。您应该将它移到废纸篓。” 这种情况在 …

EasyExcel:阿里开源的高效 Excel 处理工具,轻松解决 POI 内存溢出问题

在日常开发中&#xff0c;Excel 文件的导入导出是非常常见的需求。无论是数据批量导入、报表生成还是数据备份&#xff0c;我们都离不开对 Excel 的操作。但传统的 POI 框架在处理大数据量 Excel 时&#xff0c;常常会遇到内存溢出的问题&#xff0c;让开发者头疼不已。 今天给…

软件启动时加配置文件 vs 不加配置文件

一、基本概念不加配置文件启动直接执行启动命令&#xff0c;使用软件自带的默认参数。方便、快速&#xff0c;适合测试环境。缺点&#xff1a;灵活性差、配置不可控、不安全。redis-server zookeeper-server-start.sh kafka-server-start.sh指定配置文件启动启动时加载外部配置…

[ubuntu][C++]onnxruntime安装cpu版本后测试代码

下载官方预编译包后&#xff0c;怎么用呢。可以参考这个源码跑测试环境&#xff1a;ubuntu22.04onnxruntime1.18.0测试代码&#xff1a;CMakeLists.txtcmake_minimum_required(VERSION 3.12) project(onnx_test)# 设置C标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD…