亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Flutter之TabBarView組件項目實戰(zhàn)示例

 更新時間:2022年10月29日 09:01:11   作者:風(fēng)雨_83  
這篇文章主要為大家介紹了Flutter之TabBarView組件項目實戰(zhàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

TabBarView

TabBarView 是 Material 組件庫中提供了 Tab 布局組件,通常和 TabBar 配合使用。

TabBarView 封裝了 PageView,它的構(gòu)造方法:

 TabBarView({
  Key? key,
  required this.children, // tab 頁
  this.controller, // TabController
  this.physics,
  this.dragStartBehavior = DragStartBehavior.start,
}) 

TabController 用于監(jiān)聽和控制 TabBarView 的頁面切換,通常和 TabBar 聯(lián)動。如果沒有指定,則會在組件樹中向上查找并使用最近的一個 DefaultTabController 。

TabBar

TabBar 為 TabBarView 的導(dǎo)航標(biāo)題,如下圖所示

TabBar 有很多配置參數(shù),通過這些參數(shù)我們可以定義 TabBar 的樣式,很多屬性都是在配置 indicator 和 label,拿上圖來舉例,Label 是每個Tab 的文本,indicator 指 “新聞” 下面的白色下劃線。

const TabBar({
  Key? key,
  required this.tabs, // 具體的 Tabs,需要我們創(chuàng)建
  this.controller,
  this.isScrollable = false, // 是否可以滑動
  this.padding,
  this.indicatorColor,// 指示器顏色,默認是高度為2的一條下劃線
  this.automaticIndicatorColorAdjustment = true,
  this.indicatorWeight = 2.0,// 指示器高度
  this.indicatorPadding = EdgeInsets.zero, //指示器padding
  this.indicator, // 指示器
  this.indicatorSize, // 指示器長度,有兩個可選值,一個tab的長度,一個是label長度
  this.labelColor, 
  this.labelStyle,
  this.labelPadding,
  this.unselectedLabelColor,
  this.unselectedLabelStyle,
  this.mouseCursor,
  this.onTap,
  ...
}) 

TabBar 通常位于 AppBar 的底部,它也可以接收一個 TabController ,如果需要和 TabBarView 聯(lián)動, TabBarTabBarView 使用同一個 TabController 即可,注意,聯(lián)動時 TabBarTabBarView 的孩子數(shù)量需要一致。如果沒有指定 controller,則會在組件樹中向上查找并使用最近的一個 DefaultTabController 。另外我們需要創(chuàng)建需要的 tab 并通過 tabs 傳給 TabBar, tab 可以是任何 Widget,不過Material 組件庫中已經(jīng)實現(xiàn)了一個 Tab 組件,我們一般都會直接使用它:

const Tab({
  Key? key,
  this.text, //文本
  this.icon, // 圖標(biāo)
  this.iconMargin = const EdgeInsets.only(bottom: 10.0),
  this.height,
  this.child, // 自定義 widget
})

注意,textchild 是互斥的,不能同時制定。

全部代碼:

import 'package:flutter/material.dart';
/// @Author wywinstonwy
/// @Date 2022/1/18 9:09 上午
/// @Description: 
class MyTabbarView1 extends StatefulWidget {
  const MyTabbarView1({Key? key}) : super(key: key);
  @override
  _MyTabbarView1State createState() => _MyTabbarView1State();
}
class _MyTabbarView1State extends State<MyTabbarView1>with SingleTickerProviderStateMixin {
  List<String> tabs =['頭條','新車','導(dǎo)購','小視頻','改裝賽事'];
  late TabController tabController;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    tabController = TabController(length: tabs.length, vsync: this);
  }
  @override
  void dispose() {
    tabController.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TabbarView',textAlign: TextAlign.center,),
        bottom:TabBar(
            unselectedLabelColor: Colors.white.withOpacity(0.5),
            labelColor: Colors.white,
            // indicatorSize:TabBarIndicatorSize.label,
            indicator:const UnderlineTabIndicator(),
            controller: tabController,
            tabs: tabs.map((e){
              return Tab(text: e,);
            }).toList()) ,
      ),
      body: Column(
      children: [
        Expanded(
          flex: 1,
          child:  TabBarView(
            controller: tabController,
            children: tabs.map((e){
              return Center(child: Text(e,style: TextStyle(fontSize: 50),),);
            }).toList()),)
      ],),
    );
  }
}

運行效果:

滑動頁面時頂部的 Tab 也會跟著動,點擊頂部 Tab 時頁面也會跟著切換。為了實現(xiàn) TabBar 和 TabBarView 的聯(lián)動,我們顯式創(chuàng)建了一個 TabController,由于 TabController 又需要一個 TickerProvider (vsync 參數(shù)), 我們又混入了 SingleTickerProviderStateMixin;

由于 TabController 中會執(zhí)行動畫,持有一些資源,所以我們在頁面銷毀時必須得釋放資源(dispose)。綜上,我們發(fā)現(xiàn)創(chuàng)建 TabController 的過程還是比較復(fù)雜,實戰(zhàn)中,如果需要 TabBar 和 TabBarView 聯(lián)動,通常會創(chuàng)建一個 DefaultTabController 作為它們共同的父級組件,這樣它們在執(zhí)行時就會從組件樹向上查找,都會使用我們指定的這個 DefaultTabController。

我們修改后的實現(xiàn)如下:

class TabViewRoute2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    List tabs = ["新聞", "歷史", "圖片"];
    return DefaultTabController(
      length: tabs.length,
      child: Scaffold(
        appBar: AppBar(
          title: Text("App Name"),
          bottom: TabBar(
            tabs: tabs.map((e) => Tab(text: e)).toList(),
          ),
        ),
        body: TabBarView( //構(gòu)建
          children: tabs.map((e) {
            return KeepAliveWrapper(
              child: Container(
                alignment: Alignment.center,
                child: Text(e, textScaleFactor: 5),
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

可以看到我們無需去手動管理 Controller 的生命周期,也不需要提供 SingleTickerProviderStateMixin,同時也沒有其它的狀態(tài)需要管理,也就不需要用 StatefulWidget 了,這樣簡單很多。

TabBarView+項目實戰(zhàn)

實現(xiàn)導(dǎo)航信息流切換效果并緩存前面數(shù)據(jù):

1 構(gòu)建導(dǎo)航頭部搜索框

import 'package:flutter/material.dart';
import 'package:qctt_flutter/constant/colors_definition.dart';
enum SearchBarType { home, normal, homeLight }
class SearchBar extends StatefulWidget {
  final SearchBarType searchBarType;
  final String hint;
  final String defaultText;
  final void Function()? inputBoxClick;
  final void Function()? cancelClick;
  final ValueChanged<String>? onChanged;
  SearchBar(
      {this.searchBarType = SearchBarType.normal,
      this.hint = '搜一搜你感興趣的內(nèi)容',
      this.defaultText = '',
      this.inputBoxClick,
      this.cancelClick,
      this.onChanged});
  @override
  _SearchBarState createState() => _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      height: 74,
      child: searchBarView,
    );
  }
  Widget get searchBarView {
    if (widget.searchBarType == SearchBarType.normal) {
      return _genNormalSearch;
    }
    return _homeSearchBar;
  }
  Widget get _genNormalSearch {
    return Container(
        color: Colors.white,
        padding: EdgeInsets.only(top: 40, left: 20, right: 60, bottom: 5),
        child: Container(
          height: 30,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(6),
              color: Colors.grey.withOpacity(0.5)),
          padding: EdgeInsets.only(left: 5, right: 5),
          child: Row(
            children: [
              const Icon(
                Icons.search,
                color: Colors.grey,
                size: 24,
              ),
              Container(child: _inputBox),
              const Icon(
                Icons.clear,
                color: Colors.grey,
                size: 24,
              )
            ],
          ),
        ),);
  }
  //可編輯輸入框
  Widget get _homeSearchBar{
    return  Container(
      padding: EdgeInsets.only(top: 40, left: 20, right: 40, bottom: 5),
      decoration: BoxDecoration(gradient: LinearGradient(
          colors: [mainColor,mainColor.withOpacity(0.2)],
          begin:Alignment.topCenter,
          end: Alignment.bottomCenter
      )),
      child: Container(
        height: 30,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(6),
            color: Colors.grey.withOpacity(0.5)),
        padding: EdgeInsets.only(left: 5, right: 5),
        child: Row(
          children: [
            const Icon(
              Icons.search,
              color: Colors.grey,
              size: 24,
            ),
            Container(child: _inputBox),
          ],
        ),
      ),);
  }
 //構(gòu)建文本輸入框
  Widget get _inputBox {
    return Expanded(
      child: TextField(
        style: const TextStyle(
            fontSize: 18.0, color: Colors.black, fontWeight: FontWeight.w300),
        decoration: InputDecoration(
//                   contentPadding: EdgeInsets.fromLTRB(1, 3, 1, 3),
//                   contentPadding: EdgeInsets.only(bottom: 0),
            contentPadding:
                const EdgeInsets.symmetric(vertical: 0, horizontal: 12),
            border: InputBorder.none,
            hintText: widget.hint,
            hintStyle: TextStyle(fontSize: 15),
            enabledBorder: const OutlineInputBorder(
              // borderSide: BorderSide(color: Color(0xFFDCDFE6)),
              borderSide: BorderSide(color: Colors.transparent),
              borderRadius: BorderRadius.all(Radius.circular(4.0)),
            ),
            focusedBorder: const OutlineInputBorder(
                borderRadius: BorderRadius.all(Radius.circular(8)),
                borderSide: BorderSide(color: Colors.transparent))),
      ),
    );
    ;
  }
}

通常一個應(yīng)該會出現(xiàn)多出輸入框,但是每個地方的輸入框樣式和按鈕功能類型會有一定的區(qū)別,可以通過初始化傳參的方式進行區(qū)分。如上面事例中enum SearchBarType { home, normal, homeLight }枚舉每個功能頁面出現(xiàn)SearchBar的樣式和響應(yīng)事件。

2 構(gòu)建導(dǎo)航頭部TabBar

//導(dǎo)航tabar 關(guān)注 頭條 新車 ,,。
_buildTabBar() {
  return TabBar(
      controller: _controller,
      isScrollable: true,//是否可滾動
      labelColor: Colors.black,//文字顏色
      labelPadding: const EdgeInsets.fromLTRB(20, 0, 10, 5),
      //下劃線樣式設(shè)置
      indicator: const UnderlineTabIndicator(
        borderSide: BorderSide(color: Color(0xff2fcfbb), width: 3),
        insets: EdgeInsets.fromLTRB(0, 0, 0, 10),
      ),
      tabs: tabs.map<Tab>((HomeChannelModel model) {
        return Tab(
          text: model.name,
        );
      }).toList());
}

因為Tabbar需要和TabBarView進行聯(lián)動,需要定義一個TabController進行綁定

3 構(gòu)建導(dǎo)航底部TabBarView容器

//TabBarView容器 信息流列表
_buildTabBarPageView() {
  return KeepAliveWrapper(child:Expanded(
      flex: 1,
      child: Container(
        color: Colors.grey.withOpacity(0.3),
        child: TabBarView(
          controller: _controller,
          children: _buildItems(),
        ),
      )));
}

4 構(gòu)建導(dǎo)航底部結(jié)構(gòu)填充

底部內(nèi)容結(jié)構(gòu)包含輪播圖左右切換,信息流上下滾動,下拉刷新,上拉加載更多、刷新組件用到SmartRefresher,輪播圖和信息流需要拼接,需要用CustomScrollView。

代碼如下:

_buildRefreshView() {
  //刷新組件
  return SmartRefresher(
    controller: _refreshController,
    enablePullDown: true,
    enablePullUp: true,
    onLoading: () async {
      page++;
      print('onLoading $page');
      //加載頻道數(shù)據(jù)
      widget.homeChannelModel.termId == 0 ? _getTTHomeNews() : _getHomeNews();
    },
    onRefresh: () async {
      page = 1;
      print('onRefresh $page');
      //加載頻道數(shù)據(jù)
      widget.homeChannelModel.termId == 0 ? _getTTHomeNews() : _getHomeNews();
    },
    //下拉頭部UI樣式
    header: const WaterDropHeader(
      idleIcon: Icon(
        Icons.car_repair,
        color: Colors.blue,
        size: 30,
      ),
    ),
    //上拉底部UI樣式
    footer: CustomFooter(
      builder: (BuildContext context, LoadStatus? mode) {
        Widget body;
        if (mode == LoadStatus.idle) {
          body = const Text("pull up load");
        } else if (mode == LoadStatus.loading) {
          body = const CupertinoActivityIndicator();
        } else if (mode == LoadStatus.failed) {
          body = const Text("Load Failed!Click retry!");
        } else if (mode == LoadStatus.canLoading) {
          body = const Text("release to load more");
        } else {
          body = const Text("No more Data");
        }
        return Container(
          height: 55.0,
          child: Center(child: body),
        );
      },
    ),
    //customScrollview拼接輪播圖和信息流。
    child: CustomScrollView(
      slivers: [
        SliverToBoxAdapter(
                child: _buildFutureScroll()
              ),
        SliverList(
          delegate: SliverChildBuilderDelegate((content, index) {
            NewsModel newsModel = newsList[index];
            return _buildChannelItems(newsModel);
          }, childCount: newsList.length),
        )
      ],
    ),
  );
}

5 構(gòu)建導(dǎo)航底部結(jié)構(gòu)輪播圖

輪播圖單獨封裝SwiperView小組件

//首頁焦點輪播圖數(shù)據(jù)獲取
_buildFutureScroll(){
  return FutureBuilder(
      future: _getHomeFocus(),
      builder: (BuildContext context, AsyncSnapshot&lt;FocusDataModel&gt; snapshot){
        print('輪播圖數(shù)據(jù)加載 ${snapshot.connectionState} 對應(yīng)數(shù)據(jù):${snapshot.data}');
        Container widget;
        switch(snapshot.connectionState){
          case ConnectionState.done:
            if(snapshot.data != null){
              widget = snapshot.data!.focusList!.isNotEmpty?Container(
                height: 200,
                width: MediaQuery.of(context).size.width,
                child: SwiperView(snapshot.data!.focusList!,
                    MediaQuery.of(context).size.width),
              ):Container();
            }else{
              widget = Container();
            }
            break;
          case ConnectionState.waiting:
            widget = Container();
            break;
          case ConnectionState.none:
            widget = Container();
            break;
          default :
            widget = Container();
            break;
        }
        return widget;
      });
}

輪播圖組件封裝,整體基于第三方flutter_swiper_tv

import "package:flutter/material.dart";
import 'package:flutter_swiper_tv/flutter_swiper.dart';
import 'package:qctt_flutter/http/api.dart';
import 'package:qctt_flutter/models/home_channel.dart';
import 'package:qctt_flutter/models/home_focus_model.dart';
class SwiperView extends StatelessWidget {
  // const SwiperView({Key? key}) : super(key: key);
  final double width;
  final List<FocusItemModel> items;
  const SwiperView(this.items,this.width,{Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Swiper(
      itemCount: items.length,
      itemWidth: width,
      containerWidth: width,
      itemBuilder: (BuildContext context,int index){
        FocusItemModel focusItemModel = items[index];
        return Stack(children: [
          Container(child:Image.network(focusItemModel.picUrlList![0],fit: BoxFit.fitWidth,width: width,))
        ],
        );
      },
      pagination: const SwiperPagination(),
      // control: const SwiperControl(),
    );
  }
}

6 構(gòu)建導(dǎo)航底部結(jié)構(gòu)信息流

信息流比較多,每條信息流樣式各一,具體要根據(jù)服務(wù)端返回的數(shù)據(jù)進行判定。如本項目不至于22種樣式,

  _buildChannelItems(NewsModel model) {
    //0,無圖,1單張小圖 3、三張小圖 4.大圖推廣 5.小圖推廣 6.專題(統(tǒng)一大圖)
// 8.視頻小圖,9.視頻大圖 ,,11.banner廣告,12.車展,
// 14、視頻直播 15、直播回放 16、微頭條無圖 17、微頭條一圖
// 18、微頭條二圖以上 19分組小視頻 20單個小視頻 22 文章折疊卡片(關(guān)注頻道)
    switch (model.style) {
      case '1':
        return GestureDetector(
          child: OnePicArticleView(model),
          onTap: ()=>_jumpToPage(model),
        );
      case '3':
        return GestureDetector(
          child: ThreePicArticleView(model),
          onTap: ()=>_jumpToPage(model),
        );
      case '4':
        return GestureDetector(
          child: AdBigPicView(newsModel: model,),
            onTap: ()=>_jumpToPage(model),) ;
      case '9':
        return GestureDetector(
          child: Container(
          padding: const EdgeInsets.only(left: 10, right: 10),
          child: VideoBigPicView(model),
        ),
        onTap: ()=>_jumpToPage(model),
        );
      case '15':
        return GestureDetector(
          child: Container(
            width: double.infinity,
            padding: const EdgeInsets.only(left: 10, right: 10),
            child: LiveItemView(model),
          ),
          onTap: ()=>_jumpToPage(model),
        );
      case '16'://16、微頭條無圖
        return GestureDetector(
          child: Container(
            width: double.infinity,
            padding: const EdgeInsets.only(left: 10, right: 10),
            child: WTTImageView(model),
          ),
          onTap: ()=>_jumpToPage(model),
        );
      case '17'://17、微頭條一圖
        return GestureDetector(
          child: Container(
            width: double.infinity,
            padding: const EdgeInsets.only(left: 10, right: 10),
            child: WTTImageView(model),
          ),
          onTap:()=> _jumpToPage(model),
        );
      case '18'://18、微頭條二圖以上
        //18、微頭條二圖以上
        return GestureDetector(
          child: Container(
            width: double.infinity,
            padding: const EdgeInsets.only(left: 10, right: 10),
            child: WTTImageView(model),
          ),
          onTap: ()=>_jumpToPage(model),
        );
      case '19': //19分組小視頻
        return Container(
          width: double.infinity,
          padding: const EdgeInsets.only(left: 10, right: 10),
          child: SmallVideoGroupView(model.videoList),
        );
      case '20':
      //20小視頻 左上方帶有藍色小視頻標(biāo)記
        return Container(
          padding: const EdgeInsets.only(left: 10, right: 10),
          child: VideoBigPicView(model),
        );
      default:
        return Container(
          height: 20,
          color: Colors.blue,
        );
    }
  }

每種樣式需要單獨封裝Cell組件視圖。

通過_buildChannelItems(NewsModel model)方法返回的是單獨的Cell視圖,需要提交給對應(yīng)的list進行組裝:

SliverList(
  delegate: SliverChildBuilderDelegate((content, index) {
    NewsModel newsModel = newsList[index];
    return _buildChannelItems(newsModel);
  }, childCount: newsList.length),
)

這樣整個App首頁的大體結(jié)構(gòu)就完成了,包含App頂部搜索,基于Tabbar的頭部頻道導(dǎo)航。TabbarView頭部導(dǎo)航聯(lián)動。CustomScrollView對輪播圖信息流進行拼接,等。網(wǎng)絡(luò)數(shù)據(jù)是基于Dio進行了簡單封裝,具體不在這里細說。具體接口涉及隱私,不展示。

至于底部BottomNavigationBar會在后續(xù)組件介紹的時候詳細介紹到。

總結(jié)

本章主要介紹了TabBarView的基本用法以及實際復(fù)雜項目中TabBarView的組合使用場景,更多關(guān)于Flutter TabBarView組件的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解Swift 之clipped是什么如何用

    詳解Swift 之clipped是什么如何用

    這篇文章主要介紹了詳解Swift 之clipped是什么如何用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • iOS10適配之權(quán)限Crash問題的完美解決方案

    iOS10適配之權(quán)限Crash問題的完美解決方案

    這篇文章主要為大家詳細介紹了iOS10適配之權(quán)限Crash問題的完美解決方案,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • 史上最詳細的CocoaPods安裝教程(圖文)

    史上最詳細的CocoaPods安裝教程(圖文)

    雖然網(wǎng)上關(guān)于CocoaPods安裝教程多不勝數(shù),但是我在安裝的過程中還是出現(xiàn)了很多錯誤,所以大家可以照下來步驟裝一下,我相信會很好用
    2016-09-09
  • iOS開發(fā)之UIKeyboardTypeNumberPad數(shù)字鍵盤自定義按鍵

    iOS開發(fā)之UIKeyboardTypeNumberPad數(shù)字鍵盤自定義按鍵

    這篇文章主要介紹了iOS開發(fā)之UIKeyboardTypeNumberPad數(shù)字鍵盤自定義按鍵 的相關(guān)資料,需要的朋友可以參考下
    2016-08-08
  • IOS登錄頁面動畫、轉(zhuǎn)場動畫開發(fā)詳解

    IOS登錄頁面動畫、轉(zhuǎn)場動畫開發(fā)詳解

    本篇文章通過詳細的步驟給大家詳細講述了IOS登錄頁面動畫、轉(zhuǎn)場動畫開發(fā)的詳細教程,有興趣的朋友參考學(xué)習(xí)下。
    2018-01-01
  • 將多個字符串高亮顯示之TTTAttributedLabel

    將多個字符串高亮顯示之TTTAttributedLabel

    本文介紹了將多個字符串高亮顯示之TTTAttributedLabel。在此需要對每個字符串進行匹配,可以研究下kmp和bm算法,在這里應(yīng)用了oc自帶的NSRegularExpression 來進行正則表達式匹配,算是比較簡單的方法,需要的朋友可以參考下
    2015-07-07
  • iOS逆向教程之logify跟蹤方法的調(diào)用

    iOS逆向教程之logify跟蹤方法的調(diào)用

    這篇文章主要給大家介紹了關(guān)于iOS逆向教程之logify跟蹤方法調(diào)用的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-06-06
  • iOS如何獲取設(shè)備型號的最新方法總結(jié)

    iOS如何獲取設(shè)備型號的最新方法總結(jié)

    在開發(fā)中,我們經(jīng)常需要獲取設(shè)備的型號以進行數(shù)據(jù)統(tǒng)計或者做不同的適配。這篇文章主要給大家介紹了關(guān)于iOS如何獲取設(shè)備型號的最新方法,需要的朋友可以參考下
    2018-11-11
  • iOS評分(評價)星星圖打分功能

    iOS評分(評價)星星圖打分功能

    這篇文章主要介紹了iOS評分(評價)星星圖打分功能,評分視圖分為展示和評分兩種,具體詳情大家可以通過本文詳細學(xué)習(xí)
    2016-11-11
  • 解決iOS11圖片下拉放大出現(xiàn)信號欄白條的bug問題

    解決iOS11圖片下拉放大出現(xiàn)信號欄白條的bug問題

    這篇文章主要介紹了iOS11圖片下拉放大出現(xiàn)信號欄白條的bug問題,需要的朋友參考下吧
    2017-09-09

最新評論