flutter學習七:頂部導航欄TabBar切換子佈局FutureBuilder、shared_preferences、ExpansionTile、RefreshIndicat

實現頂部導航欄TabBar+TabBarView切換子佈局,因爲也想寫FutureBuilder、shared_preferences、ExpansionTile、RefreshIndicat文章,索性一起寫了,頂部導航切換的四個子佈局分別是:

  1. FutureBuilderPage:FutureBuilder的使用;
  2. SharedPreferencesPage:shared_preferences本地數據的存儲相當於Android的SharedPreferences;
  3. ExpansionTilePage:ExpansionTile二級列表的展開收縮;
  4. RefreshPage:ListView的水平、垂直佈局,再次基礎上加上RefreshIndicator下拉刷新、下拉加載更多;

效果圖如下(看頂部導航欄選中的對比前面的四點即可理解):

     

                       

             

                          

 

步驟如下:

 

一. MinePage實現頂部導航欄TabBar+TabBarView切換不同子佈局

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/pages/RefreshPage.dart';

import 'ExpansionTilePage.dart';
import 'FutureBuilderPage.dart';
import 'SharedPreferencesPage.dart';

//我的包含頂部導航欄
class MinePage extends StatefulWidget {
  _MinePageState createState() => _MinePageState();
}

class _MinePageState extends State<MinePage>
    with SingleTickerProviderStateMixin {
  TabController tabController;

  // 初始化方法
  @override
  void initState() {
    super.initState();
    tabController = TabController(length: 4, vsync: this);
  }

  // 銷燬方法
  @override
  void dispose() {
    tabController.dispose();
    super.dispose();
  }

  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        //標題居中
        centerTitle: true,
        //標題欄背景色
        backgroundColor: Colors.blue,
        //設置沒有返回按鈕
        automaticallyImplyLeading: false,
        title: TabBar(
          //選中的顏色
          labelColor: Colors.white,
          //選中的樣式
          labelStyle: TextStyle(fontSize: 15),
          //未選中的顏色
          unselectedLabelColor: Colors.black,
          //未選中的樣式
          unselectedLabelStyle: TextStyle(fontSize: 12),
          //是否可以滾動
          isScrollable: true,
          //指示器顏色
          indicatorColor: Colors.white,
          //指示器高度
          indicatorWeight: 1,
          //底部指示器的padding
          indicatorPadding: EdgeInsets.zero,
          //指示器大小計算方式,TabBarIndicatorSize.label跟文字等寬,TabBarIndicatorSize.tab跟每個tab等寬
          indicatorSize: TabBarIndicatorSize.tab,
          //每個label的padding值
          labelPadding: EdgeInsets.all(10),
          //內容
          tabs: <Widget>[
            Tab(icon: Icon(Icons.flag),text:'FutureBuilder'),
            Tab(icon: Icon(Icons.share),text:'shared_preferences'),
            Tab(icon: Icon(Icons.filter_vintage),text:'ExpansionTile'),
            Tab(icon: Icon(Icons.layers),text:'Refresh'),
          ],
          controller: tabController,
        ),
      ),
      body: Container(
        child: TabBarView(
          children: <Widget>[
            FutureBuilderPage(),
            SharedPreferencesPage(),
            ExpansionTilePage(),
            RefreshPage(),
          ],
          controller: tabController,
        ),
      ),
    );
  }
}

 

二.  FutureBuilderPage:FutureBuilder的使用(效果圖:效果圖中第一行)

1.首先得準備一個url,可以請求得到數據的

2. 具體代碼如下:

import 'dart:convert';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/bean/BaseBean.dart';
import 'package:http/http.dart' as http;

//FutureBuilder的使用
class FutureBuilderPage extends StatefulWidget {
  @override
  _FutureBuilderPageState createState() => _FutureBuilderPageState();
}

class _FutureBuilderPageState extends State<FutureBuilderPage> {
  String showResult = '';

  Future<BaseBean> getData() async {
    final response = await http.get(
        'http://192.168.0.224:8080/eolinker_os/Mock/simple?projectID=2&uri=http://www.mcl.net:8888/api/Test');
    Utf8Decoder utf8decoder = Utf8Decoder(); //中文亂碼
    var result = json.decode(utf8decoder.convert(response.bodyBytes));
    return BaseBean.from(result);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: FutureBuilder<BaseBean>(
          future: getData(),
          builder: (BuildContext context, AsyncSnapshot<BaseBean> snapshot) {
            //連接狀態
            switch (snapshot.connectionState) {
              //無連接
              case ConnectionState.none:
                return new Container(
                  alignment: Alignment.center,
                  child: Text("沒有連接"),
                );

              //等待
              case ConnectionState.waiting:
                return new Center(
                  child: new CircularProgressIndicator(),
                );

              //正在執行
              case ConnectionState.active:
                return new Container(
                  alignment: Alignment.center,
                  child: Text("正在執行ing"),
                );

              //執行完成
              case ConnectionState.done:
                //出錯了,顯示出錯信息
                if (snapshot.hasError) {
                  return new Container(
                    alignment: Alignment.center,
                    child: Text(
                      '${snapshot.error}',
                      style: TextStyle(color: Colors.red),
                    ),
                  );
                } else {
                  return new Container(
                    margin: EdgeInsets.only(top: 20),
                    alignment: Alignment.center,
                    child: Column(
                      children: <Widget>[
                        Text('status:${snapshot.data.status}'),
                        Text('msg:${snapshot.data.msg}'),
                        Text('data:${snapshot.data.data}')
                      ],
                    ),
                  );
                }
            }
          },
        ),
      ),
    );
  }
}

 

三. SharedPreferencesPage:本地數據存取簡單相當於Android中的SharedPreferences(效果圖:效果圖中第二行),存儲的key固定,存儲TextField輸入的內容,獲取key的內容,數據類型要對應上。類型有挺多,具體看api:

1.添加包:

2.代碼如下:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

//shared_preferences本地數據存取簡單、異步、持久化
class SharedPreferencesPage extends StatefulWidget {
  _SharedPreferencesPageState createState() => _SharedPreferencesPageState();
}

class _SharedPreferencesPageState extends State<SharedPreferencesPage> {
  String putStr = ''; //存的數據
  String getStr = ''; //取的數據
  String strKey = 'strKey'; //存或取的key
  //判斷輸入的內容是否爲空
  static bool _stateSp = false;

  //用於彈框消失時
  static BuildContext context1;

  //監聽輸入框的文字變化
  static TextEditingController _spController = new TextEditingController();

  //賬號輸入框樣式
  static Widget buildAccountTextFied(TextEditingController controller) {
    return TextField(
      //鍵盤的樣式
      keyboardType: TextInputType.text,
      //監聽
      controller: controller,
      //最大長度
      maxLength: 30,
      //顏色跟hintColor
      //最大行數
      maxLines: 1,
      //是否自動更正
      autocorrect: true,
      //是否自動化對焦
      autofocus: false,
      //文本對齊方式
      textAlign: TextAlign.start,
      //輸入文本的樣式
      style: TextStyle(fontSize: 20, color: Colors.black),
      decoration: InputDecoration(
        //聚焦時才顯示,顏色跟hintColor
        hintText: '請輸入存儲數據',
      ),
    );
  }

  //賬號、密碼輸入框
  Widget textSection = new Container(
    padding: const EdgeInsets.only(left: 32, right: 32),
    child: new Column(
      //主軸Flex的值
      mainAxisSize: MainAxisSize.max,
      //MainAxisAlignment:主軸方向上的對齊方式,會對child的位置起作用
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        buildAccountTextFied(_spController),
      ],
    ),
  );

  //封裝好的button控件
  Widget buttons(String name) {
    return new Container(
      alignment: Alignment.centerLeft,
      height: 50,
      margin: EdgeInsets.all(32),
      color: Colors.red,
      child: new Text(
        name,
        style: TextStyle(fontSize: 20, color: Colors.white),
      ),
    );
  }

  static Widget getDialog(String msg) {
    return new AlertDialog(
      title: new Text(
        '溫馨提示',
        style: new TextStyle(color: Colors.red[250], fontSize: 18),
      ),
      content: new Text(
        msg,
        style: new TextStyle(color: Colors.red, fontSize: 20),
      ),
      actions: <Widget>[
        new FlatButton(
            //扁平的button
            onPressed: () {
              Navigator.of(context1).pop(); //彈窗消失
            },
            child: new Text(
              '取消',
              style: new TextStyle(color: Colors.blue, fontSize: 18),
            )),
        new RaisedButton(
            //凸起的button
            onPressed: () {
              Navigator.of(context1).pop(); //彈窗消失
            },
            child: new Text(
              '確定',
              style: new TextStyle(color: Colors.blue, fontSize: 18),
            ))
      ],
    );
  }

  //校驗存儲的數據是否爲null
  static void _checkSp() {
    if (_spController.text.isNotEmpty) {
      _stateSp = true;
    } else {
      _stateSp = false;
    }
  }

  //存數據
  _putSp() async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    //把有修改的視圖重新繪製一遍
    setState(() {
      putStr = _spController.text.toString(); //獲取TextField輸入的內容
    });
    //保存String類型,還有int、Double、List<String>、Bool(動態類型)
    await sp.setString(strKey, putStr);
  }

  //獲取數據
  _getSp() async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    //把有修改的視圖重新繪製一遍
    setState(() {
      /*
      * 獲取String類型,還有int、Double、List<String>、Bool(動態類型);
      * 獲取動態類型時:sp.getBool(strKey) as String需要在後面加上“as 具體類型”
      * */
      getStr = sp.getString(strKey).toString();
      print('取出來的:' + getStr);
    });
  }

  @override
  Widget build(BuildContext context) {
    context1 = context;
    return Scaffold(
        body: Container(
      color: Colors.white,
      child: new ListView(
        children: <Widget>[
          textSection,
          RaisedButton(
              onPressed: () {
                _checkSp();
                if (_stateSp) {
                  //存儲的數據不爲null,那麼存儲
                  _putSp();
                } else {
                  //存儲數據爲null,那麼彈窗提示用戶
                  showDialog(
                    context: context1,
                    barrierDismissible: true, //點擊彈窗外部是否消失
                    child: getDialog('存儲的數據不能爲空!'),
                  );
                }
              },
              child: Text('存數據')),
          buttons('存儲的內容:' + putStr),
          RaisedButton(onPressed: _getSp, child: Text('獲取數據')),
          buttons('獲取的結果:' + getStr),
        ],
      ),
    ));
  }
}

 

四. ExpansionTilePage:可展開的二級列表(效果圖:效果圖中第三行)

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

//可展開的二級列表
class ExpansionTilePage extends StatefulWidget {
  _ExpansionTilePageState createState() => _ExpansionTilePageState();
}

class _ExpansionTilePageState extends State<ExpansionTilePage> {
  var CITY_NAME = {
    '賀州': ['1', '2', '3'],
    '桂林': ['1', '2', '3'],
    '梧州': ['1', '2', '3'],
    '廣州': ['1', '2', '3'],
  };

  List<Widget> _buildList() {
    List<Widget> widgets = [];
    //將數據裝到widget中
    CITY_NAME.keys.forEach((key) {
      widgets.add(_item(key, CITY_NAME[key]));
    });
    return widgets;
  }

  //一級的佈局,及關聯二級
  Widget _item(String city, List<String> subCities) {
    return ExpansionTile(
      title: Text(
        city,
        style: TextStyle(color: Colors.black, fontSize: 20),
      ),
      children: subCities.map((subCities) => _buildSub(subCities)).toList(),
    );
  }

  //二級的佈局
  Widget _buildSub(String subCities) {
    return FractionallySizedBox(
      widthFactor: 1,
      child: Container(
//        color: Colors.lightBlueAccent,
        height: 50,
        margin: EdgeInsets.only(bottom: 5),
        alignment: Alignment.centerLeft,
        //裝飾
        decoration: BoxDecoration(color: Colors.lightBlueAccent),
        child: Text(
          subCities,
          style: TextStyle(fontSize: 18),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: ListView(
          children: _buildList(),
        ),
      ),
    );
  }
}

 

五. RefreshPage:ListView的顯示方向垂直、水平,RefreshIndicator下拉刷新上拉加載更多:(效果圖:效果圖中第四行)

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

//上下拉刷新(不滿屏時沒有操作)
class RefreshPage extends StatefulWidget {
  _RefreshPageState createState() => _RefreshPageState();
}

class _RefreshPageState extends State<RefreshPage> {
  List<String> chainList = [
    '1',
    '2',
    '3',
    '4',
    '5',
    '6',
    '7',
    '8',
    '9',
    '10',
  ];
  ScrollController _scrollController = ScrollController();

  void initState() {
    //把滾動控制器加入到監聽裏面,滾動到大後就加載更多
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        _loadData();
      }
    });
    super.initState();
  }

  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  //上拉加載更多,延時2秒後
  _loadData() async {
    await Future.delayed(Duration(seconds: 2));
    setState(() {
      List<String> list =
          List<String>.from(chainList); //此時,list裏面複製了一份chainList
      list.addAll(chainList); //list再加一份chainList(此時list有兩份chainList)
      chainList = list; //更改chainList(總是比原先多一份)
    });
  }

  //下拉刷新時,延時2秒後執行
  Future<Null> _handleRefresh() async {
    await Future.delayed(Duration(seconds: 2));
    //把有修改的視圖重新繪製一遍,這裏chainList變了
    setState(() {
      //把chainList變成新的chainList(元素倒置第一個與最後一個對換,以此類推)
      chainList = chainList.reversed.toList();
    });
    return null;
  }

  //數據源循環每個元素
  List<Widget> getList() {
    return chainList.map((item) => getListWidget(item)).toList();
  }

  //每個子widget的樣式
  static Widget getListWidget(String str) {
    return Container(
      height: 50,
      width: 30,
      color: Colors.lightBlueAccent,
      alignment: Alignment.center,
      margin: EdgeInsets.all(10),
      child: Text(
        str,
        style: TextStyle(color: Colors.white, fontSize: 18),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RefreshIndicator(
        //下拉刷新
        onRefresh: _handleRefresh,
        child: ListView(
          //滾動的方向,可水平、垂直
          scrollDirection: Axis.vertical,
          //是否倒序顯示內容(默認false,改成true時,會從底部開始佈局的)
          reverse: false,
          //距離
          padding: EdgeInsets.all(15),
          //滑動監聽,用於上拉加載更多,監聽滑動的距離來執行相應的操作
          controller: _scrollController,
          //下拉刷新
          children: getList(),
        ),
      ),
    );
  }
}

 

到目前爲止,flutter的佈局暫時先寫到這裏,後期有新的學習,會繼續寫相關文章的,接下來學習Flutter與Android混合開發,從flutter學習一到七及沒有出現在文章的代碼,全部上傳到我的代碼雲上了。

Gitee地址:https://gitee.com/mo19940322/flutter_mo 

上一篇:flutter學習六:實現http網絡請求

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章