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://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…