<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在App中TabBar
形式互動是非常常見的,但是系統提供的的樣式大多數又不能滿足我們產品和UI的想法,這篇就記錄下在Flutter中我在實現自定義TabBar
的一個思路和過程,希望對你也有所幫助~
先看下我最終的效果圖:
首先我們先看下TabBar的構造方法:
const TabBar({ Key? key, required this.tabs,// tab元件列表 this.controller,// tabBar控制器 this.isScrollable = false,// 是否支援捲動 this.padding,// 內部tab內邊距 this.indicatorColor,// 指示器顏色 this.automaticIndicatorColorAdjustment = true,// 指示器顏色是否自動跟隨主題顏色 this.indicatorWeight = 2.0,// 指示器高度 this.indicatorPadding = EdgeInsets.zero,// 指示器padding this.indicator,//選擇指示器樣式 this.indicatorSize,//選擇指示器大小 this.labelColor,// 選擇標籤文字顏色 this.labelStyle,// 選擇標籤文字樣式 this.labelPadding,// 整體標籤邊距 this.unselectedLabelColor,//未選中標籤顏色 this.unselectedLabelStyle,// 未選中標籤樣式 this.dragStartBehavior = DragStartBehavior.start,//設定點選水波紋效果 跟隨全域性點選效果 this.overlayColor,// 設定水波紋顏色 this.mouseCursor, // 滑鼠指標懸停的效果 App用不到 this.enableFeedback,// 點選是否反饋聲音觸覺。 this.onTap,// 點選Tab的回撥 this.physics,// 捲動邊界互動 })
TabBar
一般和TabView
配合使用,TabBar
和 TabView
共有一個控制器從而達到聯動的效果,tab
陣列和tabView
陣列長度必須一致,不然直接報錯。其實這麼多方法,主要的就是用來進行tabs
欄位和指示器相關的樣式改變,我們先來看下官方給出的效果:
List<String> tabs = ["Tab1", "Tab2"]; late TabController _tabController = TabController(length: tabs.length, vsync: this); //tab 控制器 @override Widget build(BuildContext context) { return Column( children: [ TabBar( controller: _tabController, tabs: tabs .map((value) => Tab( height: 44, text: value, )) .toList(), indicatorColor: Colors.redAccent, indicatorWeight: 2, labelColor: Colors.redAccent, unselectedLabelColor: Colors.black87, ), Expanded( child: TabBarView( controller: _tabController, children: tabs .map((value) => Center( child: Text( value, ), )) .toList(), )) ], ); }
上面的程式碼就實現了官方的一個簡單的TabBar,你可以改變切換文字的顏色、字重、指示器的顏色、指示器的高度等一些常見的樣式。
首先我們看下Tab
的原始碼,其實Tab的原始碼很簡單,一共100多行程式碼,就是一個繼承了PreferredSizeWidget
的靜態元件。如果我們想要修改Tab樣式的話,重寫它,修改它即可。
const Tab({ Key? key, this.text,//文字 this.icon,//圖示 this.iconMargin = const EdgeInsets.only(bottom: 10.0), this.height,//tab高度 this.child,// 自定義元件 }) Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final double calculatedHeight; final Widget label; if (icon == null) { calculatedHeight = _kTabHeight; label = _buildLabelText(); } else if (text == null && child == null) { calculatedHeight = _kTabHeight; label = icon!; } else { // 這裡佈局預設icon和文字是上下排列的 calculatedHeight = _kTextAndIconTabHeight; label = Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( margin: iconMargin, child: icon, ), _buildLabelText(), ], ); } return SizedBox( height: height ?? calculatedHeight, child: Center( widthFactor: 1.0, child: label, ), ); }
接下來我們看下指示器,我們發下如果我們想要改變指示器的寬度,官方提供了indicatorSize:
欄位,但是這個欄位接受一個TabBarIndicatorSize
欄位,這個欄位並不是具體的寬度值,而是一個列舉值,見下只有兩種情況,要麼跟tab一樣寬,要麼跟文字一樣寬,顯然這並不能滿足一些產品和UI的需求,比如:寬度要設定成比文字小,指示器離文字再近一點,指示器能不能做成小圓點等等, 那麼這時候我們就不可以靠官方的欄位來實現了。
enum TabBarIndicatorSize { // 寬度和tab控制元件一樣寬 tab, // 寬度和文字一樣寬 label, }
接下來重點是對指示器的完全自定義
我們看到TabBar的建構函式裡有一個indicator
欄位來設定指示器的樣式,接受一個Decoration
裝飾盒子,從原始碼我們看到裡面有一個繪製方法,那麼我們就可以自己建立一個類繼承Decoration
自己繪製指示器不就可以了嗎?
// 建立裝飾盒子 BoxPainter createBoxPainter([ VoidCallback onChanged ]); // 繪製 void paint(Canvas canvas, Offset offset, ImageConfiguration configuration);
但是我們看到官方提供一個UnderlineTabIndicator
類,通過insets
引數可以設定指示器的邊距從而達到設定指示器寬度的效果,但是這並不能固定TabBar的寬度,而且當tabBar數量變化時或者文字長度改變,指示器寬度也會改變,我這裡直接對UnderlineTabIndicator
這個類進行了二次改造, 關鍵程式碼:通過這個方法我們自定義返回已個矩形,自定義我們需要的寬度值即可。
Rect _indicatorRectFor(Rect indicator, TextDirection textDirection) { /// 自定義固定寬度 double w = indicatorWidth; //中間座標 double centerWidth = (indicator.left + indicator.right) / 2; return Rect.fromLTWH( centerWidth, //距離左邊距 // 距離上邊距 indicator.bottom - borderSide.width - indicatorBottom, w, borderSide.width, ); }
到這裡我們就改變了指示器的寬度以及指示器的下邊距設定,接下來我們繼續看,這個類建立了個BoxPainter
類,這個類可以使用畫筆自定義一個裝飾效果,
@override BoxPainter createBoxPainter([VoidCallback? onChanged]) { return _UnderlinePainter( this, onChanged, tabController?.animation, indicatorWidth, ); } void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { // 自定義繪製 }
那不就想畫什麼畫什麼了唄,圓點、矩形等什麼圖形,但是我們雖然可以自定義畫矩形了,但是我們要實現指示器寬度動態變化還需要一個動畫監聽器,其實在我們滑動的過程中,TabController
有一個animation
回撥函數,在我們滑動的時候,他會返回tab位置的偏移量,0~1代表1個tab的位移。
// 回撥函數 動畫插值 tab位置的偏移量 Animation<double>? get animation => _animationController?.view;
並且在滑動的過程中指示器是不斷在繪製的,那麼就好了,我們只需要將動畫不斷偏移的值賦給畫筆進行繪製不就可以了嗎
import 'package:flutter/material.dart'; /// 修改下劃線自定義 class MyTabIndicator extends Decoration { final TabController? tabController; final double indicatorBottom; // 調整指示器下邊距 final double indicatorWidth; // 指示器寬度 const MyTabIndicator({ // 設定下標高度、顏色 this.borderSide = const BorderSide(width: 2.0, color: Colors.white), this.tabController, this.indicatorBottom = 0.0, this.indicatorWidth = 4, }); /// The color and weight of the horizontal line drawn below the selected tab. final BorderSide borderSide; @override BoxPainter createBoxPainter([VoidCallback? onChanged]) { return _UnderlinePainter( this, onChanged, tabController?.animation, indicatorWidth, ); } Rect _indicatorRectFor(Rect indicator, TextDirection textDirection) { /// 自定義固定寬度 double w = indicatorWidth; //中間座標 double centerWidth = (indicator.left + indicator.right) / 2; return Rect.fromLTWH( //距離左邊距 tabController?.animation == null ? centerWidth - w / 2 : centerWidth - 1, // 距離上邊距 indicator.bottom - borderSide.width - indicatorBottom, w, borderSide.width, ); } @override Path getClipPath(Rect rect, TextDirection textDirection) { return Path()..addRect(_indicatorRectFor(rect, textDirection)); } } class _UnderlinePainter extends BoxPainter { Animation<double>? animation; double indicatorWidth; _UnderlinePainter(this.decoration, VoidCallback? onChanged, this.animation, this.indicatorWidth) : super(onChanged); final MyTabIndicator decoration; @override void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { assert(configuration.size != null); // 以offset座標為左上角 size為寬高的矩形 final Rect rect = offset & configuration.size!; final TextDirection textDirection = configuration.textDirection!; // 返回tab矩形 final Rect indicator = decoration._indicatorRectFor(rect, textDirection) ..deflate(decoration.borderSide.width / 2.0); // 圓角畫筆 final Paint paint = decoration.borderSide.toPaint() ..style = PaintingStyle.fill ..strokeCap = StrokeCap.round; if (animation != null) { num x = animation!.value; // 變化速度 0-0.5-1-1.5-2... num d = x - x.truncate(); // 獲取這個數位的小數部分 num? y; if (d < 0.5) { y = 2 * d; } else if (d > 0.5) { y = 1 - 2 * (d - 0.5); } else { y = 1; } canvas.drawRRect( RRect.fromRectXY( Rect.fromCenter( center: indicator.centerLeft, // 這裡控制最長為多長 width: indicatorWidth * 6 * y + indicatorWidth, height: indicatorWidth), // 圓角 2, 2), paint); } else { canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint); } } }
上面原始碼可直接貼上到專案裡使用,直接賦值給indicator
屬性,設定控制器,即可實現開始的效果圖上的互動了。
通過記錄這次實現過程,其實搞明白內部原理,我們就可以輕而易舉的實現各種TabBar
的互動,本篇重點是如何實現自定義,上面的互動只是實現的一個例子,通過這個例子我們可以實現更多的其他的樣式,比如給文字新增全背景漸變色、tab上放置的文字左右新增圖示等等。
到此這篇關於詳解Flutter如何完全自定義TabBar的文章就介紹到這了,更多相關Flutter自定義TabBar內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45