使用Jetpack Compose实现具有多选功能的图片网格
在现代应用中,多选功能是一项常见且重要的需求。例如,Google Photos允许用户轻松选择多个照片进行分享、添加到相册或删除。在本文中,我们将展示如何使用Jetpack Compose实现类似的多选行为,最终效果如下:
主要步骤
- 实现基本网格
- 为网格元素添加选择状态
- 添加手势处理,实现拖动选择/取消选择元素
- 完善界面,使元素看起来像照片
实现基本网格
首先,我们使用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>()) } // NEWval inSelectionMode by remember { derivedStateOf { selectedIds.value.isNotEmpty() } } // NEWLazyVerticalGrid(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) // NEWImageItem(selected, inSelectionMode, Modifier.clickable { // NEWselectedIds.value = if (selected) {selectedIds.value.minus(id)} else {selectedIds.value.plus(id)}})}}
}
添加手势处理
现在我们追踪了选中状态,可以实现手势处理来增加和移除选中的元素。我们的需求如下:
- 通过长按进入选择模式
- 长按后拖动以选择或取消选择从起点到终点的所有元素
- 在选择模式下,点击元素以选择或取消选择它们
- 长按已选中的元素不执行任何操作
为了处理这些手势,我们需要在网格中添加手势处理,而不是在单个元素中添加。我们使用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() // NEWLazyVerticalGrid(state = state, // NEWcolumns = 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? = nullvar currentKey: Int? = nulldetectDragGesturesAfterLongPress(onDragStart = { offset -> .. },onDragCancel = { initialKey