寫在前面
Flutter 是 Google推出並開源的移動應用開發框架,主打跨平臺、高保真、高效能。開發者可以通過 Dart語言開發 App,一套程式碼同時執行在 iOS 和 Android平臺。
Flutter官網:flutter-io.cn
抖音,英文名TikTok,一款火遍全球的短視訊App。在玩抖音的日子裡,最令我感到舒服的就是抖音的手勢互動,加上近期都在進行Flutter方面的學習,因此就產生了使用Flutter來仿寫TikTok手勢互動的想法。
來看看實現的效果:
Github地址:github.com/ditclear/ti…
demo下載:
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。
本文主要關注的的是拖動相關的,比如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便會重新渲染檢視,從而達到上圖的效果。
-
Hero動畫
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…
如果本文對你有幫助,請點贊支援。
參考資料:
- Flutter實戰:book.flutterchina.club/
- 解析Flutter中的手勢控制Gestures:www.jianshu.com/p/228b2d043…
==================== 分割線 ======================
如果你想了解更多關於MVVM、Flutter、響應式程式設計方面的知識,歡迎關注我。
你可以在以下地方找到我:
簡書:www.jianshu.com/u/117f1cf0c…
Github: github.com/ditclear