单击、双击、长按事情

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 翻滚

  • verticalScrollhorizontalScroll 修饰符让用户在元素内容鸿沟大于最大尺度束缚时翻滚元素,跟View系统里的 ScrollViewNestedScrollView 是相同的作用。这儿需求留意一点,关于长列表场景,咱们能够运用 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))
        }
    }
}

履行作用:

Jetpack Compose | Modifier手势相关修饰符的运用(二)

假如想在初次重组时滑动到某个方位上,能够运用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
) { ... }

履行结果:

Jetpack Compose | Modifier手势相关修饰符的运用(二)

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