GestureDescription.StrokeDescription, отменить willContinue

Ситуация такая. На экране телефона отображается список. Программа должна листать его снизу вверх от нижней границы экрана до верхней. Но если использовать вот такую функцию

fun swipeScreen(swipePath: Path, startTime: Long = 0L, duration: Long = 1L): Boolean {
    val gesture: GestureDescription = GestureDescription.Builder().apply {
        addStroke(GestureDescription.StrokeDescription(swipePath, startTime, duration))
    }.build()
    return dispatchResult = dispatchGesture(gesture, null, null)
}

, то произойдет свайп, и список пролистается из начала сразу в конец. Поэтому в конце свайпа программа должна задержаться и "отпустить" жест через некоторое время, чтобы список пролистался примерно на 1 экран. Частично проблема решается, если указать 4 параметр GestureDescription.StrokeDescription (willContinue = true)

fun swipeScreen(swipePath: Path, startTime: Long = 0L, duration: Long = 1L): Boolean {
    val gesture: GestureDescription = GestureDescription.Builder().apply {
        addStroke(GestureDescription.StrokeDescription(swipePath, startTime, duration, true))
    }.build()
    return dispatchResult = dispatchGesture(gesture, null, null)
}

Но в описании к этому параметру написано следующее: "Continued strokes keep their pointers down when the gesture completes". Каким образом прекратить удерживание? Или, возможно, есть вообще другой вариант?

UPD. Я решил этот вопрос. Нашел как раз таки другой вариант.


Ответы (1 шт):

Автор решения: he1ex-tG

Скажу сразу. Вариант только лишь с использованием параметра willContinue мне не подошел, т.к. я так и не нашел, как программно "отпустить" нажатие. А мне это было нужно. Я пытался выполнить программный тап по экрану в точке окончания свайпа. Тогда этот новый жест (тап) прерывал предыдущий (свайп с задержкой от willContinue) и нажатие отпускалось. Но тап - это тап, который мог попасть, например, в гиперссылку в тексте, спровоцировав переход.

Поэтому я пошел следующим путем. Эту функцию

fun swipeScreen(swipePath: Path, startTime: Long = 0L, duration: Long = 1L): Boolean {
    val gesture: GestureDescription = GestureDescription.Builder().apply {
        addStroke(GestureDescription.StrokeDescription(swipePath, startTime, duration))
    }.build()
    return dispatchResult = dispatchGesture(gesture, null, null)
}

пришлось сильно изменить. Для начала я добавил входной параметр endDelay

fun swipeScreenWithDelay(
        swipePath: Path,
        startTime: Long = 0L,
        duration: Long = 1L,
        endDelay: Long = 500L
    ): Boolean

По умолчанию он равен 500 миллисекундам, но я использую около 300, когда вызываю функцию. Далее я разбил путь свайпа swipePath на 2 отрезка: первый = 99,5% от swipePath, второй = 0,5% от swipePath.

val pM = PathMeasure(swipePath, false)
val breakPoint: Float = pM.length * 0.995F
val p1 = Path()
pM.getSegment(0F, breakPoint, p1, true)
val p2 = Path()
pM.getSegment(breakPoint, pM.length, p2, true)

Создал 2 жеста для каждого из двух отрезков пути

val gesture1 = GestureDescription.StrokeDescription(p1, startTime, duration, true)
val gesture2 = gesture1.continueStroke(p2, 0, endDelay, false)

Тут сделаю небольшую ремарку:

  1. Первоначальный путь разбивается на 2 отрезка таким образом, чтобы конец первого и начало второго совпадали. Это важно. Если первый отрезок будет, допустим, первые 30%, а второй - последние 40%, то выполнится движение только по первому отрезку. В догонку: тем способом, которым я воспользовался, концы не совсем совпадают из-за типа Float, разница в десятитысячных, но есть. Однако ввиду того, что свайп все-таки выполняется на экране по пикселям с целочисленными координатами, то после округления, концы совпадают.
  2. Обратите внимание на то, что первый жест таки имеет параметр willContinue = true. Это нужно, чтобы второй жест был корректно создан через метод continueStroke первого жеста. То есть тут мы обозначаем, что второй жест - это именно продолжение первого жеста. И второй жест выполняется сразу без задержек после первого. Тут я пробовал не использовать continueStroke, а просто создать жест, подобно первому. Итог: не работает. Если нужна цепочка действий, то continueStroke в помощь.

Итак. Пришло время отправить жест на исполнение.

val dispatchResult = dispatchGesture(
    GestureDescription.Builder().addStroke(gesture1).build(),
    object : GestureResultCallback() {
        override fun onCompleted(gestureDescription: GestureDescription) {
            super.onCompleted(gestureDescription)
            dispatchGesture(                    
                GestureDescription.Builder().addStroke(gesture2).build(),
                null,
                null
            )
        }
        override fun onCancelled(gestureDescription: GestureDescription) {
            super.onCancelled(gestureDescription)
        }
    },
    null
)

На самом деле, по-моему, не очень прозрачно. Тут мы перегружаем callback для первого жеста и "говорим", чтобы второй жест выполнился, когда первый будет успешно завершен. А если будет 3 и более жестов, продолжающих друг-друга? В таком случае я бы не делал такую матрешку, а написал бы отдельный метод для вызова цепочки жестов. Возможно, что и так напишу, попозже. Тогда выложу тут.

Все целиком.

fun swipeScreenWithDelay(
    swipePath: Path,
    startTime: Long = 0L,
    duration: Long = 1L,
    endDelay: Long = 500L
): Boolean {
    /** Получить данные [swipePath] */
    val pM = PathMeasure(swipePath, false)

    /** Разбить [swipePath] на 2 отрезка: первый - 99,5% от [swipePath], второй - 0,5%. */
    val breakPoint: Float = pM.length * 0.995F
    val p1 = Path()
    pM.getSegment(0F, breakPoint, p1, true)
    val p2 = Path()
    pM.getSegment(breakPoint, pM.length, p2, true)

    /** Создать 2 жеста. Второй - продолжает первый через continueStroke. */
    val gesture1 = GestureDescription.StrokeDescription(p1, startTime, duration, true)
    val gesture2 = gesture1.continueStroke(p2, 0, endDelay, false)

    /**
     * Отправить жест на выполнение. Одновременно перегрузить callback таким образом, чтобы
     * второй жест выполнился сразу, как только первый сообщит об успешном завершении.
     */
    val dispatchResult = dispatchGesture(
        GestureDescription.Builder().addStroke(gesture1).build(),
        object : GestureResultCallback() {
            override fun onCompleted(gestureDescription: GestureDescription) {
                super.onCompleted(gestureDescription)
                dispatchGesture(
                    GestureDescription.Builder().addStroke(gesture2).build(),
                    null,
                    null
                )
            }
            override fun onCancelled(gestureDescription: GestureDescription) {
                super.onCancelled(gestureDescription)
            }
        },
        null
    )
    return dispatchResult
}

Итого. Когда выполняется свайп через приведенную функцию, в конце пути происходит резкое замедление, из-за которого экран не пролистывается. В принципе, это похоже на пролистывание экрана человеком.

→ Ссылка