Flutter環境在很久之前就搭建好了,遲遲沒有體驗一把,釋出會稱Flutter將要一統天下,是時候體驗一把Flutter了,還是藉助玩安卓api介面來練手。
先上圖吧
大致就分為 首頁、體系、熱搜、專案、我的、五大板塊。
一、頁面
主入口很簡單,幾句程式碼
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '玩安卓',
debugShowCheckedModeBanner: false, //去掉頁面右上角的debug標識
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MainPage(),
);
}
}
複製程式碼
很多時候寫完頁面右上角還有個debug標識,新增上以下程式碼就可以去掉。
debugShowCheckedModeBanner: false, //去掉頁面右上角的debug標識
複製程式碼
以上程式碼中home後面的MainPage()才是真正的主頁,我們來瞧一瞧
頁面下方的五個TAB新增方式
bottomNavigationBar: BottomNavigationBar(
// 底部導航
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('首頁')),
new BottomNavigationBarItem(
icon: Icon(Icons.layers), title: Text('體系')),
new BottomNavigationBarItem(
icon: Icon(Icons.search), title: Text('熱搜')),
new BottomNavigationBarItem(
icon: Icon(Icons.folder), title: Text('專案')),
new BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('我的')),
],
type: BottomNavigationBarType.fixed,
currentIndex: _selectedIndex,
fixedColor: Colors.blue,
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
),
複製程式碼
順便說下每個頁面的狀態儲存方式
class ListPageState extends State<ListPage> with AutomaticKeepAliveClientMixin
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
複製程式碼
先實現AutomaticKeepAliveClientMixin然後重寫@override bool get wantKeepAlive => true這個方法
二、資料請求及解析
1.資料請求
資料請求這塊,我用的是dio 使用方法: 1.先在pubspec.yaml中新增 dio: ^2.1.2 #網路框架 2.點選編輯器右上方的Package get 3.可以愉快的玩耍了 來一個最簡單的get請求
import 'package:dio/dio.dart';
Dio dio = new Dio();
Response response=await dio.get("https://www.google.com/");
print(response.data);
複製程式碼
2.資料解析
資料解析用的是 json_serializable: ^2.0.0 然後配合android studio 工具自動生成bean物件
3.下拉重新整理上拉載入很多
重新整理這塊用的是 flutter_refresh: ^0.0.2 flutter_spinkit: "^3.1.0" 具體使用方法:
// 頂部重新整理
Future<Null> onHeaderRefresh() {
return new Future.delayed(new Duration(seconds: 2), () {
setState(() {
pageIndex = 0;
bannerList.clear();
homeList.clear();
this.getBannerList();
this.getHomeList();
});
});
}
// 底部重新整理
Future<Null> onFooterRefresh() async {
return new Future.delayed(new Duration(seconds: 2), () {
setState(() {
pageIndex += 1;
this.getHomeList();
});
});
}
Widget buildCustomScrollView() {
return new Refresh(
onFooterRefresh: onFooterRefresh,
onHeaderRefresh: onHeaderRefresh,
childBuilder: (BuildContext context,
{ScrollController controller, ScrollPhysics physics}) {
return new Container(
child: new ListView.builder(
physics: physics,
controller: controller,
itemCount: homeList.length + headerCount,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return buildBanner();
} else {
return buildList(homeList[index - headerCount]);
}
}));
});
}
複製程式碼
接下來我們看下主頁裡面某個單頁面的具體實現,就首頁吧
import 'package:banner_view/banner_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_refresh/flutter_refresh.dart';
import 'package:flutter_wananzhuo/bean/Api.dart';
import 'package:flutter_wananzhuo/bean/BannerItem.dart' as bannerItem;
import 'package:flutter_wananzhuo/bean/HomeItem.dart' as homeItem;
import 'package:flutter_wananzhuo/utils/HttpUtil.dart';
import 'package:flutter_wananzhuo/utils/NavigatorUtil.dart';
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<bannerItem.BannerData> bannerList = [];
List<homeItem.HomeItemDataData> homeList = [];
final int headerCount = 1;
int pageIndex = 0;
var bannerIndex = 0;
// final int pageSize = 20;
@override
void initState() {
super.initState();
getBannerList();
getHomeList();
}
// 頂部重新整理
Future<Null> onHeaderRefresh() {
return new Future.delayed(new Duration(seconds: 2), () {
setState(() {
pageIndex = 0;
bannerList.clear();
homeList.clear();
this.getBannerList();
this.getHomeList();
});
});
}
// 底部重新整理
Future<Null> onFooterRefresh() async {
return new Future.delayed(new Duration(seconds: 2), () {
setState(() {
pageIndex += 1;
this.getHomeList();
});
});
}
//獲取輪播圖介面
void getBannerList() async {
var response = await new HttpUtil().get(Api.BANNER_LIST);
var item = new bannerItem.BannerItem.fromJson(response);
bannerList = item.data;
setState(() {});
}
void getHomeList() async {
var response = await new HttpUtil()
.get(Api.HOME_LIST + pageIndex.toString() + "/json");
var item = new homeItem.HomeItem.fromJson(response);
if (pageIndex == 0) {
homeList = item.data.datas;
} else {
homeList.addAll(item.data.datas);
}
setState(() {});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
elevation: 0,
title: new Text("玩安卓"),
),
body: buildCustomScrollView());
}
Widget buildCustomScrollView() {
return new Refresh(
onFooterRefresh: onFooterRefresh,
onHeaderRefresh: onHeaderRefresh,
childBuilder: (BuildContext context,
{ScrollController controller, ScrollPhysics physics}) {
return new Container(
child: new ListView.builder(
physics: physics,
controller: controller,
itemCount: homeList.length + headerCount,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return buildBanner();
} else {
return buildList(homeList[index - headerCount]);
}
}));
});
}
Widget buildList(homeItem.HomeItemDataData item) {
return new Card(
child: new InkWell(
onTap: () {
NavigatorUtil.toDetails(context, item.link, item.title);
},
child: new ListTile(
title: new Row(
children: <Widget>[
new Text(item.author,
textAlign: TextAlign.left,
style: new TextStyle(color: Colors.grey, fontSize: 13)),
new Text(item.niceDate,
textAlign: TextAlign.right,
style: new TextStyle(color: Colors.grey, fontSize: 13))
],
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
),
subtitle: new Column(
children: <Widget>[
new Text(item.title,
textAlign: TextAlign.left,
style: new TextStyle(color: Colors.black, fontSize: 15)),
new Text(
item.superChapterName + "/" + item.chapterName,
style: new TextStyle(color: Colors.blue, fontSize: 13),
)
],
crossAxisAlignment: CrossAxisAlignment.start,
),
),
),
);
}
Widget buildBanner() {
return new Container(
padding: EdgeInsets.all(5),
child: bannerList.length > 0
? new BannerView(
bannerList.map((bannerItem.BannerData item) {
return new GestureDetector(
onTap: () {
NavigatorUtil.toDetails(context, item.url, item.title);
},
child: new Image.network(
item.imagePath,
fit: BoxFit.cover,
));
}).toList(),
cycleRolling: false,
autoRolling: true,
indicatorMargin: 8.0,
// indicatorNormal: this._indicatorItem(Colors.white),
// indicatorSelected:
// this._indicatorItem(Colors.white, selected: true),
// indicatorBuilder: (context, indicator) {
// return this._indicatorContainer(indicator);
// },
onPageChanged: (index) {
bannerIndex = index;
},
)
: new Container(),
width: double.infinity,
height: 200.0,
);
}
}
複製程式碼
其他頁面的實現都大同小異,可以去看原始碼
三、遇到的問題
##1.資料還沒載入出來,介面報紅色錯誤 解決: 用一個變數控制是否在載入完成,未載入完成先顯示載入頁面 舉個例子:
@override
Widget build(BuildContext context) {
return isLoading
? SpinKitCircle(
itemBuilder: (_, int index) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Colors.grey,
),
);
},
)
: new Refresh(
onFooterRefresh: onFooterRefresh,
onHeaderRefresh: onHeaderRefresh,
childBuilder: (BuildContext context,
{ScrollController controller, ScrollPhysics physics}) {
return new Container(
));
});
}
複製程式碼
2.webview控制元件載入不出有些網頁(報錯:net err_cleartext_not_permitted)
解決: 從Android 9.0(API級別28)開始,預設情況下禁用明文支援。因此http的url均無法在webview中載入 在android manifest.xml中新增
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<application
...
android:usesCleartextTraffic="true"
...>
...
</application>
</manifest>
複製程式碼
3.某些控制元件(如Container)沒有自帶的點選事件
解決: 外面套一層
GestureDetector
onTap: () {
Navigator.push(context,new MaterialPageRoute(
builder: (context) =>
new DetailsPage(item.link, item.name)));
});
複製程式碼
4.需要使用什麼庫,搜尋一下
packages: pub.flutter-io.cn/flutter