JetpackCompose从入门到实战学习笔记6——手势的简单使用
手势
Compose 提供了多种 API,可帮助您检测用户互动生成的手势。API 涵盖各种用例:
- 其中一些级别较高,旨在覆盖最常用的手势。例如,
clickable
修饰符可用于轻松检测点击,此外它还提供无障碍功能,并在点按时显示视觉指示(例如涟漪)。 - 还有一些不太常用的手势检测器,它们在较低级别提供更大的灵活性,例如
PointerInputScope.detectTapGestures
或PointerInputScope.detectDragGestures
,但不提供额外功能。
1.点击
clickable
修饰符允许应用检测对已应用该修饰符的元素的点击。
@Composable
fun ClickableSample() {
val count = remember { mutableStateOf(0) }
// content that you want to make clickable
Text(
text = count.value.toString(),
modifier = Modifier.clickable { count.value += 1 }
)
}
2.效果预览:
3.滚动:
-
滚动修饰符
-
verticalScroll
和horizontalScroll
修饰符提供一种最简单的方法,可让用户在元素内容边界大于最大尺寸约束时滚动元素。利用verticalScroll
和horizontalScroll
修饰符,您无需转换或偏移内容。
@Composable
fun ScrollBoxes() {
Column(
modifier = Modifier
.background(Color.LightGray)
.size(200.dp)
.verticalScroll(rememberScrollState())
) {
repeat(20) {
Text("滚动的Item $it", modifier = Modifier.padding(2.dp))
}
}
}
4.效果预览:
5.可滚动的修饰符
scrollable
修饰符与滚动修饰符不同,区别在于 scrollable
可检测滚动手势,但不会偏移其内容。必须有 ScrollableState
,此修饰符才能正常工作。构造 ScrollableState
时,您必须提供一个 consumeScrollDelta
函数,该函数将在每个滚动步骤调用(通过手势输入、流畅滚动或快速滑动),并且增量以像素为单位。该函数必须返回所消耗的滚动距离,以确保在存在具有 scrollable
修饰符的嵌套元素时,可以正确传播相应事件。
@Composable
private fun ScrollBoxesSmooth() {
var offsets: Float by remember { mutableStateOf(0f) }
Box(
Modifier
.size(200.dp)
.scrollable(
orientation = Orientation.Vertical,
// Scrollable state: describes how to consume
// scrolling delta and update offset
state = rememberScrollableState { delta ->
offsets += delta
delta
}
)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text(offsets.toString())
}
}
6.效果预览:
7.嵌套滚动
- Compose 支持嵌套滚动,可让多个元素对一个滚动手势做出回应。典型的嵌套滚动示例是在一个列表中嵌套另一个列表,而收起工具栏则是一种较为复杂的嵌套滚动情况。
- 自动嵌套滚动
- 简单的嵌套滚动无需您执行任何操作。启动滚动操作的手势会自动从子级传播到父级,这样一来,当子级无法进一步滚动时,手势就会由其父元素处理。
- 部分 Compose 组件和修饰符原生支持自动嵌套滚动,包括:
verticalScroll
、horizontalScroll
、scrollable
、Lazy
API 和TextField
。这意味着,当用户滚动嵌套组件的内部子级时,之前的修饰符会将滚动增量传播到支持嵌套滚动的父级。 - 以下示例显示的元素应用了
verticalScroll
修饰符,而其所在的容器同样应用了verticalScroll
修饰符。
@Composable
fun ScrollableSample() {
// actual composable state
var offset by remember { mutableStateOf(0f) }
Box(
Modifier
.size(200.dp)
.scrollable(
orientation = Orientation.Vertical,
// Scrollable state: describes how to consume
// scrolling delta and update offset
state = rememberScrollableState { delta ->
offset += delta
delta
}
)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text(offset.toString())
}
val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White)
Box(
modifier = Modifier
.background(Color.LightGray)
.verticalScroll(rememberScrollState())
.padding(32.dp)
) {
Column {
repeat(6) {
Box(
modifier = Modifier
.height(128.dp)
.verticalScroll(rememberScrollState())
) {
Text(
"Scroll here",
modifier = Modifier
.border(12.dp, Color.DarkGray)
.background(brush = gradient)
.padding(24.dp)
.height(200.dp)
)
}
}
}
}
}
8.效果预览:
9.拖动:
draggable
修饰符是向单一方向拖动手势的高级入口点,并且会报告拖动距离(以像素为单位)。
请务必注意,此修饰符与 scrollable
类似,仅检测手势。您需要保存状态并在屏幕上表示
9.1.单一拖动:
@Composable
fun singleDraggableSample() {
var offsetX by remember { mutableStateOf(0f) }
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.offset { IntOffset(offsetX.roundToInt(), 0) }
.draggable(
state = rememberDraggableState {
offsetX += it
},
orientation = Orientation.Horizontal
)
.background(Color.Red),
contentAlignment = Alignment.Center
) {
Text(
text = "单一拖动",
style = MaterialTheme.typography.bodySmall.copy(color = Color.White)
)
}
}
9.2.单一拖动效果预览:
9.3.任意位置的拖动:
@Composable
fun DraggableSample() {
Box(modifier = Modifier.fillMaxSize()) {
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.background(Color.Blue)
.size(200.dp)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX += dragAmount.x
offsetY += dragAmount.y
}
}
) {
Text(
text = "任意位置拖动",
style = MaterialTheme.typography.bodySmall.copy(color = Color.White)
)
}
}
}
9.4 任意位置效果预览:
10.左右滑动:
val width = 200.dp
val squareSize = 100.dp
val swipeAbleState = rememberSwipeableState(0)
val sizePx = with(LocalDensity.current) { squareSize.toPx() }
val anchors = mapOf(0f to 0, sizePx to 1) // Maps anchor points (in px) to states
Box(
modifier = Modifier
.width(width)
.swipeable(
state = swipeAbleState,
anchors = anchors,
thresholds = { _, _ -> FractionalThreshold(0.3f) },
orientation = Orientation.Horizontal
)
.background(Color.LightGray),
contentAlignment = Alignment.CenterStart
) {
Box(
Modifier
.offset { IntOffset(swipeAbleState.offset.value.roundToInt(), 0) }
.size(squareSize)
.background(Color.Cyan)
)
Text(text = "左右滑动", textAlign = TextAlign.Center, style = MaterialTheme.typography.bodySmall.copy(color = Color.White))
}
11.效果预览:
12.多点触控:平移、缩放、旋转
@Composable
fun TransformableSample() {
// set up all transformation states
var scale by remember { mutableStateOf(1f) }
var rotation by remember { mutableStateOf(0f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
scale *= zoomChange
rotation += rotationChange
offset += offsetChange
}
Box(
Modifier
// apply other transformations like rotation and zoom
// on the pizza slice emoji
.graphicsLayer(
scaleX = scale,
scaleY = scale,
rotationZ = rotation,
translationX = offset.x,
translationY = offset.y
)
// add transformable to listen to multitouch transformation events
// after offset
.transformable(state = state)
.background(Color.Blue)
.size(200.dp)
)
}
13.完整代码:
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun AllGestures() {
Column(modifier = Modifier.fillMaxSize()) {
// content that you want to make clickable
Row {
val count = remember { mutableStateOf(0) }
Box(
modifier = Modifier
.size(200.dp)
.background(Color.DarkGray)
.clickable { count.value += 1 },
contentAlignment = Alignment.Center
) {
Text(
text = "点击改变" + count.value.toString(),
style = MaterialTheme.typography.bodySmall.copy(color = Color.White)
)
}
Spacer(modifier = Modifier.width(30.dp))
// Smoothly scroll 100px on first composition
val states = rememberScrollState()
LaunchedEffect(Unit) { states.animateScrollTo(200) }
Column(
modifier = Modifier
.background(Color.Magenta)
.size(200.dp)
.padding(horizontal = 8.dp)
.verticalScroll(states)
) {
repeat(20) {
Text(
"滚动的Item $it",
modifier = Modifier.padding(2.dp),
style = MaterialTheme.typography.bodySmall.copy(color = Color.White)
)
}
}
Spacer(modifier = Modifier.width(20.dp))
// actual composable state
var offsets: Float by remember { mutableStateOf(0f) }
Box(
Modifier
.size(200.dp)
.scrollable(
orientation = Orientation.Vertical,
// Scrollable state: describes how to consume
// scrolling delta and update offset
state = rememberScrollableState { delta ->
offsets += delta
delta
}
)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text(offsets.toString())
}
}
//嵌套滚动
Row() {
Spacer(Modifier.width(20.dp))
val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White)
Box(
modifier = Modifier
.background(Color.LightGray)
.verticalScroll(rememberScrollState())
.padding(32.dp)
) {
Column {
repeat(6) {
Box(
modifier = Modifier
.height(128.dp)
.verticalScroll(rememberScrollState())
) {
Text(
"Scroll here",
modifier = Modifier
.border(12.dp, Color.DarkGray)
.background(brush = gradient)
.padding(24.dp)
.height(200.dp)
)
}
}
}
}
Spacer(modifier = Modifier.width(20.dp))
//左右滑动
val width = 200.dp
val squareSize = 100.dp
val swipeAbleState = rememberSwipeableState(0)
val sizePx = with(LocalDensity.current) { squareSize.toPx() }
val anchors = mapOf(0f to 0, sizePx to 1) // Maps anchor points (in px) to states
Box(
modifier = Modifier
.width(width)
.swipeable(
state = swipeAbleState,
anchors = anchors,
thresholds = { _, _ -> FractionalThreshold(0.3f) },
orientation = Orientation.Horizontal
)
.background(Color.LightGray),
contentAlignment = Alignment.CenterStart
) {
Box(
Modifier
.offset { IntOffset(swipeAbleState.offset.value.roundToInt(), 0) }
.size(squareSize)
.background(Color.Cyan)
)
Text(
text = "左右滑动",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodySmall.copy(color = Color.White)
)
}
Spacer(modifier = Modifier.width(20.dp))
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Box(
modifier = Modifier
.size(100.dp)
.offset { IntOffset(offsetX.roundToInt(), 0) }
.draggable(
state = rememberDraggableState {
offsetX += it
},
orientation = Orientation.Horizontal
)
.background(Color.Red),
contentAlignment = Alignment.Center
) {
Text(
text = "单一拖动",
style = MaterialTheme.typography.bodySmall.copy(color = Color.White)
)
}
Spacer(modifier = Modifier.width(20.dp))
//拖动任意位置
var offX by remember { mutableStateOf(0f) }
var offY by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offX.roundToInt(), offY.roundToInt()) }
.background(Color.Blue)
.size(200.dp)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offX += dragAmount.x
offY += dragAmount.y
}
},
contentAlignment = Alignment.Center
) {
Text(
text = "任意位置拖动",
style = MaterialTheme.typography.bodySmall.copy(color = Color.White)
)
}
Spacer(modifier = Modifier.width(20.dp))
// set up all transformation states
var scale by remember { mutableStateOf(1f) }
var rotation by remember { mutableStateOf(0f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
scale *= zoomChange
rotation += rotationChange
offset += offsetChange
}
Box(
Modifier
// apply other transformations like rotation and zoom
// on the pizza slice emoji
.graphicsLayer(
scaleX = scale,
scaleY = scale,
rotationZ = rotation,
translationX = offset.x,
translationY = offset.y
)
// add transformable to listen to multitouch transformation events
// after offset
.transformable(state = state)
.background(Color.Blue)
.width(200.dp)
)
}
}
}