100 行代码实现Flutter自定义TabBar的示例代码

Flutter 的确很强大,但美中不足的是生态还有待完善,没有出现像前端的 Antd 或 Element 那样全能的基础 UI 库。
由此带来的直接影响是开发效率提不上去,需要耗费大量的时间精力在基础组件的封装上。

官方的 TabBar 不满足需求,又没有合适的轮子,只好自己造轮子啦。接下来带你一步步实现自定义 TabBar……

一、目标和效果

需求目标是:

  • 这个页面不要 material 左侧统一的返回键和 Title
  • 在右侧有取消按钮,点取消即返回
  • 点击 Tab 可以实现 content 切换并带有动画效果
  • 滑动内容区域也可以切换 Tab

效果如下图:

二、实现思路

将整个页面分为两部分,上面的 Tab 按钮和下面的内容区域。

为了保持通用性,上面的 Tab 和下面的内容区域都需要让调用者传入,它们都是 Widget 数组

class STab extends StatefulWidget {
 // tab 集合
 final List<Widget> tabs;
 // 页面集合
 final List<Widget> pages;

 STab({this.tabs, this.pages});

 @override
 _STabState createState() => _STabState();
}

页面整体的布局是一个 Column ,上面是 Tab 区域,下面的 Content 区域用 Expand 包裹,达到撑满整个屏幕的效果。

上面的 Tab 布局,最外层是 Stack 布局,因为需要取消按钮一直在最右边且不能影响 tab 按钮的排版。多个 tab 按钮用横向布局 Row 来排列,并设置居中对齐。

 @override
 Widget build(BuildContext context) {
  return Container(
    child: Column(
   children: [
    TabLayout(widget.tabs, selectedIndex, onTabChange, onCancelClick),
    ContentLayout(widget.pages, swipeControl, onPageChange)
   ],
  ));
 }

下面的内容区域,要实现左右滑动切换的效果,用了一个第三方库 flutter_swiper 。当点击 Tab 的时候,设置 swiper 的下标切换显示的内容;当左右滑动 swiper,设置 tab 的选中状态,达到 tab 选中状态跟 swiper 滑动的联动。

三、组件封装

/// tab 切换组件
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';

class STab extends StatefulWidget {
 // tab 集合
 final List<Widget> tabs;

 // 页面集合
 final List<Widget> pages;

 STab({this.tabs, this.pages});

 @override
 _STabState createState() => _STabState();
}

class _STabState extends State<STab> {
 int selectedIndex = 0;
 SwiperController swipeControl = new SwiperController();

 // tab 索引变化回调
 void onTabChange(index) {
  setState(() {
   selectedIndex = index;
  });
  swipeControl.move(index);
 }

 void onCancelClick() {
  print('cancel');
 }

 void onPageChange(index) {
  setState(() {
   selectedIndex = index;
  });
 }

 @override
 Widget build(BuildContext context) {
  return Container(
    child: Column(
   children: [
    TabLayout(widget.tabs, selectedIndex, onTabChange, onCancelClick),
    ContentLayout(widget.pages, swipeControl, onPageChange)
   ],
  ));
 }
}

/// 上面 Tab 的布局
Widget TabLayout(tabs, selectedIndex, onTabChange, onRightButtonClick) {
 List<Widget> getItem() {
  List<Widget> children = [];
  for (var i = 0; i < tabs.length; i++) {
   children.add(
    GestureDetector(
      onTap: () {
       onTabChange(i);
      },
      child: Container(
       padding: EdgeInsets.only(left: 20, right: 20, bottom: 10),
       decoration: BoxDecoration(
         border: Border(
           bottom: BorderSide(
             color: selectedIndex == i
               ? Color(0xff595959)
               : Colors.transparent,
             width: 3))),
       child: tabs[i],
      )),
   );
  }
  return children;
 }

 return Stack(
  children: [
   Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: getItem(),
   ),
   Positioned(
     top: 0,
     right: 0,
     child: GestureDetector(
      child: Container(
       height: 40,
       padding: EdgeInsets.only(left: 10, right: 10, bottom: 10),
       child: Text(
        '取消',
        textAlign: TextAlign.center,
        style: TextStyle(fontSize: 16),
       ),
      ),
      onTap: () {
       onRightButtonClick();
      },
     ))
  ],
 );
}

/// 下面页面内容布局
Widget ContentLayout(pages, swipeControl, onIndexChanged) {
 return Expanded(
  child: Container(
    decoration: BoxDecoration(color: Colors.white),
    child: Swiper(
     itemCount: pages.length,
     itemBuilder: (BuildContext context, int index) {
      return pages[index];
     },
     loop: false,
     onIndexChanged: (index) {
      onIndexChanged(index);
     },
     controller: swipeControl,
    )),
 );
}

四、如何使用

传入 tabs 和 页面 pages 即可

class Demo extends StatelessWidget {

 final List<Widget> tabBodies = [
  ExpensePage(),
  IncomePage(),
 ];

 @override
 Widget build(BuildContext context) {
  return Scaffold(
   body: Container(
     padding: EdgeInsets.only(top: 30),
     decoration: BoxDecoration(
       color: Color(0xffF9DC62)
     ),
     child: STab(
      tabs: [
       Text('支出', style: TextStyle(fontSize: 18, color: Colors.black),),
       Text('收入', style: TextStyle(fontSize: 18, color: Colors.black)),
      ],
      pages: tabBodies,
     ),
   ),
  );
 }
}

五、结语

组件的封装只是根据业务简单的封装了一下,没有考虑到更多的情况,比如右侧的取消按钮也应该由外部传入,颜色也应该由外部传入,还有没校验传入的数据是否合法……大家可以根据自己的实际业务需求调整源码。

至于封装到什么程度才算好,适合、够用就好。

如果只是给自己的业务用,那封装到这样就够了,只需考虑业务内的场景。如果要开源出去给别人共用,那最好给予更高的定制化能力。

到此这篇关于100 行代码实现Flutter自定义TabBar的示例代码的文章就介绍到这了,更多相关Flutter自定义TabBar内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Flutter沉浸式状态栏/AppBar导航栏/仿咸鱼底部凸起导航栏效果

    如下图:状态栏是指android手机顶部显示手机状态信息的位置. android 自4.4开始新加入透明状态栏功能,状态栏可以自定义颜色背景,使titleBar能够和状态栏融为一体,增加沉浸感. 如上图Flutter状态栏默认为黑色半透明,那么如何去掉这个状态栏的黑色半透明背景色,让其和标题栏颜色一致,通栏沉浸式,实现如下图效果呢?且继续看下文讲述. 在flutter项目目录下找到android主入口页面MainActivity.kt或MainActivity.java,判断一下版本号然后将状态

  • flutter BottomAppBar实现不规则底部导航栏

    本文实例为大家分享了flutter实现不规则底部导航栏的具体代码,供大家参考,具体内容如下 实现底部导航栏并点击切换页面可简述为有三种方式 TabBar + TabBarView BottomNavigationBar + BottomNavigationBarItem 自定义 BottomAppBar 在这里 使用 BottomAppBar 来实现 /** * 有状态StatefulWidget * 继承于 StatefulWidget,通过 State 的 build 方法去构建控件 */

  • Flutter 滚动监听及实战appBar滚动渐变的实现

    介绍 在 Flutter 中滚动监听一般可以采用两种方式来实现,分别是ScrollController和NotificationListener这两种方式. ScrollController介绍 ScrollController 介绍一下ScrollController常用的属性和方法: offset:可滚动组件当前的滚动位置. jumpTo(double offset)跳转到指定位置,offset为滚动偏移量. animateTo(double offset,@required Duratio

  • 100 行代码实现Flutter自定义TabBar的示例代码

    Flutter 的确很强大,但美中不足的是生态还有待完善,没有出现像前端的 Antd 或 Element 那样全能的基础 UI 库. 由此带来的直接影响是开发效率提不上去,需要耗费大量的时间精力在基础组件的封装上. 官方的 TabBar 不满足需求,又没有合适的轮子,只好自己造轮子啦.接下来带你一步步实现自定义 TabBar-- 一.目标和效果 需求目标是: 这个页面不要 material 左侧统一的返回键和 Title 在右侧有取消按钮,点取消即返回 点击 Tab 可以实现 content 切

  • Flutter 通过Clipper实现各种自定义形状的示例代码

    本文介绍了Flutter 通过Clipper实现各种自定义形状的示例代码,分享给大家,具体如下: ClipOval 圆形裁剪 ClipOval( child: SizedBox( width: 120.0, height: 120.0, child: Image.asset( Config.assets_avatar_1, ), ), ); CircleAvatar 圆形头像 CircleAvatar( radius: 60.0, backgroundImage: AssetImage( Con

  • C/C++ QT实现自定义对话框的示例代码

    对话框分为多种,常见的有通用对话框,自定义对话框,模态对话框,非模态对话框等,其中通用对话框包括了,QFileDialog文件对话框,QColorDialog颜色对话框,QFontDialog字体对话框,QInputDialog输入对话框等,自定义对话框则主要是实现自己布局的简单页面,区别于窗体对话框则显得更加简单一些,除对话框外,多窗体设计也是最常用的,例如多窗体嵌入,MID窗体等,下面则是每种窗体的代码总结. 创建自定义窗体 1.首先使用两个控件,TableView主要是表格处理,TreeV

  • Python+Flask实现自定义分页的示例代码

    目录 前言 后端 后端思路 后端代码 前端 前端思路 前端代码 前言 分页操作在web开发中几乎是必不可少的,而我们的flask不像django自带封装好的分页操作,要分页则需要依赖flask-sqlalchemy中的分页查询,但是分页这么重要且简单的操作,自己实现必须要会这个思维,我也在网上看了一些,但大体上不合我意,因此这篇我带大家手写一个分页操作! 后端 后端思路 写这个分页操作前我们首先要思考我们需要什么?我们需要将我们需要的东西封装到一个字典里,然后传给前端!那么这里我先说分页算法,很

  • 10行Python代码实现Web自动化管控的示例代码

    本博客将为各位分享Python Helium库,其是在 Selenium库基础上封装的更加高级的 Web 自动化工具,它能够通过网页端可见的标签.名称来和 Web 进行交互,据说比Selenium库简单50%,Helium库主要功能包括:模拟鼠标点击.滑动功能:模拟键盘按键功能:刷新网页功能等. 通过使用Helium库,了解其基本的API使用,即使不熟悉HTML.CSS等网页知识,也可轻松完成网页自动化开发设计,实现学习.工作所需. 1.模块安装 Helium库安装使用pip指令即可实现,如下所

  • Python三十行代码实现简单人脸识别的示例代码

    一.库介绍 opencv,face_recognition,numpy,以及dlib 注意: 安装opencv速度可能过慢,需要更换国内镜像源,参考:https://www.jb51.net/article/208359.htm 附带Python3.7,64位版本 dlib whl下载路径:dlib-19_jb51.rar 二.库安装 pip install opencv-python pip install face_recognition pip install numpy dlib库需进入

  • 基于mybatis plus实现数据源动态添加、删除、切换,自定义数据源的示例代码

    目录 简介 代码示例 mavne依赖 数据源增加.移除 数据源切换 基于AOP切换 基于重写处理器 自定义数据源 简介 基于springboot,mybatis plus集成了一套多数据源的解决方案,在使用时引入相应的插件dynamic-datasource-spring-boot-starter,可以实现数据源的动态添加.删除等功能,对于多租户或者分库等操作可以根据AOP切面代理到不同的数据源.实现单一系统数据隔离的目的. 代码示例 mavne依赖 <!--mybatis-plus--> &

  • SQL server数据库创建代码 filegroup文件组修改的示例代码

    数据库的操作: 1. 对数据文件的操作(添加,删除,修改文件的初始大小,最大大小,步长) 2. 数据库文件的收缩 3. 数据库的只读/读写   read_only只读   read_write可读写   read_only表示只读  read_write表示可读可写 4. 数据库的限制访问(单用户,多用户,限制用户模式) 5.数据库脱机/联机    offline  脱机 alter database love set offline      online 联机    alter databa

  • Java 自定义错误类示例代码

    在程序中,需要抛出异常,然后在用户界面进行错误信息输出. 一种情况是在程序中最后UI显示的时候一个一个异常捕获,然后 显示对应的ErrorMessage,有时候,程序因为业务逻辑的原因需要抛出异常,就需要自定义异常. 如何将异常消息集中处理,以对应多语言话的要求 ,这些错误消息就需要集中处理了. 自定义错误消息. 复制代码 代码如下: public class MyException extends Exception{    private static final long serialVe

  • android系统分享的自定义功能的示例代码

    分享功能是app中特别常见的功能,国内的app基本都支持分享到微信 QQ等主流的社交应用.至于分享功能的实现大多是使用第三方的share sdk一步到位,或者分享的app比较少比如就一个微信 那通常使用微信sdk的分享模块即可.但其实android系统就给我们提供过一种分享的实现方式,代码也比较简单如下 Intent share = new Intent(Intent.ACTION_SEND); share.setType("text/plain"); share.putExtra(I

随机推荐