在安卓中使用无障碍服务 AccessibilityService,如何实现上下滚动和左右滚动,如何控制滚动时间和滚动距离,相信很多小伙伴并不知道如何操作,本文就来介绍一种通过系统 API 组合提供的完美解决方案。

前言

如何使用 Android 系统的无障碍服务 AccessibilityService 实现控制控件滚动,我们都知道系统本身提供了滚动相关 API,及线上滚动和向下滚动:

//向上滚动
node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD)

//向下滚动
node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)

但提供的这两个 API,没有设置滚动距离的参数(通常是滚动列表一页),如果想获取其上下滚动距离,我们可以通过 onAccessibilityEvent 回调事件里的 event.getScrollX() and event.getScrollY() 来间接获取滚动距离和方向;此外,API 里并没有提供左右滚动的相关接口。

因此我们需要找到一种替代方案解决不能控制滚动距离和左右滚动的问题。

正文

Android 7.0 开始官方引入了 Gesturedescription 模拟手势,使用模拟手势 API,我们可以对屏幕任意位置进行模拟点击、长按和滑动等操作。

首先是dispatchGesture方法的解释

boolean dispatchGesture (GestureDescription gesture, AccessibilityService.GestureResultCallback callback, Handler handler)

这个就是我们要实现模拟手势需要调用的方法。这个方法有三个参数:
GestureDescription:关于手势的描述,如果要实现模拟,首先要描述你模拟的手势。
GestureResultCallback:关于手势的回调,手势模拟执行以后回调结果到预设的回调函数。
handler:大部分情况我们不用的话传空就可以了。


这个方法同样是有三个参数:
path:要遵循的路径,如设置坐标点,从某个点移动到另一个坐标点,这里滚动自然是需要改变点水平或竖直的值,来模拟手势操作让控件水平或竖直方向上滚动起来。
startTime:从手势开始到笔划应该开始的时间,要求非负数,一般为0。
duration:笔划路径持续的时间,要求正数,这里300毫秒做一次滑动比较符合正常操作水平。

注:需要设备系统 >= Android 7.0

工具类核心代码如下(以下为 Kotlin 编写,如果使用 Java 只要稍作修改即可)

import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityService.GestureResultCallback
import android.accessibilityservice.GestureDescription
import android.accessibilityservice.GestureDescription.StrokeDescription
import android.annotation.TargetApi
import android.app.Notification
import android.app.PendingIntent
import android.graphics.Path
import android.graphics.Point
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.blankj.utilcode.util.LogUtils
import java.lang.Exception
import java.lang.Thread.sleep

/**
 * 无障碍工具类
 */
object AccessibilityUtil {

/**
 * Gesture手势实现滚动(Android7+)
 * 解决滚动距离不可控制问题
 * @param distanceX 向右滚动为负值 向左滚动为正值
 * @param distanceY 向下滚动为负值 向上滚动为正值
 */
fun scrollByNode(
    service: AccessibilityService,
    nodeInfo: AccessibilityNodeInfo,
    distanceX: Int = 0,
    distanceY: Int = 0
): Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        val rect = Rect()
        nodeInfo.getBoundsInScreen(rect)
        val point = Point((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2)
        val builder = GestureDescription.Builder()
        val path = Path()
        path.moveTo(point.x.toFloat(), point.y.toFloat())
        path.lineTo(point.x.toFloat() + distanceX, point.y.toFloat() + distanceY)
        builder.addStroke(StrokeDescription(path, 0L, 300L))
        val gesture = builder.build()
        return service.dispatchGesture(gesture, object : GestureResultCallback() {
            override fun onCompleted(gestureDescription: GestureDescription) {
                LogUtils.e("scroll ok onCompleted")
            }

            override fun onCancelled(gestureDescription: GestureDescription) {
                LogUtils.e("scroll ok onCancelled")
            }
        }, null)
    } else
        return false
}

}

// 向下滚动一小段距离示例
// AccessibilityUtil.scrollByNode(#无障碍servicer, #需要滚动的控件内的任一节点node, 0, -300)
// 第二个参数只需要传任一滚动控件内的node用来辅助定位即可,-300即代表向上滑300像素(y-300),也等于向下滚动300像素

总结

本案例通过 Android 系统提供的 Gesturedescription API 功能完美实现任意指定距离的滚动,如果有帮助请点赞收藏~。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐