【Jetpack Compose】手势之点击、拖动和滑动事件

Compose在手势处理方面,为开发者提供了众多的API,可以说比原生View的手势处理更为方便,考虑到手势内容种类比较多,分两节内容详细介绍,本篇文章主要介绍点击、双击、长按、拖动和滑动事件,较为复杂的滚动、和多点触控放在下一篇文章介绍。

在上一篇【Jetpack Compose】仿微信查看大图渐入渐出效果中也是使用了比较常用的几种手势,看完本篇可以更加轻松的去理解上一篇的处理逻辑。

单击、双击和长按事件

在学习手势的开始,先从最简单的单击事件入手,看看Compose是如何为开发者提供单击事件的入口。

kotlin 复制代码
@Composable
fun ClickGestureScreen() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize()
    ) {
        Text(text = "点击事件", modifier = Modifier.clickable {
            Log.d(TAG, "ClickGestureScreen: click")
        })
    }
}

fun Modifier.clickable(
    enabled: Boolean = true,
    onClickLabel: String? = null,
    role: Role? = null,
    onClick: () -> Unit
)

单击事件可以由Modifier.clickable()方法提供,此方法有四个参数,其中只有onClick参数为必传,其余三个参数都是可以默认不传入,参数的介绍分别如下:

  • enabled:表示点击事件是否可用,默认为true,可使用此参数控制点击事件是否响应;
  • onClickLabel:此参数主要用于无障碍模式下播报的标签,一般情况下不会使用,如果应用需要适配无障碍模式,那么此参数就需要设置对应的语义;
  • role:此参数也是用于无障碍模式,它充当了一种角色,Compose默认提供了一系列角色比如:Button、CheckBox等;
  • onClick:此参数就是点击事件响应的回调,可在此Lambda内部进行点击事件的逻辑处理。

Compose的点击事件中,默认会添加水波纹效果,如果不需要此效果,可以将indication设置为null

kotlin 复制代码
@Composable
fun ClickGestureScreen() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize()
    ) {
        Text(text = "去除点击事件的水波纹", modifier = Modifier.clickable(
            interactionSource = remember {
                MutableInteractionSource()
            },
            indication = null
        ) {
            Log.d(TAG, "ClickGestureScreen: click")
        })
    }
}

点击事件为手势中最为简单的事件之一了,下面我们继续看下长按和双击事件是如何处理的。

less 复制代码
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DoubleClickGestureScreen() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize()
    ) {
        Text(text = "双击和长按事件", modifier = Modifier.combinedClickable(
            onLongClick = { Log.d(TAG, "DoubleClickGestureScreen: onLongClick") },
            onDoubleClick = { Log.d(TAG, "DoubleClickGestureScreen: onDoubleClick") },
            onClick = { Log.d(TAG, "DoubleClickGestureScreen: onClick") }
        ))
    }
}

长按和双击事件只是将Modifier.clickable()方法替换成combinedClickable()即可,combinedClickable方法内部有三个Lambda参数,只需要在对应的参数中处理相应的逻辑即可,还是非常便捷的。

combinedClickable()方法也是同样可以去除水波纹效果的,用法可单击事件一样~

拖动事件

在Compose中,拖动事件可以通过Modifier.draggable()方法提供,可以监听水平或者垂直方向上拖动事件,下面来看看具体是如何监听拖动事件的。

kotlin 复制代码
@Composable
fun DraggableGestureScreen() {

    var dragDistance by remember {
        mutableStateOf(0F)
    }
    val draggableState = rememberDraggableState(onDelta = {
        dragDistance += it
        Log.d(TAG, "DraggableGestureScreen onDelta: $it, dragDistance: $dragDistance")
    })
    Box(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize()
    ) {
        Text(
            text = "拖动", modifier = Modifier
                .draggable(
                    draggableState, Orientation.Vertical,
                    onDragStarted = { offset ->
                        Log.d(TAG, "DraggableGestureScreen start offset: $offset")
                    },
                    onDragStopped = { offset ->
                        Log.d(TAG, "DraggableGestureScreen end dragDistance: $offset")
                    }
                )
                .offset(y = dragDistance.dp)
        )
    }
}

fun Modifier.draggable(
    state: DraggableState,
    orientation: Orientation,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource? = null,
    startDragImmediately: Boolean = false,
    onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {},
    onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit = {},
    reverseDirection: Boolean = false
)

draggable()方法参数比较多,这里列举几个比较重要的参数做下解释:

  • state:用于监听指定方向上手指拖动的偏移量;
  • orientation:指定水平或者垂直方向;
  • onDragStarted:拖动开始的回调,是一个挂起函数,内部的startedPosition表示手指落下的位置;
  • onDragStopped:拖动停止的回调,也是一个挂起函数,内部的velocity表示停止时拖动的速度;
  • reverseDirection:表示是否反转拖动反向,默认为false。

示例代码中监听垂直方向的拖动事件,并且将文本组件随着拖动做偏移动作,这里采用的是设置Modifier.offset(y = dragDistance)来偏移文本组件在y轴的坐标,下面是滑动的具体效果:

通过Modifier.draggable()方法只能监听单一方向上的拖动事件,除此之外还可以Modifier.pointInput()来同时监听水平和垂直方向上的拖动事件,这个后续会单独介绍,感兴趣的小伙伴可以提前去了解下🙂。

滑动事件

Compose中滑动事件可以通过Modifier.swipeable()方法进行监听,但是swipeable目前之后material包下才有,material3包下面还未提供。

滑动事件和拖动事件极为类似,都是在指定的方向上随着手指的移动而响应的事件,只是滑动事件比拖动事件多了个锚点的概念,它可以根据锚点的状态来设置阈值,事件会在对应的阈值范围内进行额外的滑动事件,比如说我们设置一个Box从上滑到下,并且阈值为0.3,当手指从上滑到下总长的70%时停止,那么剩下的30%距离也会进行滑动事件。

下面我们来看具体代码实现:

scss 复制代码
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SwipGestureScreen() {

    val swipeableState = rememberSwipeableState(initialValue = 0)
    val boxSize = with(LocalDensity.current) { 50.dp.toPx() }
    val anchorMap = mapOf(0F to 0, boxSize to 1)

    Box(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize()
    ) {
        Box(
            modifier = Modifier
                .size(50.dp, 100.dp)
                .background(color = Color.Blue)
        ) {
            Box(
                modifier = Modifier
                    .offset { IntOffset(0, swipeableState.offset.value.toInt()) }
                    .swipeable(
                        state = swipeableState,
                        anchors = anchorMap,
                        orientation = Orientation.Vertical,
                        thresholds = { from, to ->
                            Log.d("SwipGestureScreen", "SwipGestureScreen from: $from, to: $to")
                            if (from == 1) {
                                FractionalThreshold(0.2F)
                            } else {
                                FractionalThreshold(0.4F)
                            }
                        }
                    )
                    .size(50.dp)
                    .background(color = Color.Red)
            )
        }
    }
}

swipeableState是记录滑动事件的状态,可以从此state中获取滑动的偏移量;

anchorMap是一个锚点集合,当偏移量为0时,锚点值设置为0,当偏移量为Box大小时,此时锚点值设置为1,后续可以根据锚点值进行对应的阈值设置;

Modifier.offset { IntOffset(0, swipeableState.offset.value.toInt()) }设置最小的Box随之滑动进行Y轴偏移;

Modifier.swipeable()方法有四个比较重要的参数,分别作用如下:

  • state:用于监听滑动事件的状态;
  • anchors:配置滑动事件的锚点集合,也就是我们上面提到的anchorMap;
  • orientation:配置滑动事件的方向,此处设置为垂直方向;
  • thresholds:根据锚点值设置阈值,当从上到下滑动时,阈值设置为0.4F,当从下到上滑动时,阈值设置为0.2F。

下面来看下具体实现的效果:

从上面的录屏中可以清晰的看出,当手指从上到下滑动是,触发到0.4F阈值时,此时松开手指,红色的方块依旧会往下滑动;当从下往上滑动是只需要触发0.2F阈值时,手指松开方块也会继续往上滑动,这就是swipe比drag更美妙的地方。

我们可以通过swipe实现滑动关闭、折叠标题栏的各种炫酷的效果,小伙伴们赶紧去试试吧~

关于我

我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~

相关推荐
帅得不敢出门10 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡
我又来搬代码了11 小时前
【Android】使用productFlavors构建多个变体
android
德育处主任13 小时前
Mac和安卓手机互传文件(ADB)
android·macos
芦半山13 小时前
Android“引用们”的底层原理
android·java
迃-幵14 小时前
力扣:225 用队列实现栈
android·javascript·leetcode
大风起兮云飞扬丶14 小时前
Android——从相机/相册获取图片
android
Rverdoser14 小时前
Android Studio 多工程公用module引用
android·ide·android studio
aaajj14 小时前
[Android]从FLAG_SECURE禁止截屏看surface
android
@OuYang14 小时前
android10 蓝牙(二)配对源码解析
android
Liknana14 小时前
Android 网易游戏面经
android·面试