flutter仿微信底部圖標(biāo)漸變功能的實(shí)現(xiàn)代碼
先給大家展示下效果圖,感覺不錯(cuò)請(qǐng)參考實(shí)例代碼。
實(shí)現(xiàn)思路
在flutter中,如果想實(shí)現(xiàn)上面的頁面切換效果,必然會(huì)想到pageView。pageView的controller可以監(jiān)聽到pageView的滾動(dòng)事件,也可以獲取pageView滾動(dòng)的位置,所以我們?cè)跐L動(dòng)事件中根據(jù)位置去改變對(duì)應(yīng)的圖標(biāo)顏色就可以實(shí)現(xiàn)了。
改變圖標(biāo)顏色
圖標(biāo)是從微信中提取出來的,都是webp格式的圖片。要改變圖片顏色可以使用ImageIcon這個(gè)組件。
ImageIcon會(huì)把一張圖片變成單色圖片,所以只要圖片沒有多色的要求,就可以用這個(gè)組件。
既然能改變顏色了,我們也需要知道pageView滾動(dòng)的時(shí)候究竟要改什么顏色。從一個(gè)頁面滾動(dòng)到另一個(gè)頁面的過程中,顏色都是線性漸變的,要獲取這個(gè)過程中的顏色可以使用flutter的Color類提供的lerp方法,作用是獲取兩種顏色之間的線性差值
里面有3個(gè)參數(shù),a和b都是顏色,t是夾在0到1之間的,當(dāng)t為0時(shí)返回a,當(dāng)t為1時(shí)返回b
也就是在滾動(dòng)事件中,計(jì)算出 t ,根據(jù) t 改變圖標(biāo)顏色就可以實(shí)現(xiàn)上面的效果了。
pageController.addListener(() { int currentPage = pageController.page.toInt(); //當(dāng)前頁面的page是double類型的, 把它減去當(dāng)前頁面的int類型就可以得出當(dāng)前頁面到下一個(gè)頁面的偏移量了 double t = pageController.page - currentPage; //根據(jù)上一次的頁面位置獲得方向 if (lastPage <= pageController.page) { //向右滑動(dòng)時(shí)currentPage是當(dāng)前頁 //從當(dāng)前頁過渡到下一頁 streamController.sink.add(StreamModel( timeline: t, index: currentPage, gotoIndex: currentPage + 1)); } else { //向左滑動(dòng)時(shí)currentPage是上一頁 //從當(dāng)前頁過渡到上一頁 streamController.sink.add(StreamModel( timeline: t, index: currentPage + 1, gotoIndex: currentPage)); } lastPage = pageController.page; });
上面代碼中currentPage的值舉個(gè)例子:當(dāng)前page是1,要滑動(dòng)到2,那么它的值是1.11...1.21...這樣一直到2,所以在這個(gè)過程中currentPage是當(dāng)前頁。如果當(dāng)前page是4,要滑動(dòng)到3的時(shí)候,它的值是3.99...3.81...這樣一直到3,在這個(gè)過程中currentPage就是上一頁了。
t 的計(jì)算就更簡(jiǎn)單了,1.11-1=0.11,3.99-3=0.99 .....
管理圖標(biāo)顏色
因?yàn)槲沂怯昧俗詭У牡撞繉?dǎo)航BottomNavigationBar,在pageController的滾動(dòng)事件中改變圖標(biāo)顏色太麻煩了,所以用了Stream來管理圖標(biāo)的狀態(tài)。使用Stream創(chuàng)建一個(gè)多訂閱的管道,讓所有圖標(biāo)都訂閱它,然后在滑動(dòng)事件中把需要的數(shù)據(jù)都發(fā)送給所有圖標(biāo)。
需要的數(shù)據(jù):
class StreamModel { const StreamModel({this.timeline, this.index, this.gotoIndex}); final double timeline; final int index; final int gotoIndex; }
圖標(biāo)組件
構(gòu)造方法設(shè)置一個(gè)index,方便判斷圖標(biāo)是哪個(gè)。
使用StreamBuilder包住要改變顏色的組件,并且綁定從構(gòu)造函數(shù)設(shè)置的StreamController。
在StreamBuilder中根據(jù)pageView滾動(dòng)事件傳進(jìn)來的參數(shù)控制圖標(biāo)顏色。
class BottomNavIcon extends StatelessWidget { final StreamController<StreamModel> streamController; final int index; final String img; final String title; final double fontSize; Color _color; Color _activeColor; final bool isActive; BottomNavIcon(this.title, this.img, this.index, {@required this.streamController, this.isActive = false, this.fontSize = 18.0, Color color = Colors.grey, Color activeColor = Colors.blue}) { _color = isActive ? activeColor : color; _activeColor = isActive ? color : activeColor; } @override Widget build(BuildContext context) { return StreamBuilder( stream: streamController.stream, builder: (BuildContext context, AsyncSnapshot snapshot) { final StreamModel data = snapshot.data; double t = 0.0; if (data != null) { //開始的index if (data.index == index) { t = data.index > data.gotoIndex ? data.timeline : 1.0 - data.timeline; print("this${data.index}:${t}"); } //結(jié)束的index if (data.gotoIndex == index) { t = data.index > data.gotoIndex ? 1.0 - data.timeline //開始的index大于結(jié)束的index方向向左 : data.timeline; //小于方向向右 //過渡到的圖標(biāo)顏色的插值超過0.6時(shí), 個(gè)人感覺當(dāng)前顏色和結(jié)束的哪個(gè)顏色相差太多, //所以超過0.6時(shí)恢復(fù)默認(rèn)顏色 t = t >= 0.6 ? 1 : t; print("goto${data.gotoIndex}:${t}"); } } if (t > 0.0 && t < 1.0) { //color.lerp 獲取兩種顏色之間的線性插值 return Column( children: <Widget>[ ImageIcon(AssetImage(this.img), color: Color.lerp(_color, _activeColor, t)), Text(title, style: TextStyle( fontSize: fontSize, color: Color.lerp(_color, _activeColor, t))), ], ); } return Column( children: <Widget>[ ImageIcon(AssetImage(this.img), color: Color.fromRGBO(_color.red, _color.green, _color.blue, 1)), Text(title, style: TextStyle( fontSize: fontSize, color: Color.fromRGBO( _color.red, _color.green, _color.blue, 1))), ], ); }); } }
圖標(biāo)的顏色都是當(dāng)前的(index == data.index)漸漸變淺,要滾動(dòng)到(index==data.gotoIndex)的圖標(biāo)顏色漸深
創(chuàng)建多訂閱的管道(Stream)
final StreamController<StreamModel> streamController = StreamController.broadcast(); 加載圖標(biāo) for (int i = 0; i < pages.length; i++) { TabBarModel model = pages[i]; bars.add( BottomNavigationBarItem( icon: BottomNavIcon( model.title, 'assets/images/tabbar_' + model.icon + '_c.webp', i, streamController: streamController, ), activeIcon: BottomNavIcon( model.title, 'assets/images/tabbar_' + model.icon + '_s.webp', i, streamController: streamController, isActive: true, ), title: Center(), ), ); }
上面代碼的title為Center的原因是已經(jīng)在圖標(biāo)組件中創(chuàng)建了一個(gè)顯示標(biāo)題的組件,方便一起設(shè)置顏色。這里就不需要了,但是它的title不允許為null,所以隨便給它一個(gè)高寬都是0的組件
結(jié)語
其實(shí)這個(gè)效果和微信的不是一模一樣,微信的應(yīng)該是選中圖標(biāo)疊加到默認(rèn)圖標(biāo)上面。默認(rèn)圖標(biāo)顏色線性漸變,選中圖標(biāo)透明度漸變。flutter實(shí)現(xiàn)這個(gè)用自帶的BottomNavigationBar估計(jì)不行,可能需要自定義一個(gè)底部導(dǎo)航。
第一次寫技術(shù)文章,感覺有點(diǎn)亂,所以貼下完整的代碼地址:
gist: gist.github.com/327100395/9 …
dartPad: dartpad.dev/9dee2497a99…(圖片讀的是本地的,在dartPad中路徑錯(cuò)誤,所以圖片不顯示)
相關(guān)文章
Studio 編譯報(bào)錯(cuò):compileSdkVersion ''android-24'' requires JDK 1.
今天小編就為大家分享一篇關(guān)于Studio編譯報(bào)錯(cuò):compileSdkVersion 'android-24' requires JDK 1.8 or later to compile.的解決辦法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-10-10Android取消EditText自動(dòng)獲取焦點(diǎn)默認(rèn)行為
在項(xiàng)目中,一進(jìn)入一個(gè)頁面, EditText默認(rèn)就會(huì)自動(dòng)獲取焦點(diǎn),很是郁悶,Android 如何讓EditText不自動(dòng)獲取焦點(diǎn)?于是搜集整理一番,曬出來和大家分享,希望對(duì)你們有所幫助2012-12-12Android實(shí)現(xiàn)閃屏及注冊(cè)和登錄界面之間的切換效果
這篇文章主要介紹了Android實(shí)現(xiàn)閃屏及注冊(cè)和登錄界面之間的切換效果,實(shí)現(xiàn)思路是先分別實(shí)現(xiàn)閃屏、注冊(cè)界面、登錄界面的活動(dòng),再用Intent將相關(guān)的活動(dòng)連接起來,實(shí)現(xiàn)不同活動(dòng)之間的跳轉(zhuǎn),對(duì)android 實(shí)現(xiàn)閃屏和界面切換感興趣的朋友一起看看吧2016-11-11Android入門之BroadCast模擬實(shí)現(xiàn)異地登錄事件發(fā)生后的主動(dòng)退出
隨著對(duì)BroadCast的越來越深入,我們今天要實(shí)現(xiàn)一個(gè)稍微復(fù)雜一點(diǎn)的BroadCast。即只允許一個(gè)設(shè)備登錄一個(gè)帳號(hào)時(shí),APP會(huì)彈一個(gè)對(duì)話框如:您的賬號(hào)在別處登錄,請(qǐng)重新登陸!感興趣的可以了解一下2022-12-12Android編程實(shí)現(xiàn)自定義Tab選項(xiàng)卡功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)自定義Tab選項(xiàng)卡功能,結(jié)合完整實(shí)例形式分析了Android自定義tab選項(xiàng)卡的遍歷、設(shè)置及屬性操作相關(guān)技巧,需要的朋友可以參考下2017-02-02Android自定義View中attrs.xml的實(shí)例詳解
這篇文章主要介紹了Android自定義View中attrs.xml的實(shí)例詳解的相關(guān)資料,在自定義View首先對(duì)attrs.xml進(jìn)行布局的實(shí)現(xiàn)及屬性的應(yīng)用,需要的朋友可以參考下2017-07-07安卓(android)仿電商app商品詳情頁按鈕浮動(dòng)效果
很多電商類app的商品詳情頁(其他app也有類似效果,比如qq音樂)都有這么一個(gè)效果:當(dāng)用戶向上滑動(dòng)頁面內(nèi)容超過一定距離后,中間的標(biāo)題欄會(huì)卡在頂部,往下拉回到固定高度后又會(huì)消失。那么如何實(shí)現(xiàn)這個(gè)效果呢?跟著小編一起來學(xué)習(xí)學(xué)習(xí)。2016-08-08Android實(shí)現(xiàn)在列表List中顯示半透明小窗體效果的控件用法詳解
這篇文章主要介紹了Android實(shí)現(xiàn)在列表List中顯示半透明小窗體效果的控件用法,結(jié)合實(shí)例形式分析了Android半透明提示框的實(shí)現(xiàn)與設(shè)置技巧,需要的朋友可以參考下2016-06-06