我是靠谱客的博主 活泼大碗,最近开发中收集的这篇文章主要介绍解析Flutter中的手势控制Gestures,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

  Flutter提供了很多处理触摸事件的控件,例如InkWellInkResponse可以处理点击、双击、长按等事件,将它们包裹在需要响应触摸事件的控件外部就可以了,而且InkWellInkResponse还会添加一个水波纹的点击效果,InkResponse还可以设置水波纹的形状。但是,InkWellInkResponse都不会做任何的渲染工作,它们只是更新了父级Material Widget。一个简单的例子就是image,如果你将一个image用InkWell包裹住,那么你会发现,水波纹效果不见了。这是因为水波纹是被绘制在image的下层的,所以被遮挡住了。如果想要水波纹可见,那么请使用Ink.Image
  但是如果你想要捕捉更多的触摸事件,比如用户的拖拽行为,那么你就必须使用GestureDetector来实现了。

什么是GestureDetector

  最基本的解释就是:一个处理各种 touch event 的 stateless widget。GestureDetector是一个纯粹的用来处理手势的控件,没有任何UI上的表现(不像Ink那样会有水波纹的触摸反馈)。
  下面的表格是GestureDetector提供的各种 callbacks 和对这些回调的简单解释:

Property/CallbackDescription
onTapDown用户每次和屏幕交互时都会被调用
onTapUp用户停止触摸屏幕时触发
onTap短暂触摸屏幕时触发
onTapCancel用户触摸了屏幕,但是没有完成Tap的动作时触发
onDoubleTap用户在短时间内触摸了屏幕两次
onLongPress用户触摸屏幕时间超过500ms时触发
onVerticalDragDown当一个触摸点开始跟屏幕交互,同时在垂直方向上移动时触发
onVerticalDragStart当触摸点开始在垂直方向上移动时触发
onVerticalDragUpdate屏幕上的触摸点位置每次改变时,都会触发这个回调
onVerticalDragEnd当用户停止移动,这个拖拽操作就被认为是完成了,就会触发这个回调
onVerticalDragCancel用户突然停止拖拽时触发
onHorizontalDragDown当一个触摸点开始跟屏幕交互,同时在水平方向上移动时触发
onHorizontalDragStart当触摸点开始在水平方向上移动时触发
onHorizontalDragUpdate屏幕上的触摸点位置每次改变时,都会触发这个回调
onHorizontalDragEnd水平拖拽结束时触发
onHorizontalDragCancelonHorizontalDragDown没有成功完成时触发
onPanDown当触摸点开始跟屏幕交互时触发
onPanStart当触摸点开始移动时触发
onPanUpdate屏幕上的触摸点位置每次改变时,都会触发这个回调
onPanEndpan操作完成时触发
onScaleStart触摸点开始跟屏幕交互时触发,同时会建立一个焦点为1.0
onScaleUpdate跟屏幕交互时触发,同时会标示一个新的焦点
onScaleEnd触摸点不再跟屏幕有任何交互,同时也表示这个scale手势完成

  GestureDetector并不会监听上面所有的手势,只有传入的callbacks非空时,才会监听。所以,如果你想要禁用某个手势时,可以给对应的callback传null。

我们以onTap为例,看一下GestureDetector时如何工作的

  我们首先创建一个带有onTap回调的GestureDetector,每次tap事件发生的时候,都会触发我们这个回调。而在GestureDetector内部,会创建一个Gesture FactoryGesture Recognizer则决定了需要处理哪一个手势。当有多个回调时,也是同样的处理过程。然后,GestureFactories会被传给RawGestureDetector
  RawGestureDetector负责检测手势。它是一个stateful widget,当它的状态发生改变时,会同步所有的手势,处理recognizers,然后将所有发生的pointer events传给注册了的recognizers,之后它会和Gesture Arena来一场battle,决定将这个事件交给谁。
  RawGestureDetector通过Listener类创建了一个基础的监听pointer events的listener。如果你想要使用来自platform的未处理过的 input,比如 up、down、cancel 事件,你需要使用的就是这个Listener类。但是,Listener类并不会给你提供具体的手势信息,它只会提供四个基本的事件:onPointerDown、 onPointerUponPointerMoveonPointerCancel。每一件事都需要手动进行,包括将你自己报告给Gesture Arena,如果不这么做,之后没法自动收到cancel消息,也没法收到接下来的任何交互信息。
  Listener是一个由RenderPointerListener组成的SingleChildRenderObjectWidget,用来汇报未处理的pointer events,RenderPointerListener继承自RenderProxyBoxWithHitTestBehavior,当定制一个HitTestBehavior时,它会模拟所有子类的属性。如果你想要更多地了解Render Boxes,可以看下这篇文章:Flutter, what are Widgets, RenderObjects and Elements?
  HitTestBehavior有三个属性:deferToChildopaquetranslucent,这三个属性来源于GestureDetector中的配置。deferToChild会将事件在widget tree中向下传递;opaque防止background widgets收到事件;translucent则允许background widgets收到事件。

如果想要父控件和子控件都能收到手势事件呢?

  想象一个场景:你有一个嵌套的list,想要它们能够同时滑动。这时,你就需要父控件和子控件都能收到pointer events了。你可能觉得,我设置一下translucent不就行了嘛,这样两个控件都能收到事件,但是,事实并没有想象的这么美好······那么,为什么行不通呢?
  这时候就需要用到之前提到的GestureArena了。GestureArena是在 gesture disambiguation 中被用到的,所有的recognizer都会被传送到这里,然后来一场大乱斗。屏幕上的每一个点,都可能形成多个gesture recognizers。Arena通过分析用户触摸屏幕的时长和用户拖拽的角度,决定胜出者是谁。
  父控件和子控件都会有自己的recognizers被传递到Arena这里,只有一个recognizers会取胜,而且大部分情况下胜者都是子控件。

(注:Flutter源码注释就是用的win和lose这两个词,确实不太好翻译,暂且称为获胜和失败,一个gesture的结果只有两种,要么wins the arena,要么loses the arena,所以一定要理解win、lose和arena这三个词的意思。)

  解决方法就是使用你自定义的GestureFactoryRawGestureDetector,强行改变Arena的行为。
  我们用一个简单的例子来解释下,下面的这个App里面包括了两个containers,我们的目的是父控件和子控件都能收到手势。
  两个控件都会被包裹在RawGestureDetector里面,接下来我们自定义一个gesture recognizer AllowMultipleGestureRecognizerGestureRecognizer是所有recognizers的基类,自定义的recognizers都应该继承自它,它提供了最基本的和gesture recognizers交互的API,GestureRecognizer并不关心子recognizers各自的具体特性。

// 自定义 Gesture Recognizer.
// 重写rejectGesture(). 当一个手势被拒绝时,会调用这个函数。
// 默认情况下,它会处理Recognizer,并在处理完毕后销毁。
// 我们重写这个方法,自己处理Recognizer
// 处理结果就是,我们有两个Recognizer都赢了Arena,而不是一个,因为我们手动接受了两个recognizers
class AllowMultipleGestureRecognizer extends TapGestureRecognizer {
  @override
  void rejectGesture(int pointer) {
    acceptGesture(pointer);
  }
}

  acceptGesture()是在给定的pointer id获胜(win)时的回调;rejectGesture()是在给定的pointer id失败(lose)时的回调。所以我们在自定义的recognizer中,强行在rejectGesture()中做了accept操作。
  然后,我们在控件中,将我们的自定义gesture-recognizer,通过GestureRecognizerFactoryWithHandlers传递给RawGestureDetector

Widget build(BuildContext context) {
   return RawGestureDetector(
     gestures: {
       AllowMultipleGestureRecognizer: GestureRecognizerFactoryWithHandlers<
          AllowMultipleGestureRecognizer>(
         () => AllowMultipleGestureRecognizer(), //constructor
         (AllowMultipleGestureRecognizer instance) { //initializer
           instance.onTap = () => print('Episode 4 is best! (parent container) ');
         },
       )
     },

   factory的构造器需要两个属性、一个构造器和一个用来初始化gesture recognizer的初始化器,我们通过lambda表达式来传参。这个构造器返回了一个新的AllowMultipleGestureRecognizer事例,初始化器持有的instance属性则是用来监听tap操作,在console中做一些print操作。
  完整的样例代码如下:

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

//Main function. The entry point for your Flutter app.
void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: DemoApp(),
      ),
    ),
  );
}

class DemoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RawGestureDetector(
      gestures: {
        AllowMultipleGestureRecognizer: GestureRecognizerFactoryWithHandlers<
            AllowMultipleGestureRecognizer>(
          () => AllowMultipleGestureRecognizer(),
          (AllowMultipleGestureRecognizer instance) {
            instance.onTap = () => print('Episode 4 is best! (parent container) ');
          },
        )
      },
      behavior: HitTestBehavior.opaque,
      //Parent Container
      child: Container(
        color: Colors.blueAccent,
        child: Center(
          //Wraps the second container in RawGestureDetector
          child: RawGestureDetector(
            gestures: {
              AllowMultipleGestureRecognizer:
                  GestureRecognizerFactoryWithHandlers<
                      AllowMultipleGestureRecognizer>(
                () => AllowMultipleGestureRecognizer(),  //constructor
                (AllowMultipleGestureRecognizer instance) {  //initializer
                  instance.onTap = () => print('Episode 8 is best! (nested container)');
                },
              )
            },
            //Creates the nested container within the first.
            child: Container(
               color: Colors.yellowAccent,
               width: 300.0,
               height: 400.0,
            ),
          ),
        ),
      ),
    );
  }
}

class AllowMultipleGestureRecognizer extends TapGestureRecognizer {
  @override
  void rejectGesture(int pointer) {
    acceptGesture(pointer);
  }
}

输出结果

当某个recognizer获胜(win the arena)会发生什么呢?

  当某个recognizer获胜后, Arena会closed and swept,处理掉没有用的recognizers,并且重置Arena。而这个最终的gesture就会执行最终的action。
  回到最初的Tap例子中,最终结果就是调用onTap方法。一旦一个gesture获胜了,就会立即触发onTapUp,而没有获胜的gesture则会触发onTapCancel

最后

以上就是活泼大碗为你收集整理的解析Flutter中的手势控制Gestures的全部内容,希望文章能够帮你解决解析Flutter中的手势控制Gestures所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部