使用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 
  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Calvin880828

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值