實現頂部導航欄TabBar+TabBarView切換子佈局,因爲也想寫FutureBuilder、shared_preferences、ExpansionTile、RefreshIndicat文章,索性一起寫了,頂部導航切換的四個子佈局分別是:
- FutureBuilderPage:FutureBuilder的使用;
- SharedPreferencesPage:shared_preferences本地數據的存儲相當於Android的SharedPreferences;
- ExpansionTilePage:ExpansionTile二級列表的展開收縮;
- 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網絡請求