移植一個抖音貼紙組件到Flutter

本文發(fā)于簡書——何時夕,搬運轉(zhuǎn)載請注明出處,否則將追究版權(quán)責任。交流qq群:859640274

大家好久不見,又有一個多月沒有發(fā)文章了,所以今天發(fā)一篇來刷刷存在感。最近 Flutter 非?;?,我這一個月也不斷的找資料來學習 Flutter。經(jīng)過一段時間的摸索,我發(fā)現(xiàn)現(xiàn)在很多資料都非?!彼?。各種 Dart 入門、Flutter 入門、Flutter 資料收集,完全沒有任何有趣的東西。我不想去寫重復而無聊的文章,所以本篇文章會拋轉(zhuǎn)引玉的探討一些在學習和開發(fā) Flutter 的過程中遇見的問題和解決方案。

閱讀須知:

  • 1.WE——>WsElement、ECWS——>ElementContainerWidgetState、EAL——>ElementActionListener、

本文分為以下章節(jié),讀者可按需閱讀:

  • 1.Flutter之問——以 QA 的形式來闡述我對 Flutter 的看法和學習經(jīng)驗。
  • 2.移植一個Flutter控件——將仿寫抖音的貼紙控件移植到 Flutter 中。
  • 3.Flutter探究——聊一聊 Flutter 的原理。
  • 4.尾巴

一、Flutter之問

天下事有難易乎?為之,則難者亦易已!

Q:Flutter 怎么學?

A:這是老生常談的問題了。隨便打開一個 Flutter 系列文章,都會為你鋪平接下來幾周的路。但是幾周之后呢?似乎很少文章會接著寫下去,畢竟大腦最喜歡簡單的東西(我也不例外),一件事情的難度與受歡迎程度成反比。所以 Flutter 怎么學?所謂:取乎其上,得乎其中。我只有一句話:以讓 Flutter 成為你最拿手技能為目標去學。

Q:能給一些 Flutter 的學習資料嗎?

A:我列舉一下我學習 Flutter 過程中用到的資料:

  • 1.Dart官網(wǎng),啃完官方文檔,Dart 你就入門了。

  • 2.Flutter實戰(zhàn),這本開源書的例子很多,全部敲一遍 flutter 你就入門了。特別是最后的 Flutter 原理分析可以仔細看看。

  • 3.Flutter github 倉庫,現(xiàn)在網(wǎng)絡(luò)上 Flutter 原理分析的文章真的非常少,所以真想要成為 Flutter 專家,你必須作為開拓者去閱讀 Flutter 在各種層級下的源碼。

Q:Flutter 會干掉 Native?

A:Flutter 是 Native 的子集。在手機被”革命“之前,但凡業(yè)務(wù)比較復雜的公司,只會要求 Native 工程師掌握 Flutter。而不會出現(xiàn)拋棄 Native 只做 Flutter 的工程師,因為 Flutter 說一千道一萬只是一個 ui 框架。畢竟它自身的復雜度很難支撐起比它還復雜的業(yè)務(wù)。以上只是個人觀點,有分歧可以在評論區(qū)探討

Q:Flutter 哪些地方做的比 Native 好?

A:下面是我總結(jié)出來的 Flutter 比 Native 好的地方:

  • 1.ios、android 一把抓,還可能帶上 web、mac、pc。
  • 2.Dart 語言非?,F(xiàn)代,比 java、oc 好上太多。
  • 3.新興框架沒有歷史包袱。
  • 4.熱更技術(shù)非常誘人。
  • 5.入門很簡單。

二、移植一個FluTter控件

經(jīng)常讀我的文章的讀者應(yīng)該看過我上一篇文章:抖音、ins、微信功能大比拼——Story的貼紙文字,這篇文章中詳細比較了各家 Story 的貼紙文字的功能,然后在 Android 端實現(xiàn)了一個貼紙框架。而這一章我就打算將這個貼紙框架移植到 Flutter,相信最后的還原度會超過你的想象。接下來建議配合源碼閱讀文章。注意這一章的大部分內(nèi)容和上一篇文章中講解 Android 端實現(xiàn)控件的章節(jié)是差不多的。

github 地址

使用方式:sticker_framework: ^0.0.1

1.架構(gòu)方式

我們第一節(jié)先講講文字貼紙控件的架構(gòu)實現(xiàn),我會基于下面的 圖1 和 github 上的代碼進行講解。建議大家把代碼 clone 下來,當然別忘了給個 star。

flutter文字貼紙架構(gòu).jpg

我們先來根據(jù)圖1來講講整個控件的架構(gòu)

  • 1.我們先從整體來看:
    • 1.我們需要選擇一個 StatefulWidget 作為基本的容器。所以圖中的 ElementContainerWidgetState 就是一個構(gòu)造這樣的容器的 State,簡單概括一下它有這些功能:
      • 1.處理各種手勢事件,這里的手勢包括單指和雙指。
      • 2.添加和刪除一些子 Widget。這里的子 Widget 用于繪制各種元素。
      • 3.提供一些 api 讓外部能操控元素。
      • 4.提供一個 listener,讓外部能夠監(jiān)聽內(nèi)部的各種流程。
    • 2.有了繪制容器,我們需要向繪制容器里面添加 Widget。而 Widget 在用戶操作的過程中需要有各種數(shù)據(jù),所以這里我用了 WE 來封裝需要展示的 Widget,其內(nèi)部有下面這些東西:
      • 1.各種用戶操作過程中需要的數(shù)據(jù)例如:scale、rotate、x、y等等。
      • 2.有一些方法能夠通過數(shù)據(jù)來更新 Widget。
      • 3.提供一些 api 讓 ECWS 能更新 WE 里面的數(shù)據(jù) 。
    • 3.由 ECWS 和 WE 就能繼續(xù)繼承出各種各樣的擴展控件。
  • 2.整體講完了,我們就可以來仔細的講講圖中的流程
    • 1.先講橫著的箭頭:外部/內(nèi)部調(diào)用,外部需要調(diào)用 ECWS 來進行對 WE 的增刪改查等操作時會進入這個路徑,這個路徑里可以有下面這些操作:
      • 1.addElement:向 ECWS 中添加一個元素。
      • 2.deleteElement:從 ECWS 中刪除一個元素。
      • 3.update:讓 WE 根據(jù)當前數(shù)構(gòu)建出一個 Widget。
      • 4.findElementByPosition:找到傳入的坐標下的最頂層的 WE。
      • 5.selectElement:選中一個 WE 且將其調(diào)到最頂層。
      • 6.unSelectElement:取消選中一個 WE。
    • 2.再來講豎著的箭頭:手勢事件流,這里中間會經(jīng)歷一些內(nèi)部邏輯我們后面來講,最終事件流會觸發(fā)下面的一系列行為:
      • 1.單指移動的整個流程:當我們選中了一個 WE 的時候就可以對它進行移動。這里移動可以分為開始、進行中、結(jié)束。每個事件都會調(diào)用 WE 的對應(yīng)方法以更新其內(nèi)部數(shù)據(jù)。
      • 2.雙指旋轉(zhuǎn)縮放的整個流程:當我們選中了一個 WE 的時候可以用雙指對它進行縮放和旋轉(zhuǎn)。這里可以分為開始、進行中、結(jié)束。這里也會調(diào)用 WE 的對應(yīng)方法更新數(shù)據(jù)。
      • 3.選中元素再次點擊:當我們選中了一個 WE 的時候,可以對其再次點擊。
      • 4.點擊空白區(qū)域:當我們沒有點擊任意 WE 的時候可以進行一些操作,例如清除當前 WE 的選中狀態(tài)。這個行為是可以繼承的,可以交由子類來覆寫。
      • 5.子類事件:我們看上面其實感覺觸發(fā)的事件比較少。所以在 down、move、up 的時候會優(yōu)先調(diào)用三個方法 downSelectTapOtherAction、scrollSelectTapOtherAction、upSelectTapOtherAction。這三個方法可以被子類覆寫,如果返回 true 的話表示事件已經(jīng)消耗了,ECWS 就不會再觸發(fā)其他事件。這樣一來子類也可以對手勢進行擴展,例如按住某個地方單指縮放等等。
      • 7.我圖中 ECWS 也實現(xiàn)了一個子類 DECWS,這個類簡單的加兩個手勢:
        • 1.單指移動縮放:類似抖音的隨拍,按住元素的右下角的時候可以用拖動來對元素進行縮放和旋轉(zhuǎn)。
        • 2.刪除:類似抖音的隨拍,點擊元素左上角的時候可以直接刪除元素。
    • 3.圖1中有一個特性其實沒有畫出來因為畫不下了,那就是:ECWS 在1和2中的幾乎所有行為都能被外部監(jiān)聽,ElementActionListener 就是負責監(jiān)聽的接口。ECWS 中存有一個 EAL 的 set 集合所以監(jiān)聽器可以添加多個。

2.技術(shù)點實現(xiàn)

我在開發(fā)整個控件的時候遇到過比較多的技術(shù)實現(xiàn)上的難點,所以這一節(jié)就選一些來講講,讓讀者在看源碼的時候不會特別困惑。

(1).定義數(shù)據(jù)結(jié)構(gòu)與繪制坐標系

-----代碼塊1----- ws_element.dart

int mZIndex = -1; // 圖像的層級

  double mMoveX = 0.0; // 初始化后相對 ElementContainerWidget 中心的移動距離

  double mMoveY = 0.0; // 初始化后相對 ElementContainerWidget 中心的移動距離

  double mOriginWidth; // 初始化時內(nèi)容的寬度

  double mOriginHeight; // 初始化時內(nèi)容的高度

  Rect mEditRect; // 可繪制的區(qū)域

  double mRotate = 0.0; // 圖像順時針旋轉(zhuǎn)的角度,以 π 為基準

  double mScale = 1.0; // 圖像縮放的大小

  double mAlpha = 1.0; // 圖像的透明度

  bool mIsSelected = false; // 是否處于選中狀態(tài)

  bool mIsSingeFingerMove = false; // 是否處于單指移動的狀態(tài)

  bool mIsDoubleFingerScaleAndRotate = false; // 是否處于雙指旋轉(zhuǎn)縮放的狀態(tài)

  Widget mElementShowingWidget; // 展示內(nèi)容的 widget

  Offset mOffset; // ElementContainerWidget 相對屏幕的位移

函數(shù)未動數(shù)據(jù)先行,數(shù)據(jù)結(jié)構(gòu)是一個框架非常核心的東西,定義了一個好的數(shù)據(jù)結(jié)構(gòu)可以省去很多不必要的代碼。所以這一小節(jié)我們來根據(jù)代碼塊1定義一下數(shù)據(jù)結(jié)構(gòu)和 Widget 繪制坐標系

  • 1.我們將 WE 所在的 ECWS 作為 WE 中 view 的可繪制區(qū)域,代碼塊1中的 mEditRect 就是這個區(qū)域代表的矩形。所以 mEditRect 一般為[0, 0, ECWS.getWidth, ECWS.getHeight],mEditRect 的單位為px。

  • 2.我們定義的坐標系原點在 mEditRect 的中心點,也就是 ECWS 的中心點。mMoveX、mMoveY 分別表示 view 距離坐標系原點的距離。因為它們倆默認為 0,所以一般 view 被添加到 ECWS 中的時候默認位置就在 ECWS 的中心。這兩個參數(shù)的單位為px。

  • 3.我們的坐標系具有 z 軸,mZIndex 就是 z 軸的坐標,z 軸表示 view 的層疊關(guān)系,mZIndex 為 0 時表示 view 在 ECWS 的頂層。mZindex 默認為 -1,表示 view 沒有被添加到 ECWS 中。mZIndex 是整數(shù)。

  • 4.我們定義 mRotate 為正時 view 順時針轉(zhuǎn)動,mRotate 的區(qū)間為[-360,360]。

    5.我們定義 view 沒有縮放的時候 mScale 為 1,mScale 為 2 的時候表示 view 放大 2 倍,以此類推。

  • 6.mOriginWidth 和 mOriginHeight 為 view 的初始大小,單位是px

  • 7.mAlpha 為 view 的透明度,默認為 1 且小于等于1。

  • 8.剩下的參數(shù)就不用解釋了,代碼里面都有注釋。

(2).WE是如何刷新元素的

-----代碼塊2----- ws_element.dart
    
  add() {
    mElementShowingWidget = initWidget();
  }

  Widget initWidget();

  Widget buildTransform() {
    Matrix4 matrix4 = Matrix4.translationValues(mMoveX, mMoveY, 0);
    matrix4.rotateZ(mRotate);
    matrix4.scale(mScale, mScale, 1);
    return Transform(
      alignment: Alignment.center,
      transform: matrix4,
      child: Opacity(
        opacity: mAlpha,
        child: mElementShowingWidget,
      ),
    );
  }
  • 1.刷新元素的核心代碼就是代碼塊2:
    • 1.首先在 ECWS 添加一個 WE 的時候,WE 的子類中可以通過實現(xiàn) initWidget() 來初始化自己需要的元素內(nèi)容
    • 2.然后每次數(shù)據(jù)更新時,我們會通過 buildTransform() 構(gòu)建一個 Widget 給外部使用。
    • 3.而 buildTransfrom 內(nèi)部則是通過 Matrix4 和 Transform 來實現(xiàn)移動旋轉(zhuǎn)縮放,通過 Opacity 來進行 Alpha 變換。

(3).ECWS如何構(gòu)建整個容器

-----代碼塊2----- element_container_widget.dart

@override
  Widget build(BuildContext context) {
    RawGestureDetector gestureDetectorTwo = GestureDetector(
      child: GestureDetector(
        child: Stack(
            alignment: AlignmentDirectional.center,
            key: globalKey,
            children: mElementList.map((e) {
              return e.buildTransform();
            })
                .toList()
                .reversed
                .toList()
        ),
        onPanUpdate: onMove,
        behavior: HitTestBehavior.opaque,
      ),
    ).build(context);
    gestureDetectorTwo.gestures[RotateScaleGestureRecognizer] =
        GestureRecognizerFactoryWithHandlers<RotateScaleGestureRecognizer>(
              () => RotateScaleGestureRecognizer(debugOwner: this),
              (RotateScaleGestureRecognizer instance) {
            instance
              ..onStart = onDoubleFingerScaleAndRotateStart
              ..onUpdate = onDoubleFingerScaleAndRotateProcess
              ..onEnd = onDoubleFingerScaleAndRotateEnd;
          },
        );
    return Listener(
      child: ConstrainedBox(
        constraints: BoxConstraints(
          minHeight: double.infinity,
          minWidth: double.infinity,
        ),
        child: gestureDetectorTwo,
      ),
      behavior: HitTestBehavior.opaque,
      onPointerDown: onDown,
      onPointerUp: onUp,
    );
  }

  • 1.我們都知道 State 中需要在 build() 中返回一個 Widget 給 StatefulWidget。
  • 2.為了裝下多個有層疊關(guān)系的元素,我們使用 Stack 作為元素的容器。
  • 3.Stack 外面包裝了 GestureDetector 來處理 move 事件。
  • 4.GestureDetector 外部包裝了我自定義的 RotateScaleGestureRecognizer 來處理雙指旋轉(zhuǎn)縮放事件。
  • 5.最外層則是用 Listener 來監(jiān)聽手指 down 和 up 事件。
  • 6.上面這樣的設(shè)計的原因我會在后面深入 Flutter 的時候講解。

3.源碼流程解析

這一節(jié)我主要會對項目中的測試 demo 進行源碼流程分析,讓讀者對控件整體的運行方式有個簡單的了解。這一節(jié)主要是講解源碼,所以讀者一定要去 clone 源碼,跟隨文章的腳步前進。

(1).添加元素

  • 1.簡單的初始化動作我就不贅述了,我們從 main.dart 的 add 按鈕開始。點擊后先會創(chuàng)建一個 StickerElement 這個是我測試用的元素,里面代碼很簡單也不說了。
  • 2.addSelectAndUpdateElement 是一個組合方法,里面調(diào)用了 addElement、selectElement、update,也就是添加元素,選中元素,更新元素。我們一個個來分析::
    • 1.addElement:這個方法里主要做了下面這些事情:
      • 1.進行數(shù)據(jù)檢查,如果被添加的 WE 為空或者該 WE 已經(jīng)在 ECWS 中,那么添加失敗。
      • 2.在 ECWS 中我維持了一個 WE 的 List,所有的 WE 都存于其中,每次 add 的時候 WE 都會被添加到 list 的最前面 ,其他 WE 的 mZIndex 也會順勢更新。
      • 3.調(diào)用 WE.add 方法,里面使用 initWidget 初始化了 mElementShowingView,前面我們說過了 initWidget 的邏輯由子類定義。
      • 4.調(diào)用監(jiān)聽器的對應(yīng)方法,且調(diào)用自動取消選中的方法(ECWS 可以被外部決定是否自動取消選中)。
    • 2.selectElement:WE 被 add 了之后,我們這里直接將其選中,代碼里面主要做了下面這些事情:
      • 1.進行數(shù)據(jù)檢查,如果需要選中的 WE 沒有被添加到 ECWS 中則選中失敗。
      • 2.將需要選中的 WE 從 list 中移除然后添加到 list 的頂部,然后順便更新其他 WE 的 mZIndex。
      • 3.調(diào)用 WE 的 select 方法,里面主要就是更新要選中的 WE 的數(shù)據(jù)。
      • 4.調(diào)用監(jiān)聽器對應(yīng)的方法。
    • 3.update:前面都做好了,就需要將 WE 調(diào)整到其應(yīng)該的狀態(tài),這里我想大家都猜到了就是調(diào)用 setState 然后其會觸發(fā)我們在第二節(jié)中說的 build 方法,然后調(diào)用每個 WE 的 buildTransform 返回數(shù)據(jù)被更新后的 Widget。

(2).元素單指手勢

元素手勢不像添加元素那樣需要外部調(diào)用,元素手勢是通過事件分發(fā)觸發(fā)的,我們這里不講 Flutter 的事件分發(fā)機制,只講我們基于其上的邏輯。

  • 1.對于元素單指手勢的處理,主要看三個觸摸事件:down、move、up。所以我們直接看 ECWS.build 中設(shè)置的三個回調(diào)方法。
    • 1.onDown 里面的邏輯如下:
      • 1.通過 findElementByPosition 根據(jù) down 的位置找到當前位置下最頂層的 WE。
      • 2.如果當前有選中的 WE 且與當前觸摸 WE 是同一個的話,那么先調(diào)用 downSelectTapOtherAction,這個函數(shù)可以被子類覆寫,默認返回 false。也就是說子類可以優(yōu)先處理當前事件,如果子類處理了這個事件,那么 return。如果子類不處理,那么將 mMode 標記為 SELECTED_CLICK_OR_MOVE,表示最終的手勢可能是點擊元素,也可能是移動元素。具體的行為需要 move 或者 up 的時候才能判定。
      • 3.如果當前有選中的 WE 但與當前觸摸的 WE 不是同一個的時候也分兩種情況:一種情況是觸摸的 WE 不存在,此時表示將 mMode 標記為 SINGLE_TAP_BLANK_SCREEN 表示點擊了 ECWS 的空白區(qū)域。另一種情況是觸摸的 WE 存在,此時表示重新選中了一個 WE。
      • 4.如果當前沒有選中的 WE,也會有兩種情況:一個是觸摸的 WE 也不存在,那么和前面一樣表示點擊空白區(qū)域。否則的話就是選中一個 WE。
    • 2.onMove 中會優(yōu)先將 move 事件交給 scrollSelectTapOtherAction,該方法也可以被子類覆寫,同樣默認返回 false,如果子類處理了這個事件,那么就直接 return 了。否則當 mModeSELECTED_CLICK_OR_MOVE(已經(jīng)選中了 WE 開始移動)、SELECT(沒有選中 WE 開始移動)、MOVE(WE 移動過程中) 三種情況中的一種的時候,都可以觸發(fā)移動手勢。具體的邏輯在 singleFingerMove 中:
      • 1.先根據(jù) mMode 的狀態(tài),調(diào)用 singleFingerMoveStartsingleFingerMoveProcess。singleFingerMoveStart 中調(diào)用了監(jiān)聽器和 WE 的對應(yīng)方法,里面基本沒什么邏輯。 singleFingerMoveProcess 中也調(diào)用了監(jiān)聽和 WE 的對應(yīng)方法,但是 WE 的對應(yīng)方法中更新了 mMoveX 和 mMoveY 的數(shù)據(jù)。
      • 2.調(diào)用 update 更新 WE 中的 view。將 mMode 設(shè)置為 MOVE,表示處于移動中。
    • 3.onUp 方法:
      • 1.mModeSELECTED_CLICK_OR_MOVE,到這里的時候才能確認,用戶的行為是選中了元素之后的點擊,我們在前面分析過了這里面的事件分發(fā)的機制,這里也不贅述了。
      • 2.mModeSINGLE_TAP_BLANK_SCREEN,表示點擊 ECWS 的空白處,這里調(diào)用的 onClickBlank 也是可以被子類覆寫的,可以實現(xiàn)一些自己的邏輯。
      • 3.mModeMOVE,結(jié)束調(diào)用單指移動結(jié)束。

三、Flutter探究

這一章我會從一個 Android 工程師的角度來研究一下 Flutter,講一講我在移植控件時遇見的問題們。

1.Flutter與Android對比

先看看 Flutter 與 Android 寫的 App 實際的比較吧

圖2:對比
  • 1.我在將代碼從 Android 移植到 Flutter 上花費了大概 10 個小時。整個控件在 Android 上開始設(shè)計到開發(fā)完成則是花費了 100 多個小時。所以整個庫的移植成本并不算太高。
  • 2.看上面 gif 的比較,可以發(fā)現(xiàn)流暢度上面并沒有區(qū)別。我找了幾個朋友實際體驗了一下,大家都同樣沒有發(fā)現(xiàn)使用起來有差異。
  • 3.圖3、圖4分別是 Flutter 和 Android 的性能圖。我們發(fā)現(xiàn)的確像很多測評文章里面說到的。Flutter 的內(nèi)存消耗要比 Native 多。在實驗比較的時候我添加了幾十個元素。最后兩端都穩(wěn)定在了一個內(nèi)存數(shù)值上面。Flutter 是 256MB 左右,Android 是 128MB 左右。
圖3:flutter profile
圖4:android profile
  • 4.在移植代碼的過程中,我總結(jié)了下面這些寫 Java 和 Dart 之間的區(qū)別:
    • 1.Dart 有非常多的語法糖,代碼比起 java 來說有比較多的精簡。
    • 2.Dart 的傳參方式使得寫 Flutter 控件的時候更像是在寫屬性配置表。

2.Flutter原理

以一個 Android 工程師的眼光來看 Flutter

(1).Flutter的事件簡單總結(jié)

  • 1.LIstener 是手勢的基礎(chǔ):GestureDetector 是基于 Listener 開發(fā)的。

  • 2.事件自底向上,事件不可截斷

    • 1.先定義一下:自底向上表示從子 view 到父 view。自頂向下表示從父 view 到子 view。
    • 2.做過 Android 的同學知道 Android 中的事件是一個自頂向下再自底向上的過程。在中間的任意一環(huán)我們都可以進行攔截,從而讓事件不再繼續(xù)傳遞。
    • 3.Flutter 的事件模型則是:自底向上,而且目前來看沒有任何操作能阻斷這個流程。
    • 4.也就是說,如果我們使用 Listener 對任意一個 Widget 進行監(jiān)聽,那么我們在事件傳遞的過程中阻止 Listener 獲取事件。
    • 5.事件不可截斷的特性在開發(fā)中最有用的地方就是:如果我們使用 tapUp,tapDown,這類手勢想要監(jiān)聽手指的抬起和放下,那么這些手勢可能會被其他手勢給沖掉。此時我們就能使用 Listener 來通過監(jiān)聽具體的 down 和 up 事件,因為這個是不可截斷的。
  • 3.開發(fā)中我們使用 GestureDetector 封裝 Widget,我們定義的一個個手勢回調(diào)會讓 GestureDetector 生成多個 GestureRecognizer 附著在當前的 Widget 上以處理 Widget 接收到的事件。

  • 4.每根手指的 down、move、up 都是一個事件流,當 down 事件自底向上確立了一個 Widget 鏈的時候,附著在鏈中各個 Widget 上的 GestureRecognizer 們就會去競爭這個事件流的歸屬。

  • 5.一個事件流的勝出 GestureRecognizer 只有一個,勝出后整個事件流都屬于這個 GestureRecognizer 。

  • 6.GestureRecognizer 的勝出機制,就是 Flutter 在事件不可截斷這個 feature 上的補充的靈活性,可以使得某個 Widget 上的手勢被截斷,推薦優(yōu)先使用 Gesture

  • 7.Gesture 的勝出機制是怎么樣的?

    • 1.如果一次競爭中只有一個 GestureRecognizer,那么他就直接勝出。
    • 2.如果一次競爭中有多個相同的 GestureRecognizer,那么越底層的越勝出。
    • 3.如果一次競爭中有不同的 GestureRecognizer:
      • 1.GestureRecognizer 中定義了一個超時機制,有些 GestureRecognizer 定義了某個事件進行了一個時間閾值后如果沒有其他 GestureRecognizer 申請延長閾值那么本 GestureRecognizer 就直接勝出。例如:TapGestureRecognizer 定義了 down 事件進行了 100 ms 之后,如果沒有其他 GestureRecognizer 延長閾值,那么自己就獲得事件流。
      • 2.而 LongPressGestureRecognizer 定義的時間閾值是 500ms,如果 500ms 后沒有其他 GestureRecognizer 申請延長閾值則自己獲得事件流。
      • 3.那么 TapGestureRecognizer 和 LongPressGestureRecognizer 都在的時候,通過 down 事件的長短來判斷誰勝出。

(2).Flutter的繪制邏輯

Flutter 核心原理

四、尾巴

啊!感覺這篇文章有點虎頭蛇尾的感覺,文章從開始到結(jié)束跨了好幾周。中間又是加班又是搬家,把我的熱血都消磨了。本來多加一些 Flutter 的深入探究的,但是感覺會越寫越久,所以先就這樣。接下來我會寫一系列文章來分析 Flutter 的原理和 Flutter Sdk。所以更多內(nèi)容敬請期待!ps:一鼓作氣,再而竭,三而衰。真是完美的表現(xiàn)了我寫這篇文章的過程,希望讀者們不要學我。

連載文章

不販賣焦慮,也不標題黨。分享一些這個世界上有意思的事情。題材包括且不限于:科幻、科學、科技、互聯(lián)網(wǎng)、程序員、計算機編程。下面是我的微信公眾號:世界上有意思的事,干貨多多等你來看。

世界上有意思的事

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 作者: Mike Bluestein | 譯:孫印鳳 原文地址: [https://www.smashingmag...
    格老子閱讀 3,557評論 0 6
  • 原文在此,此處只為學習 Widget與ElementWidget主要接口Stateless WidgetState...
    lltree閱讀 4,623評論 0 1
  • Flutter是谷歌的移動UI框架,可以快速在iOS和Android上構(gòu)建高質(zhì)量的原生用戶界面。 Flutter可...
    Cat9527閱讀 781評論 0 1
  • 目錄 一、Flutter 為何使用Dart開發(fā)語言二、Flutter的UI系統(tǒng)1.特點2.架構(gòu)簡介2.1 Flut...
    十拿九穩(wěn)啦閱讀 3,886評論 3 28
  • 擬古.戊戌元旦口占 薄蔚嵐云春日高, 冰消潤土脂如膏。 東風可意傳佳訊, 早送烏衣返燕巢!
    微仲子孫閱讀 154評論 0 0

友情鏈接更多精彩內(nèi)容