使用Flutter仿寫TikTok的手勢互動

ditclear發表於2019-04-20

寫在前面

Flutter 是 Google推出並開源的移動應用開發框架,主打跨平臺、高保真、高效能。開發者可以通過 Dart語言開發 App,一套程式碼同時執行在 iOS 和 Android平臺。

Flutter官網:flutter-io.cn

抖音,英文名TikTok,一款火遍全球的短視訊App。在玩抖音的日子裡,最令我感到舒服的就是抖音的手勢互動,加上近期都在進行Flutter方面的學習,因此就產生了使用Flutter來仿寫TikTok手勢互動的想法。

來看看實現的效果:

使用Flutter仿寫TikTok的手勢互動

Gif:giphy.com/gifs/Y0nMQw…

Github地址:github.com/ditclear/ti…

demo下載:

使用Flutter仿寫TikTok的手勢互動

GestureDetector以及Transform

既然是手勢互動,那麼就必然要檢測手勢,在Flutter中提供了GestureDetector來幫助開發者,並提供了多個回撥來處理手勢。

Property/Callback Description
onTapDown 使用者每次和螢幕互動時都會被呼叫
onTapUp 使用者停止觸控螢幕時觸發
onTap 短暫觸控螢幕時觸發
onTapCancel 使用者觸控了螢幕,但是沒有完成Tap的動作時觸發
onDoubleTap 使用者在短時間內觸控了螢幕兩次
onLongPress 使用者觸控螢幕時間超過500ms時觸發
onVerticalDragDown 當一個觸控點開始跟螢幕互動,同時在垂直方向上移動時觸發
onVerticalDragStart 當觸控點開始在垂直方向上移動時觸發
onVerticalDragUpdate 螢幕上的觸控點位置每次改變時,都會觸發這個回撥
onVerticalDragEnd 當使用者停止移動,這個拖拽操作就被認為是完成了,就會觸發這個回撥
onVerticalDragCancel 使用者突然停止拖拽時觸發
onHorizontalDragDown 當一個觸控點開始跟螢幕互動,同時在水平方向上移動時觸發
onHorizontalDragStart 當觸控點開始在水平方向上移動時觸發
onHorizontalDragUpdate 螢幕上的觸控點位置每次改變時,都會觸發這個回撥
onHorizontalDragEnd 水平拖拽結束時觸發
onHorizontalDragCancel onHorizontalDragDown沒有成功完成時觸發
onPanDown 當觸控點開始跟螢幕互動時觸發
onPanStart 當觸控點開始移動時觸發
onPanUpdate 螢幕上的觸控點位置每次改變時,都會觸發這個回撥
onPanEnd pan操作完成時觸發
onScaleStart 觸控點開始跟螢幕互動時觸發,同時會建立一個焦點為1.0
onScaleUpdate 跟螢幕互動時觸發,同時會標示一個新的焦點
onScaleEnd 觸控點不再跟螢幕有任何互動,同時也表示這個scale手勢完成

GestureDetector並不會監聽上面所有的手勢,只有傳入的callbacks非空時,才會監聽。所以,如果你想要禁用某個手勢時,可以給對應的callback傳null。

本文主要關注的的是拖動相關的,比如onPanXXonHorizontalDragXXonVerticalDragXX等等回撥事件。

Transform可以在其子Widget繪製時對其應用一個矩陣變換(transformation),Matrix4是一個4D矩陣,通過它我們可以實現各種矩陣操作。

Container(
  color: Colors.black,
  child: new Transform(
    alignment: Alignment.topRight, //相對於座標系原點的對齊方式
    transform: new Matrix4.skewY(0.3), //沿Y軸傾斜0.3弧度
    child: new Container(
      padding: const EdgeInsets.all(8.0),
      color: Colors.deepOrange,
      child: const Text('Apartment for rent!'),
    ),
  ),
);
複製程式碼

效果如下:

使用Flutter仿寫TikTok的手勢互動

在Flutter中提供了一些封裝好的transform效果供開發者選擇,比如:平移(translate)、旋轉(rotate)、縮放(scale)。

在瞭解了這兩點之後,我們來逐步分解前文的效果。

互動分解

首先,需要明確的是這些互動效果其實都是通過檢測手指的滑動,得到一個x座標或者y座標的偏移量,然後配合Transform進行各種不同的變換,明白了這一點,想做到這樣的效果並不難。

  • 首頁的互動

使用Flutter仿寫TikTok的手勢互動

Gif :user-gold-cdn.xitu.io/2019/4/20/1…

這裡的互動都是橫向的滑動,因此這裡主要處理onHorizontalDragXX相關的事件。

然後來看看首頁的佈局:

使用Flutter仿寫TikTok的手勢互動

Left:拍攝頁 Middle:主頁 Right:使用者頁

外層是一個GestureDetector用於處理整個頁面的手勢,裡面用的是一個Stack,類似於Android中的FrameLayout,它包含3個Transform的子Widget。

使用Flutter仿寫TikTok的手勢互動

這裡選取拍攝頁(left)來具體談談.

通過觀察可以發現,隨著偏移量的改變,這裡其實包含兩個變化:1.縮放 2. 前景色透明度

縮放可以直接採用前文提到的Transform.scale,前景色可以用foregroundDecoration通過改變Color的透明度來達到效果,看看實現:

/// 左側Widget
///
/// 通過 [Transform.scale] 進行根據 [offsetX] 縮放
/// 最小 0.88 最大為 1
Transform buildLeftPage(double screenWidth) {
  return Transform.scale(
    scale: 0.88 + 0.12 * offsetX / screenWidth < 0.88 ? 0.88 : 0.88 + 0.12 * offsetX / screenWidth,
    child: Container(
      child: Image.asset(
        "assets/left.png",
        fit: BoxFit.fill,
      ),
      foregroundDecoration: BoxDecoration(
          color: Color.fromRGBO(0, 0, 0, 1 - (offsetX / screenWidth)),
         ),
    ),
  );
}
複製程式碼

當我們的手指在橫向移動的時候,記錄下偏移總量offsetX,然後通過setState進行更新。

onHorizontalDragUpdate: (details) {
  // 控制 offsetX 的值在 -screenWidth 到 screenWidth 之間
  if (offsetX + details.delta.dx >= screenWidth) {
    setState(() {
      offsetX = screenWidth;
    });
  } else if (offsetX + details.delta.dx <= -screenWidth) {
    setState(() {
      offsetX = -screenWidth;
    });
  } else {
    setState(() {
      offsetX += details.delta.dx;
    });
  }
}
複製程式碼

通過setState更新偏移量offsetX之後,Flutter便會重新渲染檢視,從而達到上圖的效果。

  • Hero動畫

使用Flutter仿寫TikTok的手勢互動

Gif:user-gold-cdn.xitu.io/2019/4/20/1…

Flutter提供了Hero動畫來實現這樣的過渡效果。Hero指的是可以在路由(頁面)之間“飛行”的widget,簡單來說Hero動畫就是在路由切換時,有一個共享的Widget可以在新舊路由間切換,由於共享的Widget在新舊路由頁面上的位置、外觀可能有所差異,所以在路由切換時會逐漸過渡,這樣就會產生一個Hero動畫。

/// tiktok_page.dart
Widget build(BuildContext context) {
		return	Hero(
              tag: "detail",
              //child
            )
 )               
 
 /// detail_page.dart
 Widget build(BuildContext context) {
	return Hero(
      tag: "detail",
      // child
        )
  }
複製程式碼

保證tag一致就可以了。

  • 詳情頁的互動

使用Flutter仿寫TikTok的手勢互動

Gif :user-gold-cdn.xitu.io/2019/4/20/1…

跟首頁一樣的思路,只是這裡的手勢是垂直方向。

佈局同樣是GestureDetector加上Stack再配合Transform.translate

Hero(
      tag: "detail",
      child: GestureDetector(
        onVerticalDragUpdate: (details){
          // dy 不超過 -screenHeight * 0.6
          dy += details.delta.dy;
          if ((dy < 0 && dy.abs() > screenHeight * 0.6)) {
            dy = -screenHeight * 0.6;
          } else {
            setState(() {});
          }
        },
        child: Stack(
          children: <Widget>[
            Image.asset(
              "assets/detail.png",
              fit: BoxFit.fitWidth,
              width: screenWidth,
              height: screenHeight,
            ),
            Transform.translate(
              offset: Offset(0, dy + screenHeight),
              child: Container(
                  height: screenHeight * 0.6,
                  child: GestureDetector(
                    onTap: () {},
                    child: Image.asset(
                        "assets/comment.png",),
                  )
              ),
            ),
          ],
        ),
      ),
    );
複製程式碼

在手指離開螢幕時,根據偏移利用動畫進行調整。

onVerticalDragEnd: (_){
          // 滑動截止時,根據 dy 判斷是展開還是回縮
          if (dy < 0) {
            if (!isCommentShow && dy.abs() > screenHeight * 0.2) {
              if (dy.abs() > screenHeight * 0.2) {
                animateToTop(screenHeight);
              } else {
                animateToBottom(screenHeight);
              }
            } else {
              if (dy.abs() > screenHeight * 0.4) {
                animateToTop(screenHeight);
              } else {
                animateToBottom(screenHeight);
              }
            }
          }
        },
複製程式碼

寫在最後

總的來說,這些互動都是依靠著對手勢的檢測做到的,相比於Android,Flutter有著一切都是Widget的概念,

GestureDetector以及Hero都是Widget而且提供了很多回撥函式,再配合資料驅動UI和Flutter優秀的渲染機制,減輕了開發者進行手勢互動的難度。

Github地址:github.com/ditclear/ti…

如果本文對你有幫助,請點贊支援。

參考資料:

==================== 分割線 ======================

如果你想了解更多關於MVVM、Flutter、響應式程式設計方面的知識,歡迎關注我。

你可以在以下地方找到我:

簡書:www.jianshu.com/u/117f1cf0c…

掘金:juejin.im/user/582d60…

Github: github.com/ditclear

使用Flutter仿寫TikTok的手勢互動

相關文章