【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,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~

相关推荐
inmK11 小时前
蓝奏云官方版不好用?蓝云最后一版实测:轻量化 + 不限速(避更新坑) 蓝云、蓝奏云第三方安卓版、蓝云最后一版、蓝奏云无广告管理工具、安卓网盘轻量化 APP
android·工具·网盘工具
giaoho1 小时前
Android 热点开发的相关api总结
android
咖啡の猫3 小时前
Android开发-常用布局
android·gitee
程序员老刘4 小时前
Google突然“变脸“,2026年要给全球开发者上“紧箍咒“?
android·flutter·客户端
Tans54 小时前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
雨白4 小时前
实现双向滑动的 ScalableImageView(下)
android
峥嵘life4 小时前
Android Studio新版本编译release版本apk实现
android·ide·android studio
studyForMokey7 小时前
【Android 消息机制】Handler
android
敲代码的鱼哇7 小时前
跳转原生系统设置插件 支持安卓/iOS/鸿蒙UTS组件
android·ios·harmonyos
翻滚丷大头鱼7 小时前
android View详解—动画
android