使用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>()) } // 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)
}
})
}
}
}
添加手势处理
现在我们追踪了选中状态,可以实现手势处理来增加和移除选中的元素。我们的需求如下:
- 通过长按进入选择模式
- 长按后拖动以选择或取消选择从起点到终点的所有元素
- 在选择模式下,点击元素以选择或取消选择它们
- 长按已选中的元素不执行任何操作
为了处理这些手势,我们需要在网格中添加手势处理,而不是在单个元素中添加。我们使用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