深度解读Flutter手势系统

2023-05-24  本文已影响0人  第八区

前言

对于做为客户端开发,永远绕不开的两座大山:手势系统渲染系统。Flutter做为当前比较火的跨平台开发框架,学习它的手势系统也是很有必要的。当然网上也有一些讲解,你可能会看到手势竞争,竞争胜出的会消费事件,但却很少能把如何竞争,以及为什么它能胜出或者失败能够讲清楚,当自己要处理手势问题时还是无从下手,不知道重点在哪里。文章涉及的源码很多,会拿一些widget来举例。内容可能会有一点枯燥。

适合哪些人看?

先做一道题开开胃

下面的代码运行后会屏幕中会出现一个红色方块,蓝色方块上覆盖着一个红色方块,请问分别进行以下操作,控制台的打印会是什么?

  1. 点击蓝色方块时

  2. 长按蓝色方块时


import 'package:flutter/material.dart';

void main() {

  runApp(const MyApp());

}

class MyApp extends StatelessWidget {

  const MyApp({super.key});

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      theme: ThemeData(

        primarySwatch: Colors.blue,

      ),

      home: const MyHomePage(title: '手势示例'),

    );

  }

}

class MyHomePage extends StatefulWidget {

  const MyHomePage({super.key, required this.title});

  final String title;

  @override

  State<MyHomePage> createState() => _MyHomePageState();

}

class _MyHomePageState extends State<MyHomePage> {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text(widget.title),

      ),

      body: Container(

        alignment: Alignment.center,

        child: GestureDetector(

          onTapDown: (TapDownDetails details) {

            print("red onTapDown");

          },

          onTap: () {

            print("red onTap");

          },

          onLongPressDown: (LongPressDownDetails details){

            print("red onLongPressDown");

          },

          child: Container(

            color: Colors.red,

            height: 300,

            width: 300,

            alignment: Alignment.center,

            child: GestureDetector(

              onTapDown: (TapDownDetails details) {

                print("blue onTapDown");

              },

              onTap: () {

                print("blue onTap");

              },

              onLongPressDown: (LongPressDownDetails details){

                print("blue onLongPressDown");

              },

              child: Container(

                color: Colors.blue,

                height: 150,

                width: 150,

              ),

            ),

          ),

        ),

      ),

    );

  }

}

如果你发现打印的出乎你的意料,那你是否有兴趣进入Flutter的手势分发的世界!

手势分发

下面介绍Flutter手势分发的流程

示例一 onTapDown之无竞争手势

我们从一个简单的示例来入手,一个居中大小为300的红色方块


class _MyHomePageState extends State<MyHomePage> {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text(widget.title),

      ),

      body: Container(

        alignment: Alignment.center,

        child: GestureDetector(

          onTapDown: (TapDownDetails details){

            print("red onTapDown");

          },

          child: Container(

            color: Colors.red,

            height: 300,

            width: 300,

          ),

        ),

      ),

    );

  }

}

我们打一个断点,点击蓝色的方块,看一下调用链:

[图片上传失败...(image-8b5b5b-1684980168825)]

[图片上传失败...(image-baa766-1684980168825)]

从调用链我们就能大概看到事件分发处理的流程

我们就从handlePointerEvent开始看,这个方法来自GestureBinding

如果看过flutter启动流程的同学应该知道,flutter定义了若干个Binding,如果处理手势的GestureBinding,渲染的RenderBinding,调度任务的SchedulerBinding。我们今天讲的手势系统,那理所应当就是在GestureBinding中


void handlePointerEvent(PointerEvent event) {

  //...

  _handlePointerEventImmediately(event);

}

我们继续进入到_handlePointerEventImmediately


void _handlePointerEventImmediately(PointerEvent event) {

  HitTestResult? hitTestResult;

  if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) {

    hitTestResult = HitTestResult();

    hitTest(hitTestResult, event.position);//重点

    if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {

      _hitTests[event.pointer] = hitTestResult;

    }

  } else if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) {

    hitTestResult = _hitTests.remove(event.pointer);

  } else if (event.down || event is PointerPanZoomUpdateEvent) {

    hitTestResult = _hitTests[event.pointer];

  }

  if (hitTestResult != null ||

      event is PointerAddedEvent ||

      event is PointerRemovedEvent) {

    dispatchEvent(event, hitTestResult); //重点

  }

}

手势事件的处理就在这个方法里了。我们先从第一个事件PointerDownEvent开始,整体就是2个步骤

下面先看hitTest

hitTest

GestureBinding的hitTest

我们先看它在GestureBinding中的定义是什么样的

[图片上传失败...(image-f0bd91-1684980168825)]

看到AS里的这两个箭头,应该能反应过来,它是一个覆写方法,同时还有子类覆写它。我们先看它继承自哪里,直接箭头点过去:

HitTestable


/// An object that can hit-test pointers.

abstract class HitTestable {

  // This class is intended to be used as an interface, and should not be

  // extended directly; this constructor prevents instantiation and extension.

  HitTestable._();

  /// Check whether the given position hits this object.

  ///

  /// If this given position hits this object, consider adding a [HitTestEntry]

  /// to the given hit test result.

  void hitTest(HitTestResult result, Offset position);

}

其实我觉得flutter的注释写的非常清楚了:一个可以测试pointers是否命中的对象

一个方法hitTest,用来检查给定位置是否命中该对象。如果这个给定的位置命中了这个对象,考虑添加一个[HitTestEntry],返回给定的HitTestResult。

RenderBinding的hitTest

前面我们看了继承类的hitTest,还是箭头直接点过去,我们发现实现是在RenderBinding中

如果有同学好奇为什么覆写跑到了RenderBinding中,可以了解下dart的mixin机制,WidgetsFlutterBinding的定义如下


class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

}```


```dart

@override

void hitTest(HitTestResult result, Offset position) {

 renderView.hitTest(result, position: position);

 super.hitTest(result, position);

}

_handlePointerEventImmediately中hitTest的关键流程

通过前面的hitTest的继承实现分析,我们的结论是在_handlePointerEventImmediately执行的hitTest逻辑是

更多请查看 深度解读Flutter手势系统

上一篇 下一篇

猜你喜欢

热点阅读