单击、双击、长按事情
Compose 中完成点击事情很简单,直接经过Modifier.clickable{}
即可,示例:
@Composable
fun ClickSample() {
var count by remember { mutableStateOf(0) }
Box(
modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center
) {
Text(fontSize = 20.sp,
textAlign = TextAlign.Center,
text = count.toString(),
modifier = Modifier
.size(100.dp)
.clickable { count += 1 }
)
}
}
每次点击Text,对应的count 都会进行自增1,上述示例运用的是Text控件,假如是 Button,不用再运用Modifier.clickable{}了,直接运用内部的onClick即可:
Button(onClick = { ... }) {
Text(text = "点击Button")
}
detectTapGestures
除了单击事情外,还能够处理双击、长按等事情:
suspend fun PointerInputScope.detectTapGestures(
onDoubleTap: ((Offset) -> Unit)? = null,
onLongPress: ((Offset) -> Unit)? = null,
onPress: suspend PressGestureScope.(Offset) -> Unit = NoPressGesture,
onTap: ((Offset) -> Unit)? = null
){ ... }
-
onPress:每次点击都会回调,相当于View系统中的
ACTION_DOWN
,其间offset是回调的点击方位(x, y); - onTap:轻触时回调;
- onDoubleTap: 双击时回调;
- onLongPress:长准时回调,其设定阈值默许是400ms。
运用示例:
Modifier.pointerInput(Unit) {
detectTapGestures(
onPress = { offset -> log("onPress: $offset") },
onTap = { offset -> log("onTap: $offset") },
onDoubleTap = { offset -> log("onDoubleTap: $offset") },
onLongPress = { offset -> log("onLongPress: $offset") },
)
}
几个场景:
1、快速点击并松手:
17:21:12.117 E onPress: Offset(28.5, 29.5)
17:21:12.468 E onTap: Offset(28.5, 29.5)
2、双击:
17:23:16.060 E onPress: Offset(31.5, 27.5)
17:23:16.241 E onPress: Offset(38.5, 25.5)
17:23:16.308 E onDoubleTap: Offset(38.5, 25.5)
3、长按:
17:23:41.329 E onPress: Offset(12.5, 62.5)
17:23:41.634 E onLongPress: Offset(12.5, 62.5)
Scroll 翻滚
-
verticalScroll
和horizontalScroll
修饰符让用户在元素内容鸿沟大于最大尺度束缚时翻滚元素,跟View系统里的ScrollView
或NestedScrollView
是相同的作用。这儿需求留意一点,关于长列表场景,咱们能够运用 LazyColumn 与 LazyRow 组件来完成,而关于一般组件,能够经过xxxScroll() 使其具有翻滚才能。
来看一个官方示例:
@Composable
fun ScrollBoxes() {
Column(
modifier = Modifier
.background(Color.LightGray)
.size(100.dp)
.verticalScroll(rememberScrollState())
) {
repeat(10) {
Text("Item $it", modifier = Modifier.padding(2.dp))
}
}
}
履行作用:
假如想在初次重组时滑动到某个方位上,能够运用rememberScorllState:
// Smoothly scroll 100px on first composition
val state = rememberScrollState()
LaunchedEffect(Unit) { state.animateScrollTo(100) }
-
scrollable
修饰符与翻滚修饰符(verticalScroll、horizontalScroll)不相同,scrollable修饰符只会检测翻滚手势,而不会偏移其内容。
@Composable
fun ScrollSample() {
var offset by remember { mutableStateOf(0f) }
//指定ScrollableState 每次滚调时会回调,增量delta以px像素为单位
val scrollState = rememberScrollableState { delta ->
offset += delta
delta
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(150.dp)
.scrollable(orientation = Orientation.Vertical, state = scrollState)
.background(Color.LightGray),
) {
Text(text = offset.toString())
}
}
当竖直滑动时,Text 中一直会展现 offset 偏移值。
drag 拖动
draggable 修饰符是向单一方向(横向or纵向)拖动手势,draggable 与 scrollable相似,仅仅检测手势,假如还需求移动元素,考虑添加offset修饰符。
//1、操控横向or纵向drag拖拽
//注:draggable 与 scrollable相似,仅仅检测手势,假如还需求移动元素,考虑添加offset修饰符
var offsetX by remember { mutableStateOf(0f) }
val draggableState = rememberDraggableState(
onDelta = { delta -> offsetX += delta }
)
Text(text = "Drag me!", modifier = Modifier
.fillMaxWidth()
.background(Color.Gray)
.offset { IntOffset(offsetX.roundToInt(), 0) }
.draggable(
orientation = Orientation.Horizontal, state = draggableState
))
由于添加了Modifier.offset 修饰符,此刻拖动Text ,Text 文字会在内部横向进行滑动。
detectDragGestures
假如需求操控整个拖动手势,能够经过 Modifier.pointerInput来检测:
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(50.dp)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX += dragAmount.x
offsetY += dragAmount.y
}
}
)
}
//detectDragGestures
suspend fun PointerInputScope.detectDragGestures(
onDragStart: (Offset) -> Unit = { },
onDragEnd: () -> Unit = { },
onDragCancel: () -> Unit = { },
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
) { ... }
履行结果:
detectDragGestures扩展函数内部经过onDrag(PointerInputChange, Offset)完成拖拽回调,得到x、y轴的差值,从而经过offset 移动元素。
swipe 滑动
swipe 与 drag 很相似,不同的是,swipe支持设置锚点和阈值。当滑动到阈值时,控件能够自行滑动到目标终点。swipeable 修饰符中的参数如下:
fun <T> Modifier.swipeable(
state: SwipeableState<T>,
anchors: Map<Float, T>,
orientation: Orientation,
enabled: Boolean = true,
reverseDirection: Boolean = false,
interactionSource: MutableInteractionSource? = null,
thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
velocityThreshold: Dp = VelocityThreshold
){ ... }
-
state: SwipeableState<T>
:用于跟踪滑动的状况。SwipeableState 包含有关当时滑动方位、速度等信息的状况。 -
anchors: Map<Float, T>
:界说了滑动的锚点。Map 的键是锚点的方位,key表明偏移量(单位是Px),value是状况(T 类型)。 -
orientation: Orientation
:指定滑动的方向,能够是 Orientation.Horizontal 或 Orientation.Vertical。
上面三个参数是必需要设置的。
-
thresholds: (from: T, to: T) -> ThresholdConfig
:用于界说滑动的阈值装备。默许情况下,运用固定的阈值(FixedThreshold(56.dp))。能够依据 from 和 to 的状况值来自界说阈值装备。
ThresholdConfig阈值有两个详细完成: 1、FixedThreshold(private val offset: Dp)来设置详细偏移值,thresholds默许便是 { _, _ -> FixedThreshold(56.dp) }; 2、FractionalThreshold(fraction: Float)来设置偏移比例, 其间fraction的取值范围是[0.0, 1.0]。 当滑动超越阈值时,松手,滑块也会主动吸附到目标状况。如下设置中,默许是CLOSE状况,当滑动超越20%时会主动滑动到OPEN状况;反之,当时是OPEN状况,需求滑动超越30%时才会主动滑动到CLOSE状况。
-
enabled: Boolean
:指定是否启用滑动手势。默许为 true。 -
reverseDirection: Boolean
:指定是否答应反向滑动。默许为 false,即只能在指定方向上滑动。 -
interactionSource: MutableInteractionSource?
:用于指定供给交互事情的 MutableInteractionSource 实例。 -
resistance: ResistanceConfig?
:用于界说滑动的阻力装备。默许情况下,运用锚点的方位来设置阻力。 -
velocityThreshold: Dp
:用于指定触发滑动的速度阈值。默许为 VelocityThreshold,是一个常量,表明触发滑动的默许速度阈值。
上面介绍了各个参数的含义,其间state、anchors锚点、orientation方向、thresholds阈值是一般要设置的,来看运用示例:
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SwipeableSample() {
val width = 144.dp
val squareSize = 48.dp
val swipeableState = rememberSwipeableState(Switch.CLOSE)
//LocalDensity.current获取当时组合中的像素密度,从而进行dp->px的转化 ,px=dp*density
val sizePx = with(LocalDensity.current) { squareSize.toPx() }
//每个状况都对应一个锚点,锚点以键值对进行表明:key表明偏移量(单位是Px),value是状况
//如下设置:偏移量为0f时表明的是CLOSE状况,而偏移96dp时表明的是OPEN状况
val anchors =
mapOf(0f to Switch.CLOSE, sizePx * 2 to Switch.OPEN) // Maps anchor points (in px) to states
Box(
modifier = Modifier
.width(width)
.swipeable(
state = swipeableState,
anchors = anchors,
thresholds = { from, to ->
//from、to都表明的是anchors中设置的状况,这儿表明的是CLOSE/OPEN状况。
if (from == Switch.CLOSE) {
FractionalThreshold(0.2f)
} else {
FractionalThreshold(0.3f)
}
},
orientation = Orientation.Horizontal
)
.background(Color.LightGray)
) {
Box(
Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
.size(squareSize)
.background(Color.Red)
)
}
}
注:在Compose中,LocalDensity.current
是一个 CompositionLocal
对象,用于获取当时组合(Composition)中的屏幕像素密度(density)。屏幕像素密度一般以”DPI”(每英寸点数)为单位。
LocalDensity.current
的首要作用是供给当时组合中的屏幕像素密度,以便在编写UI时进行适当的尺度和布局调整,以习惯不同的屏幕密度。这能够保证应用在不同设备上有共同的外观和布局。以下两种写法都能够将dp转化为px:
val squareSize = 48.dp
//方法一:直接调用Density.toPx()办法
val sizePx = with(LocalDensity.current) { squareSize.toPx() }
//方法二:获取当时屏幕像素密度,然后自行计算
val density = LocalDensity.current.density
val sizePx = squareSize * density
多点触控
transformer
修饰符用于检测平移、缩放和旋转等多触控手势,transformer
本身不会旋转元素,只会检测手势。
@Composable
fun rememberTransformableState(
onTransformation: (zoomChange: Float, panChange: Offset, rotationChange: Float) -> Unit
): TransformableState { ... }
}
rememberTransformableState() 能够获取 TransformableState 实例,经过lambda回调 获取到双指拖动、缩放、旋转等手势信息,从而进行相应的操作即可。
运用示例:
@Composable
fun TransformableSample() {
var scale by remember { mutableStateOf(1f) }
var rotationAngle by remember { mutableStateOf(0f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val state = rememberTransformableState(onTransformation = { zoomChange, offsetChange, rotationChange ->
scale *= zoomChange
offset += offsetChange
rotationAngle += rotationChange
})
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Box(
modifier = Modifier
.size(150.dp)
.rotate(rotationAngle)
.offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
.scale(scale)
.transformable(state = state)
.background(Color.Blue)
)
}
}
detectTransformGestures
上述的 transformable() 能够运用detectTransformGestures 进行替换,运用如下:
Modifier.pointerInput(Unit) {
detectTransformGestures(
//假如panZoomLock为true,则只要在平移或缩放运动之前检测到旋转的触摸歪斜时才答应旋转。不然,将检测平移和缩放手势,但不会检测旋转手势。
//假如panZoomLock为false,一切三种手势都被检测,默许是false。
panZoomLock = false,
onGesture = { centroid: Offset, pan: Offset, zoom: Float, rotation: Float ->
offset += pan
scale *= zoom
rotationAngle += rotation
})
}
总结
下面几个修饰符只担任检测手势,假如还需求移动元素,考虑添加offset
修饰符:
-
scrollable
修饰符只检测翻滚
手势,不会偏移其内容。 -
draggable
修饰符是向单一方向(横向or纵向)拖动
手势,仅仅检测手势。 -
swipeable
修饰符是像一个方向滑动
,此修饰符不会移动元素,而只检测手势。
资料
【1】Compose 点击、滑动、拖动、多点触控等:https://developer.android.com/jetpack/compose/touch-input/gestures?hl=zh-cn