Flutter?Flow實(shí)現(xiàn)滑動(dòng)顯隱層示例詳解
1. 前言
最近要實(shí)現(xiàn)一個(gè)小需求,涵蓋了很多知識(shí)點(diǎn),比如手勢(shì)、動(dòng)畫、布局等。挺有意思的,寫出來(lái)和大家分享一下。如下所示,分為上下兩層;當(dāng)左右滑時(shí),上層會(huì)隨偏移量而平移,從而讓上層產(chǎn)生滑動(dòng)手勢(shì)顯隱的效果:
標(biāo)題
這里上層通過不透明度 0.2 的藍(lán)色示意,實(shí)際使用時(shí)可以改為透明色。很多直播間的浮層就是這種交互邏輯,通過右滑來(lái)隱藏浮層。
直播 | 右滑中 |
---|---|
2. 實(shí)現(xiàn)思路
思路其實(shí)非常簡(jiǎn)單,監(jiān)聽橫向滑動(dòng)的手勢(shì)事件,根據(jù)偏移量讓上層組件進(jìn)行偏移。當(dāng)放手時(shí),根據(jù)偏移量是否達(dá)到寬度的一半,使用動(dòng)畫進(jìn)行移出或者關(guān)閉。
偏移的實(shí)現(xiàn)方式有很多,但需要自由地進(jìn)行布局和矩陣變換、透明度,并且需要支持動(dòng)畫的變化,F(xiàn)low 組件是一個(gè)非常不錯(cuò)的選擇。 Flow 組件可以通過代理類對(duì)子組件進(jìn)行自定義布局,靈活性極強(qiáng);如果是 CustomPaint
是 繪制之王 可以繪制萬(wàn)物,那么 Flow
就是 布局之王,可以擺放萬(wàn)物。三年前寫過一篇介紹 Flow 使用的文章: 《【Flutter高級(jí)玩法- Flow 】我的位置我做主》 。 本文就不對(duì) Flow 的基礎(chǔ)使用進(jìn)行介紹了。
另外,在滑動(dòng)過程中需要注意限制偏移量,使偏移量在 0~size.width
之內(nèi);當(dāng)放手時(shí),通過動(dòng)畫控制器來(lái)驅(qū)動(dòng)動(dòng)畫,使用補(bǔ)間讓偏移量運(yùn)動(dòng)到 0
(打開) 或 size.width
(關(guān)閉) 。當(dāng)關(guān)閉時(shí),在右下角展示一個(gè)按鈕用于點(diǎn)擊展開:
3. 布局的代碼實(shí)現(xiàn)
Flow 組件布局最重要的是實(shí)現(xiàn) FlowDelegate
,在其中的 paintChildren
方法中實(shí)現(xiàn)布局的邏輯。和 CustomPainter
類似,FlowDelegate
的實(shí)現(xiàn)類也可以通過 super
構(gòu)造為 repaint
入?yún)⒃O(shè)置可監(jiān)聽對(duì)象??杀O(jiān)聽對(duì)象的變化會(huì)觸發(fā) paintChildren
重新繪制:
SwipeFlowDelegate
實(shí)現(xiàn)類再構(gòu)造時(shí)傳入可監(jiān)聽對(duì)象 offsetX
,在繪制索引為 1
的孩子時(shí),通過 Matrix4
進(jìn)行偏移。這樣只要在手勢(shì)水平滑動(dòng)中,更新 offsetX 值即可。另外,可以根據(jù) offsetX.value
是否達(dá)到 size.width
知道是否是關(guān)閉狀態(tài),如果已經(jīng)關(guān)閉,繪制按鈕。
class SwipeFlowDelegate extends FlowDelegate { final ValueListenable<double> offsetX; SwipeFlowDelegate(this.offsetX) : super(repaint: offsetX); @override void paintChildren(FlowPaintingContext context) { Size size = context.size; context.paintChild(0); Matrix4 offsetM4 = Matrix4.translationValues(offsetX.value, 0, 0); context.paintChild(1, transform: offsetM4); // 偏移量對(duì)于父級(jí)尺寸 if (offsetX.value == size.width) { Matrix4 m1 = Matrix4.translationValues(size.width / 2 - 30, size.height / 2 - 30, 0); context.paintChild(2, transform: m1); Matrix4 m2 = Matrix4.translationValues(size.width / 2 - 30, -(size.height / 2 - 50), 0); context.paintChild(3, transform: m2); } } @override bool shouldRepaint(covariant SwipeFlowDelegate oldDelegate) { return oldDelegate.offsetX.value != offsetX.value; } }
從這里可以看出,FlowDelegate
的最大優(yōu)勢(shì)是可以自定義孩子的繪制與否,還可以在繪制時(shí)通過 Matrix4
對(duì)孩子進(jìn)行矩陣變換,還有可選參數(shù)可以控制透明度。接下來(lái)使用 Flow
組件時(shí),提供 SwipeFlowDelegate
,并在 children 列表中依次放入子組件。其中前兩個(gè)組件由外界傳入,分別是底組件和上層組件,這樣組件的布局就完成了,接下來(lái)監(jiān)聽事件,更新 factor
即可:
final ValueNotifier<double> factor = ValueNotifier(0); Flow( delegate: SwipeFlowDelegate(factor), children: [ widget.content, widget.overflow, GestureDetector( onTap: open, child: const Icon(Icons.menu_open_outlined, color: Colors.white)), GestureDetector( onTap: () { Navigator.of(context).pop(); }, child: const Icon(Icons.close, color: Colors.white)) ], )
4. 手勢(shì)的監(jiān)聽
這里手勢(shì)的處理是非常簡(jiǎn)單的,通過 GestureDetector
監(jiān)聽水平拖拽事件。在 onHorizontalDragUpdate
中根據(jù)拖拽的偏移量更新 factor
的值,其中通過 .clamp(0, widget.width)
可以限制偏移量的取值區(qū)間。
@override Widget build(BuildContext context) { return GestureDetector( behavior: HitTestBehavior.opaque, onHorizontalDragUpdate: _onHorizontalDragUpdate, onHorizontalDragEnd: _onHorizontalDragEnd, child: SizedBox( height: MediaQuery.of(context).size.height, width: widget.width, child: Flow( delegate:// 同上,略... ); } void _onHorizontalDragUpdate(DragUpdateDetails details) { double cur = factor.value + details.delta.dx; factor.value = cur.clamp(0, widget.width); } void _onHorizontalDragEnd(DragEndDetails details) { if (factor.value > widget.width / 2) { close(); } else { open(); } }
最后在 _onHorizontalDragEnd
回調(diào)中,根據(jù)當(dāng)前偏移量是否大于一般寬度,決定關(guān)閉還是打開。期間過程使用動(dòng)畫進(jìn)行偏移量的過渡變化。
5. 動(dòng)畫的使用
動(dòng)畫的使用,主要是通過 AnimationController
動(dòng)畫控制器來(lái)驅(qū)動(dòng)數(shù)值的變化;在放手時(shí) Tween
創(chuàng)建補(bǔ)間動(dòng)畫器,監(jiān)聽動(dòng)畫器數(shù)值的變化更新偏移量。這樣偏移量就可以在指定時(shí)間內(nèi),在兩個(gè)值之間漸變,從而產(chǎn)生動(dòng)畫效果。比如抬手時(shí),open
方法是讓偏移量從當(dāng)前位置變化到 0
:
class _ScrollHideWrapperState extends State<ScrollHideWrapper> with SingleTickerProviderStateMixin { late AnimationController _ctrl; final ValueNotifier<double> factor = ValueNotifier(0); @override void initState() { super.initState(); _ctrl = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ); } @override Widget build(BuildContext context) { // 略同... } // 動(dòng)畫關(guān)閉 Future<void> close() async { Animation<double> anim = Tween<double>(begin: factor.value, end: widget.width).animate(_ctrl); anim.addListener(() => factor.value = anim.value); await _ctrl.forward(from: 0); } // 動(dòng)畫打開 Future<void> open() async { Animation<double> anim = Tween<double>(begin: factor.value, end: 0).animate(_ctrl); anim.addListener(() => factor.value = anim.value); await _ctrl.forward(from: 0); } }
如果想讓動(dòng)畫的變化非勻速,可以使用 Curve 來(lái)控制動(dòng)畫曲線。這樣,基于 Flow 實(shí)現(xiàn)的自定義布局,就可以根據(jù)手勢(shì)和動(dòng)畫,完成特定的交互功能。從這里可以看出 Flow 自定義布局的靈活性非常強(qiáng),很多疑難雜癥,都可以使用它來(lái)完成。
比如企業(yè)微信中:側(cè)滑展示左欄,而且上層不會(huì)全部消失,通過 Flow 來(lái)自定義布局就很容易實(shí)現(xiàn)。大家可以基于本文,自己實(shí)現(xiàn)一下作為練習(xí)。那本文就到這里,謝謝觀看 ~
標(biāo)題 | 關(guān)閉 |
---|---|
以上就是Flutter Flow實(shí)現(xiàn)滑動(dòng)顯隱層示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Flutter Flow滑動(dòng)顯隱層的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)中實(shí)現(xiàn)應(yīng)用的前后臺(tái)切換效果
這篇文章主要介紹了Android開發(fā)中實(shí)現(xiàn)應(yīng)用的前后臺(tái)切換效果的方法,文章最后還附帶了監(jiān)聽程序是否進(jìn)入后臺(tái)的判斷方法,需要的朋友可以參考下2016-02-02Android中的ViewPager視圖滑動(dòng)切換類的入門實(shí)例教程
Android中ViewPager通常與Fragments組件共同使用來(lái)實(shí)現(xiàn)視圖切換功能,本文就帶大家一起來(lái)學(xué)習(xí)Android中的ViewPager視圖滑動(dòng)切換類的入門實(shí)例教程:2016-06-06Android實(shí)現(xiàn)LED發(fā)光字效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)LED發(fā)光字效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03安卓圖片反復(fù)壓縮后為什么普遍會(huì)變綠而不是其它顏色?
今天小編就為大家分享一篇關(guān)于安卓圖片反復(fù)壓縮后為什么普遍會(huì)變綠而不是其它顏色?,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12解決Android Studio一直停留在MyApplication:syncing的問題
這篇文章主要介紹了Android Studio一直停留在MyApplication:syncing的完美解決方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10利用Android實(shí)現(xiàn)比較炫酷的自定義View
自定義View、多線程、網(wǎng)絡(luò),被認(rèn)為是Android開發(fā)者必須牢固掌握的最基礎(chǔ)的三大基本功,這篇文章主要給大家介紹了關(guān)于如何利用Android實(shí)現(xiàn)比較炫酷的自定義View的相關(guān)資料,需要的朋友可以參考下2021-07-07Android控件SeekBar仿淘寶滑動(dòng)驗(yàn)證效果
這篇文章主要為大家詳細(xì)介紹了Android控件SeekBar仿淘寶滑動(dòng)驗(yàn)證效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11詳解Android ContentProvider的基本原理和使用
ContentProvider(內(nèi)容提供者)是 Android 的四大組件之一,管理 Android 以結(jié)構(gòu)化方式存放的數(shù)據(jù),以相對(duì)安全的方式封裝數(shù)據(jù)(表)并且提供簡(jiǎn)易的處理機(jī)制和統(tǒng)一的訪問接口供其他程序調(diào)用2021-06-06Android基礎(chǔ)總結(jié)篇之三:Activity的task相關(guān)介紹
這篇文章主要介紹了Android基礎(chǔ)總結(jié)篇之三:Activity的task相關(guān)介紹,具有一定的參考價(jià)值,有需要的可以了解一下。2016-11-11