使用Flutter仿寫TikTok的手勢交互

寫在前面

Flutter 是 Google推出並開源的移動應用開發框架,主打跨平臺、高保真、高性能。開發者能夠經過 Dart語言開發 App,一套代碼同時運行在 iOS 和 Android平臺。git

Flutter官網:flutter-io.cngithub

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

來看看實現的效果:markdown

Gif:giphy.com/gifs/Y0nMQw…框架

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

demo下載: oop

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中提供了一些封裝好的transform效果供開發者選擇,好比:平移(translate)、旋轉(rotate)、縮放(scale)。

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

交互分解

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

  • 首頁的交互

Gif :p1-jj.byteimg.com/tos-cn-i-t2…

這裏的交互都是橫向的滑動,所以這裏主要處理onHorizontalDragXX相關的事件。

而後來看看首頁的佈局:

Left:拍攝頁 Middle:主頁 Right:用戶頁

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

這裏選取拍攝頁(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動畫

Gif:p1-jj.byteimg.com/tos-cn-i-t2…

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一致就能夠了。

  • 詳情頁的交互

Gif :p1-jj.byteimg.com/tos-cn-i-t2…

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

佈局一樣是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.cn/user/817692…

Github: github.com/ditclear

相關文章
相關標籤/搜索