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
既然是手勢交互,那麼就必然要檢測手勢,在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。性能
本文主要關注的的是拖動相關的,好比onPanXX
、onHorizontalDragXX
、onVerticalDragXX
等等回調事件。學習
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進行各類不一樣的變換,明白了這一點,想作到這樣的效果並不難。
這裏的交互都是橫向的滑動,所以這裏主要處理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便會從新渲染視圖,從而達到上圖的效果。
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一致就能夠了。
跟首頁同樣的思路,只是這裏的手勢是垂直方向。
佈局一樣是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…
Github: github.com/ditclear