使用Jetpack Compose实现具有多选功能的图片网格

使用Jetpack Compose实现具有多选功能的图片网格

在现代应用中,多选功能是一项常见且重要的需求。例如,Google Photos允许用户轻松选择多个照片进行分享、添加到相册或删除。在本文中,我们将展示如何使用Jetpack Compose实现类似的多选行为,最终效果如下:
图片网格多选功能效果图

主要步骤

  1. 实现基本网格
  2. 为网格元素添加选择状态
  3. 添加手势处理,实现拖动选择/取消选择元素
  4. 完善界面,使元素看起来像照片

实现基本网格

首先,我们使用LazyVerticalGrid实现基本网格,使应用能够在所有屏幕尺寸上良好运行。较大屏幕显示更多列,较小屏幕显示更少列。以下代码展示了如何实现一个简单的网格:

@Composable
private fun PhotoGrid() {
   
  val photos by rememberSaveable {
    mutableStateOf(List(100) {
    it }) }

  LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 128.dp),
    verticalArrangement = Arrangement.spacedBy(3.dp),
    horizontalArrangement = Arrangement.spacedBy(3.dp)
  ) {
   
    items(photos, key = {
    it }) {
   
      Surface(
        tonalElevation = 3.dp,
        modifier = Modifier.aspectRatio(1f)
      ) {
   }
    }
  }
}

在这里,我们使用LazyVerticalGrid创建一个自适应的网格,并使用PhotoItem来展示每个元素。尽管目前只是显示一个简单的有色Surface,但我们已经有了一个可以滚动的网格。
基本网格

添加选择状态

为了实现多选功能,我们需要追踪当前选中的元素及是否处于选择模式,并使网格元素反映这些状态。首先,将网格元素提取为独立的可组合项PhotoItem,并反映其选择状态:

@Composable
private fun ImageItem(
  selected: Boolean, inSelectionMode: Boolean, modifier: Modifier
) {
   
  Surface(
    tonalElevation = 3.dp,
    contentColor = MaterialTheme.colorScheme.primary,
    modifier = modifier.aspectRatio(1f)
  ) {
   
    if (inSelectionMode) {
   
      if (selected) {
   
        Icon(Icons.Default.CheckCircle, null)
      } else {
   
        Icon(Icons.Default.RadioButtonUnchecked, null)
      }
    }
  }
}


这个可组合项根据传入的状态显示不同的图标。当用户点击一个元素时,我们将其ID添加或移除到选中集合中,并根据选中集合的状态确定是否处于选择模式:

@Composable
private fun PhotoGrid() {
   
  val photos by rememberSaveable {
    mutableStateOf(List(100) {
    it }) }
  val selectedIds = rememberSaveable {
    mutableStateOf(emptySet<Int>()) } // NEW
  val inSelectionMode by remember {
    derivedStateOf {
    selectedIds.value.isNotEmpty() } } // NEW


  LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 128.dp),
    verticalArrangement = Arrangement.spacedBy(3.dp),
    horizontalArrangement = Arrangement.spacedBy(3.dp)
  ) {
   
    items(photos, key = {
    it }) {
    id ->
      val selected = selectedIds.value.contains(id) // NEW
      ImageItem(selected, inSelectionMode, Modifier.clickable {
    // NEW
        selectedIds.value = if (selected) {
   
          selectedIds.value.minus(id)
        } else {
   
          selectedIds.value.plus(id)
        }
      })
    }
  }
}

添加手势处理

现在我们追踪了选中状态,可以实现手势处理来增加和移除选中的元素。我们的需求如下:

  1. 通过长按进入选择模式
  2. 长按后拖动以选择或取消选择从起点到终点的所有元素
  3. 在选择模式下,点击元素以选择或取消选择它们
  4. 长按已选中的元素不执行任何操作

为了处理这些手势,我们需要在网格中添加手势处理,而不是在单个元素中添加。我们使用LazyGridState和拖动位置来检测当前指针指向的元素:

@Composable
private fun PhotoGrid() {
   
  val photos by rememberSaveable {
    mutableStateOf(List(100) {
    it }) }
  val selectedIds = rememberSaveable {
    mutableStateOf(emptySet<Int>()) }
  val inSelectionMode by remember {
    derivedStateOf {
    selectedIds.value.isNotEmpty() } }

  val state = rememberLazyGridState() // NEW

  LazyVerticalGrid(
    state = state, // NEW
    columns = GridCells.Adaptive(minSize = 128.dp),
    verticalArrangement = Arrangement.spacedBy(3.dp),
    horizontalArrangement = Arrangement.spacedBy(3.dp),
    modifier = Modifier.photoGridDragHandler(state, selectedIds) // NEW
  ) {
   
    //..
  }
}

在上述代码中,我们使用pointerInput修饰符和detectDragGesturesAfterLongPress方法设置拖动处理。我们在onDragStart中设置初始元素,在onDrag中更新当前指针指向的元素。

fun Modifier.photoGridDragHandler(
  lazyGridState: LazyGridState,
  selectedIds: MutableState<Set<Int>>
) = pointerInput(Unit) {
   
  var initialKey: Int? = null
  var currentKey: Int? = null
  detectDragGesturesAfterLongPress(
    onDragStart = {
    offset -> .. },
    onDragCancel = {
    initialKey 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/754962.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【redis】Redis AOF

1、AOF的基本概念 AOF持久化方式是通过保存Redis所执行的写命令来记录数据库状态的。AOF以日志的形式来记录每个写操作&#xff08;增量保存&#xff09;&#xff0c;将Redis执行过的所有写指令记录下来&#xff08;读操作不记录&#xff09;。AOF文件是一个只追加的文件&…

Redis 高级数据结构业务实践

0、前言 本文所有代码可见 > 【gitee code demo】 本文会涉及 hyperloglog 、GEO、bitmap、布隆过滤器的介绍和业务实践 1、HyperLogLog 1.1、功能 基数统计&#xff08;去重&#xff09; 1.2、redis api 命令作用案例PFADD key element [element ...]添加元素到keyPF…

PortSip测试

安装PBX 下载 免费下载 PortSIP PBX 安装PBX&#xff0c;安装后&#xff0c;运行 &#xff0c;默认用户是admin 密码是admin&#xff0c;然后配置IP 为192.168.0.189 设置域名为192.168.0.189 配置分机 添加分机&#xff0c;添加了10001、10002、9999 三个分机&#xff0c…

深度学习实验第T2周:彩色图片分类

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/)** 目录 一、前言 目标 二、我的环境&#…

【Linux进程通信】进程间通信介绍、匿名管道原理分析

目录 进程通信是什么&#xff1f; 进程通信的目的 进程通信的本质 匿名管道&#xff1a;基于文件级别的通信方式 站在文件描述符角度-深度理解管道原理 进程通信是什么&#xff1f; 进程通信就是两个或多个进程之间进行数据层面的交互。 进程通信的目的 1.数据传输&#x…

已解决java.security.acl.LastOwnerException:无法移除最后一个所有者的正确解决方法,亲测有效!!!

已解决java.security.acl.LastOwnerException&#xff1a;无法移除最后一个所有者的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 1. 检查当前所有者数量 2. 添加新的所有者 3. 维…

mac Canon打印机连接教程

官网下载安装驱动&#xff1a; 选择打印机类型和mac系统型号下载即可 Mac PS 打印机驱动程序 双击安装 系统偏好设置 点击“”添加&#xff1a; OK可打印玩耍&#xff01;&#xff01; 备注&#xff1a; 若需扫描&#xff0c;下载扫描程序&#xff1a; 备注&#xff1a;…

设置小蓝熊的CPU亲和性、CPU优先级再设置法环的CPU亲和性

# 适用于Windows系统 # 时间 : 2024-06-28 # 作者 : 三巧(https://blog.csdn.net/qq_39124701) # 文件名 : 设置小蓝熊的CPU亲和性、CPU优先级再设置法环的CPU亲和性.ps1 # 使用方法: 打开记事本&#xff0c;将所有代码复制到记事本中&#xff0c;保存文件时候修改文件后…

Hugging Face Accelerate 两个后端的故事:FSDP 与 DeepSpeed

社区中有两个流行的零冗余优化器 (Zero Redundancy Optimizer&#xff0c;ZeRO)算法实现&#xff0c;一个来自DeepSpeed&#xff0c;另一个来自PyTorch。Hugging FaceAccelerate对这两者都进行了集成并通过接口暴露出来&#xff0c;以供最终用户在训练/微调模型时自主选择其中之…

zabbix-server的搭建

zabbix-server的搭建 部署 zabbix 服务端(192.168.99.180) rpm -ivh https://mirrors.aliyun.com/zabbix/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm cd /etc/yum.repos.d sed -i s#http://repo.zabbix.com#https://mirrors.aliyun.com/zabbix# zabbix.r…

关于FPGA对 DDR4 (MT40A256M16)的读写控制 4

关于FPGA对 DDR4 &#xff08;MT40A256M16&#xff09;的读写控制 4 语言 &#xff1a;Verilg HDL 、VHDL EDA工具&#xff1a;ISE、Vivado、Quartus II 关于FPGA对 DDR4 &#xff08;MT40A256M16&#xff09;的读写控制 4一、引言二、DDR4 SDRAM设备中模式寄存器重要的模式寄存…

Arduino - LED 矩阵

Arduino - LED 矩阵 Arduino - LED Matrix LED matrix display, also known as LED display, or dot matrix display, are wide-used. In this tutorial, we are going to learn: LED矩阵显示器&#xff0c;也称为LED显示器&#xff0c;或点阵显示器&#xff0c;应用广泛。在…

“Hello, World!“ 历史由来

布莱恩W.克尼汉&#xff08;Brian W. Kernighan&#xff09;—— Unix 和 C 语言背后的巨人 布莱恩W.克尼汉在 1942 年出生在加拿大多伦多&#xff0c;他在普林斯顿大学取得了电气工程的博士学位&#xff0c;2000 年之后取得普林斯顿大学计算机科学的教授教职。 1973 年&#…

C++ | Leetcode C++题解之第203题移除链表元素

题目&#xff1a; 题解&#xff1a; class Solution { public:ListNode* removeElements(ListNode* head, int val) {struct ListNode* dummyHead new ListNode(0, head);struct ListNode* temp dummyHead;while (temp->next ! NULL) {if (temp->next->val val) {…

小柴冲刺软考中级嵌入式系统设计师系列一、计算机系统基础知识(1)嵌入式计算机系统概述

flechazohttps://www.zhihu.com/people/jiu_sheng 小柴冲刺嵌入式系统设计师系列总目录https://blog.csdn.net/qianshang52013/article/details/139975720?spm1001.2014.3001.5501 根据IEEE&#xff08;国际电气电子工程师协会&#xff09;的定义&#xff0c;嵌入式系统是&q…

Linux高并发服务器开发(六)线程

文章目录 1. 前言2 线程相关操作3 线程的创建4 进程数据段共享和回收5 线程分离6 线程退出和取消7 线程属性&#xff08;了解&#xff09;8 资源竞争9 互斥锁9.1 同步与互斥9.2 互斥锁 10 死锁11 读写锁12 条件变量13 生产者消费者模型14 信号量15 哲学家就餐 1. 前言 进程是C…

哪吒汽车,正在等待“太乙真人”的拯救

文丨刘俊宏 在360创始人、哪吒汽车股东周鸿祎近日连续且着急的“督战”中&#xff0c;哪吒汽车&#xff08;下简称哪吒&#xff09;终究还是顶不住了。 6月26日&#xff0c;哪吒通过母公司合众新能源在港交所提交了IPO文件&#xff0c;急迫地希望成为第五家登陆港股的造车新势力…

uniapp中实现瀑布流 短视频页面展示

直接上干货 第一部分为结构 <swiper class"list" :currentindex change"swiperchange" scrolltolower"onReachBottom"><swiper-item style"overflow: scroll;" v-for"(item,index) in 2" :key"index"&g…

DataV大屏组件库

DataV官方文档 DataV组件库基于Vue &#xff08;React版 (opens new window)&#xff09; &#xff0c;主要用于构建大屏&#xff08;全屏&#xff09;数据展示页面即数据可视化&#xff0c;具有多种类型组件可供使用&#xff1a; 源码下载

Golang | Leetcode Golang题解之第204题计数质数

题目&#xff1a; 题解&#xff1a; func countPrimes(n int) int {primes : []int{}isPrime : make([]bool, n)for i : range isPrime {isPrime[i] true}for i : 2; i < n; i {if isPrime[i] {primes append(primes, i)}for _, p : range primes {if i*p > n {break}…