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

news2024/11/25 22:43:18

使用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.coloradmin.cn/o/1875542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习笔记: 最详尽解释R 平方 (R²)

欢迎收藏Star我的Machine Learning Blog:https://github.com/purepisces/Wenqing-Machine_Learning_Blog。如果收藏star, 有问题可以随时与我交流, 谢谢大家&#xff01; 理解 R 平方 (R) 什么是相关性 R? 相关性测量两个定量变量&#xff08;例如&#xff0c;重量和尺寸&a…

iOS shouldRecognizeSimultaneouslyWithGestureRecognizer 调用机制探索

shouldRecognizeSimultaneouslyWithGestureRecognizer 经常会看到&#xff0c;但是一直没有弄清楚其中的原理和运行机制&#xff0c;今天专门研究下 其运行规律 我们准备三个视图&#xff0c;如下&#xff0c;红色的是绿色视图的父视图&#xff0c;绿色视图 是蓝色视图的父视图…

layui在表格中嵌入上传按钮,并修改上传进度条

当需要在表格中添加上传文件按钮&#xff0c;并不需要弹出填写表单的框的时候&#xff0c;需要在layui中&#xff0c;用按钮触发文件选择 有一点需要说明的是&#xff0c;layui定义table并不是在定义的标签中渲染&#xff0c;而是在紧接着的标签中渲染&#xff0c;所以要获取实…

Unity WebGL项目问题记录

一、资源优化 可通过转换工具配套提供的资源优化工具&#xff0c;将游戏内纹理资源针对webgl导出做优化。 工具入口&#xff1a; 工具介绍 Texture 搜索规则介绍 已开启MipMap: 搜索已开启了MipMap的纹理。 NPOT: 搜索非POT图片。 isReadable: 搜索已开启readable纹理。 …

为什么有的手机卡没有语音功能呢?

大家好&#xff0c;今天这篇文章为大家介绍一下&#xff0c;无通话功能的手机卡&#xff0c; 在网上申请过手机卡的朋友应该都知道&#xff0c;现在有这么一种手机卡&#xff0c;虽然是运营商推出的正规号卡&#xff0c;但是却屏蔽了通话功能&#xff0c;你知道这是为什么吗&am…

第六节:如何解决@ComponentScan只能扫描当前包及子包(自学Spring boot 3.x的第一天)

大家好&#xff0c;我是网创有方&#xff0c;继上节咱们使用了Component和ComponentScan的方法实现了获取IOC容器中的Bean&#xff0c;但是存在一个问题&#xff0c;就是必须把AppConfig和要扫描的bean类放在同一个目录下&#xff0c;这样就导致了AppConfig类和bean类在同一个目…

T4打卡 学习笔记

所用环境 ● 语言环境&#xff1a;Python3.11 ● 编译器&#xff1a;jupyter notebook ● 深度学习框架&#xff1a;TensorFlow2.16.1 ● 显卡&#xff08;GPU&#xff09;&#xff1a;NVIDIA GeForce RTX 2070 设置GPU from tensorflow import keras from tensorflow.keras…

Dahlia Hart: Stylized Casual Character(休闲角色模型)

此包包含两个发型和两个服装&#xff0c;每个都有多种颜色选择。每个发型都适合与物理资源一起使用&#xff0c;并包含各种表情和音素混合形状。 下载&#xff1a;​​Unity资源商店链接资源下载链接 效果图&#xff1a;

自适应IT互联网营销企业网站pbootcms模板

模板介绍 一款蓝色自适应IT互联网营销企业网站pbootcms模板&#xff0c;该模板采用响应式设计&#xff0c;可自适应手机端&#xff0c;适合一切网络技术公司、互联网IT行业&#xff0c;源码下载&#xff0c;为您提供了便捷哦。 模板截图 源码下载 自适应IT互联网营销企业网站…

C++精解【9】

文章目录 大整数GMP概述GMP安装 [cygwin](https://cygwin.com/install.html)安装 gmpexample Eigen基本属性和运算 大整数GMP 概述 GMP GMP是一个用于任意精度算术的免费库&#xff0c;可对有符号整数、有理数和浮点数进行操作。除了运行GMP的机器的可用内存所暗示的精度外&…

【建设方案】工单系统建设方案(Word原件)

工单管理系统解决方案 1、工单创建&#xff1a;根据告警信息创建工单。 2、工单管理&#xff1a;列表形式展示所有工单信息及进度状态。 3、工单处理&#xff1a;对接收的工单进行处理反馈。 4、工单催办&#xff1a;根据工单时效自动发送工单催办消息通知。 5、工单归档&#…

鲲鹏arm服务器部署paddleOCR

1. 部署环境信息查看 1.1 操作系统 $ cat /etc/os-release PRETTY_NAME"UnionTech OS Server 20" NAME"UnionTech OS Server 20" VERSION_ID"20" VERSION"20" ID"uos" PLATFORM_ID"platform:uel20" HOME_URL&q…

数据结构与算法笔记:高级篇 - 搜索:如何用 A* 搜索算法实现游戏中的寻路功能?

概述 魔兽世界、仙剑奇侠传这类 MMRPG 游戏&#xff0c;不知道你玩过没有&#xff1f;在这些游戏中&#xff0c;有一个非常重要的功能&#xff0c;那就是任务角色自动寻路。当任务处于游戏地图中的某个位置时&#xff0c;我们用鼠标点击另外一个相对较远的位置&#xff0c;任务…

Java学习 - 布隆过滤器

前置需求 需求 已经有50亿个电话号码&#xff0c;现在给出10万个电话号码&#xff0c;如何快速准确地判断这些电话号码是否已经存在&#xff1f; 参考方案 通过数据库查询&#xff1a;比如MySQL&#xff0c;性能不行&#xff0c;速度太慢将数据先放进内存&#xff1a;50亿*8字…

用pycharm进行python爬虫的步骤

使用 pycharm 进行 python 爬虫的步骤&#xff1a;下载并安装 pycharm。创建一个新项目。安装 requests 和 beautifulsoup 库。编写爬虫脚本&#xff0c;包括获取页面内容、解析 html 和提取数据的代码。运行爬虫脚本。保存和处理提取到的数据。 用 PyCharm 进行 Python 爬虫的…

机器人控制系列教程之Simulink中模型搭建(1)

机器人模型获取 接上期&#xff1a;机器人控制系列教程之控制理论概述&#xff0c;文中详细讲解了如何通过Solidworks软件导出URDF格式的文件。文末提到了若需要将其导入到Simulink中可在命令行中输入smimport(urdf/S_Robot_urdf.urdf)&#xff0c;MATLAB将自动打开Simulink以…

TCP单进程循环服务器程序与单进程客户端程序

实验目的 理解并掌握以下内容: 网络进程标识(即套接字地址)在Linux中的数据结构与地址转换函数。网络字节序与主机字节序的定义、转换以及相关函数在网络编程中的应用。数据结构内存对齐的基本规则,以及基于数据结构构建PDU的基本方法。TCP单进程循环服务器与单进程客户端的…

【ai】ubuntu18.04 找不到 nvcc --version问题

nvcc --version显示command not found问题 这个是cuda 库: windows安装了12.5 : 参考大神:解决nvcc --version显示command not found问题 原文链接:https://blog.csdn.net/Flying_sfeng/article/details/103343813 /usr/local/cuda/lib64 与 /usr/local/cuda-11.3/lib64 完…

【机器学习300问】133、什么是降维?有哪些降维的方法?

假如你有一本非常厚的书&#xff0c;每一章代表一个特征维度&#xff0c;而书中的故事&#xff08;数据点&#xff09;在每个章节&#xff08;维度&#xff09;都有详细的描述。但是&#xff0c;读者&#xff08;模型&#xff09;发现很难理解和记忆这个复杂的故事&#xff0c;…

视频监控业务平台LntonCVS国标视频综合管理平台功能及技术优势

随着安防行业的快速进步&#xff0c;传统的视频监控平台正在与先进的技术和互联网技术融合&#xff0c;包括5G通信、GIS、大数据、云计算、边缘计算、AI识别、智能分析和视频直播等。这些技术的整合形成了综合性视频监控管理平台&#xff0c;具备集中管理、多级联网共享、互联互…