Flutter TabBar+TabBarView实战DEMO

理论上适用于大部分标签页+列表切换效果,唯一的区别就是实体类接口这块的逻辑,按照自己的项目改一改,然后把listview的item布局按照自己需求改一改,改吧改吧自己就能用了
效果图如下:
Flutter TabBar+TabBarView实战DEMO_第1张图片

底下的每个TabBarView都做了缓存,切换的时候第一次需要加载,之后再切换回来很流畅。现在粘贴代码:
main.dart:

import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app01/mainpage.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'TypeModel.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
        // This makes the visual density adapt to the platform that you run
        // the app on. For desktop platforms, the controls will be smaller and
        // closer together (more dense) than on mobile platforms.
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;


  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  MyHomePage({Key key, this.title}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin{
  TypeModel typeModel;



  RefreshController _refreshController =
      RefreshController(initialRefresh: false);

  TabController _tabController;
  List<Data> data;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, length: data?.length ?? 0);

    this.getHttp();
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {


    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
   return Scaffold(
     appBar: AppBar(
       title: Text(widget.title),
     ),
     body: Center(
       child:Column(
         children: <Widget>[
           TabBar(
             onTap: (tab) {
               print(tab);
             },
             labelStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
             unselectedLabelStyle: TextStyle(fontSize: 16),
             isScrollable: true,
             controller: _tabController,
             labelColor: Colors.blue,
             indicatorWeight: 3,
             indicatorPadding: EdgeInsets.symmetric(horizontal: 10),
             unselectedLabelColor: Colors.grey,
             indicatorColor: Colors.orangeAccent,
             tabs: data?.map((e) => Tab(text: e.title))?.toList()??[],
           ),
           Expanded(child: Container(
               width: MediaQuery.of(context).size.width,
               child: buildTabBarView()) )

         ],
       ) ,
     ),
   );
  }
  Widget buildTabBarView()=>TabBarView(
    controller: _tabController,
    children: data?.map((e) => Center(
      child:A(e.id)
    ))?.toList()??[],
  );
  void getHttp() async {
    try {
      Response response = await Dio().get("https://api.sunofbeach.net/shop/discovery/categories");
      typeModel= TypeModel.fromJson(response.data);
      data=typeModel.data;
      setState(() {
        _tabController = TabController(vsync: this, length: data?.length);
        _tabController.addListener(() {

        });
      });

    } catch (e) {
      print(e);
    }
  }





}

mainpage.dart,这个是底下列表的界面,类似android的fragment:

import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app01/web_detail.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';

import 'TypeListModel.dart';

class A extends StatefulWidget {
  int id;

  A(this.id);

  @override
  _AState createState() => _AState(id);
}

class _AState extends State<A> with AutomaticKeepAliveClientMixin {
  RefreshController _refreshController =
  RefreshController(initialRefresh: false);

  TypeListModel typeListModel;
  List<DataListBean> listData;
  int id;
  bool isLoading=true;
  int page=1;
  _AState(this.id);

  @override
  void initState() {
    super.initState();
    getListData(id.toString(),true);
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      body: Center(
        child: ModalProgressHUD(child: buildRefreshContainer(),inAsyncCall:isLoading),
      ),

    );
  }
  void _onRefresh() async{
      page=1;
      getListData(id.toString(),true);
  }

  void _onLoading() async{
    page++;
    getListData(id.toString(),false);
  }

  Widget buildRefreshContainer()=>SmartRefresher(
    enablePullDown: true,
    enablePullUp: true,
    header: WaterDropHeader(),
    footer: CustomFooter(
      builder: (BuildContext context,LoadStatus mode){
        Widget body ;
        if(mode==LoadStatus.idle){
          body =  Text("pull up load");
        }
        else if(mode==LoadStatus.loading){
          body =  CupertinoActivityIndicator();
        }
        else if(mode == LoadStatus.failed){
          body = Text("Load Failed!Click retry!");
        }
        else if(mode == LoadStatus.canLoading){
          body = Text("release to load more");
        }
        else{
          body = Text("No more Data");
        }
        return Container(
          height: 55.0,
          child: Center(child:body),
        );
      },
    ),
    controller: _refreshController,
    onRefresh: _onRefresh,
    onLoading: _onLoading,
    child: buildListView(),
  );

  Widget buildListView()=>ListView.separated(
      itemCount: listData?.length??0,
      separatorBuilder: (context,index){
        return Divider(
          height: 0.5,
          indent: 10,
          endIndent: 10,
          color: Color(0xFFDDDDDD),
        );
      },
      itemBuilder: (BuildContext context, int index) {
        return GestureDetector(
          child:new Container(
            height: 80,
            padding:
            EdgeInsets.only(left: 10, right: 10, top: 5, bottom: 5),

            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,

              children: <Widget>[
                Expanded(
                    child: Column(
                      children: <Widget>[
                        Text(
                          listData[index].title,
                          style: TextStyle(
                            fontSize: 16,
                            color: Colors.black,
                            fontWeight: FontWeight.bold,
                          ),
                          maxLines: 2,
                          overflow: TextOverflow.ellipsis,
                        ),
                        Expanded(child: Row(
                          crossAxisAlignment: CrossAxisAlignment.end,
                          children: <Widget>[
                            Text(
                              this.getStartTime(listData[index].couponStartTime).toString(),
                              style: TextStyle(fontSize: 14, color: Colors.grey),
                            )
                          ],
                        )
                        ),

                      ],
                      crossAxisAlignment: CrossAxisAlignment.start,
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,

                    )
                ),
                Image.network(
                  "http:"+listData[index].pictUrl ,
                  height: 70,
                  width: 90,
                  fit: BoxFit.cover,
                )
              ],
            ),
          ) ,
          onTap: (){
            String url=this.listData[index].clickUrl;
            Navigator.push(context, MaterialPageRoute(builder: (_) {
              return new WebDetail(url);
            }));
          },
        ) ;


      });

  @override
  bool get wantKeepAlive => true;


  DateTime getStartTime(String startTime){
    var date1= DateTime.fromMillisecondsSinceEpoch(int.parse(startTime??"0"));
    return date1;
  }

  void getListData(String materialId,bool isRefresh) async{
    try {
      Response response = await Dio().get("https://api.sunofbeach.net/shop/discovery/"+materialId+"/"+page.toString());
      typeListModel= TypeListModel.fromJson(response.data);

      if(isRefresh){
        _refreshController.refreshCompleted();
        listData=typeListModel.data;
      }else{
        _refreshController.loadComplete();
        listData..addAll(typeListModel.data);
      }
      setState(() {
        isLoading=false;
      });

      print(response);
    } catch (e) {
      print(e);
    }
  }

}

下面是两个model代码,一个是,上面分类标签的实体类,一个是下面列表的实体类
TypeModel.dart:


class TypeModel {
 bool success;
 int code;
 String message;
 List<Data> data;

 TypeModel({this.success, this.code, this.message, this.data});

 TypeModel.fromJson(Map<String, dynamic> json) {
  success = json['success'];
  code = json['code'];
  message = json['message'];
  if (json['data'] != null) {
   data = new List<Data>();
   json['data'].forEach((v) {
    data.add(new Data.fromJson(v));
   });
  }
 }

 Map<String, dynamic> toJson() {
  final Map<String, dynamic> data = new Map<String, dynamic>();
  data['success'] = this.success;
  data['code'] = this.code;
  data['message'] = this.message;
  if (this.data != null) {
   data['data'] = this.data.map((v) => v.toJson()).toList();
  }
  return data;
 }
}

class Data {
 int id;
 String title;

 Data({this.id, this.title});

 Data.fromJson(Map<String, dynamic> json) {
  id = json['id'];
  title = json['title'];
 }

 Map<String, dynamic> toJson() {
  final Map<String, dynamic> data = new Map<String, dynamic>();
  data['id'] = this.id;
  data['title'] = this.title;
  return data;
 }
}

TypeListModel.dart:

class TypeListModel {
  String message;
  bool success;
  int code;
  List<DataListBean> data;

  TypeListModel({this.message, this.success, this.code, this.data});

  TypeListModel.fromJson(Map<String, dynamic> json) {    
    this.message = json['message'];
    this.success = json['success'];
    this.code = json['code'];
    this.data = (json['data'] as List)!=null?(json['data'] as List).map((i) => DataListBean.fromJson(i)).toList():null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['message'] = this.message;
    data['success'] = this.success;
    data['code'] = this.code;
    data['data'] = this.data != null?this.data.map((i) => i.toJson()).toList():null;
    return data;
  }

}

class DataListBean {
  String categoryName;
  String clickUrl;
  String commissionRate;
  String couponClickUrl;
  String couponEndTime;
  String couponInfo;
  String couponShareUrl;
  String couponStartFee;
  String couponStartTime;
  String itemDescription;
  String levelOneCategoryName;
  String nick;
  String pictUrl;
  String shopTitle;
  String title;
  String zkFinalPrice;
  int categoryId;
  int couponAmount;
  int couponRemainCount;
  int couponTotalCount;
  int levelOneCategoryId;
  int sellerId;
  int userType;
  int volume;
  num itemId;
  SmallImagesBean smallImages;

  DataListBean({this.categoryName, this.clickUrl, this.commissionRate, this.couponClickUrl, this.couponEndTime, this.couponInfo, this.couponShareUrl, this.couponStartFee, this.couponStartTime, this.itemDescription, this.levelOneCategoryName, this.nick, this.pictUrl, this.shopTitle, this.title, this.zkFinalPrice, this.categoryId, this.couponAmount, this.couponRemainCount, this.couponTotalCount, this.levelOneCategoryId, this.sellerId, this.userType, this.volume, this.itemId, this.smallImages});

  DataListBean.fromJson(Map<String, dynamic> json) {    
    this.categoryName = json['category_name'];
    this.clickUrl = json['click_url'];
    this.commissionRate = json['commission_rate'];
    this.couponClickUrl = json['coupon_click_url'];
    this.couponEndTime = json['coupon_end_time'];
    this.couponInfo = json['coupon_info'];
    this.couponShareUrl = json['coupon_share_url'];
    this.couponStartFee = json['coupon_start_fee'];
    this.couponStartTime = json['coupon_start_time'];
    this.itemDescription = json['item_description'];
    this.levelOneCategoryName = json['level_one_category_name'];
    this.nick = json['nick'];
    this.pictUrl = json['pict_url'];
    this.shopTitle = json['shop_title'];
    this.title = json['title'];
    this.zkFinalPrice = json['zk_final_price'];
    this.categoryId = json['category_id'];
    this.couponAmount = json['coupon_amount'];
    this.couponRemainCount = json['coupon_remain_count'];
    this.couponTotalCount = json['coupon_total_count'];
    this.levelOneCategoryId = json['level_one_category_id'];
    this.sellerId = json['seller_id'];
    this.userType = json['user_type'];
    this.volume = json['volume'];
    this.itemId = json['item_id'];
    this.smallImages = json['small_images'] != null ? SmallImagesBean.fromJson(json['small_images']) : null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['category_name'] = this.categoryName;
    data['click_url'] = this.clickUrl;
    data['commission_rate'] = this.commissionRate;
    data['coupon_click_url'] = this.couponClickUrl;
    data['coupon_end_time'] = this.couponEndTime;
    data['coupon_info'] = this.couponInfo;
    data['coupon_share_url'] = this.couponShareUrl;
    data['coupon_start_fee'] = this.couponStartFee;
    data['coupon_start_time'] = this.couponStartTime;
    data['item_description'] = this.itemDescription;
    data['level_one_category_name'] = this.levelOneCategoryName;
    data['nick'] = this.nick;
    data['pict_url'] = this.pictUrl;
    data['shop_title'] = this.shopTitle;
    data['title'] = this.title;
    data['zk_final_price'] = this.zkFinalPrice;
    data['category_id'] = this.categoryId;
    data['coupon_amount'] = this.couponAmount;
    data['coupon_remain_count'] = this.couponRemainCount;
    data['coupon_total_count'] = this.couponTotalCount;
    data['level_one_category_id'] = this.levelOneCategoryId;
    data['seller_id'] = this.sellerId;
    data['user_type'] = this.userType;
    data['volume'] = this.volume;
    data['item_id'] = this.itemId;
    if (this.smallImages != null) {
      data['small_images'] = this.smallImages.toJson();
    }
    return data;
  }
}

class SmallImagesBean {
  List<String> string;

  SmallImagesBean({this.string});

  SmallImagesBean.fromJson(Map<String, dynamic> json) {    

    List<dynamic> stringList = json['string'];
    this.string = new List();
    this.string.addAll(stringList.map((o) => o.toString()));
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['string'] = this.string;
    return data;
  }
}

附上这个DEMO在pubspec.yaml需要依赖的库:

  pull_to_refresh: ^1.6.3
  dio: 3.0.10
  json_serializable: 3.5.0
  json_annotation: 3.1.1
  modal_progress_hud: 0.1.3

你可能感兴趣的:(Flutter,Flutter,TabBar,TabBarView)