Flutter環境在好久以前就搭建好了,遲遲沒有體驗一把,發佈會稱Flutter將要一統天下,是時候體驗一把Flutter了,仍是藉助玩安卓api接口來練手。android
大體就分爲 首頁、體系、熱搜、項目、個人、五大板塊。git
主入口很簡單,幾句代碼github
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(),
);
}
}
複製代碼
debugShowCheckedModeBanner: false, //去掉頁面右上角的debug標識
複製代碼
以上代碼中home後面的MainPage()纔是真正的主頁,咱們來瞧一瞧web
頁面下方的五個TAB添加方式json
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;
});
},
),
複製代碼
順便說下每一個頁面的狀態保存方式api
class ListPageState extends State<ListPage> with AutomaticKeepAliveClientMixin
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
複製代碼
先實現AutomaticKeepAliveClientMixin而後重寫@override bool get wantKeepAlive => true這個方法bash
數據請求這塊,我用的是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);
複製代碼
數據解析用的是 json_serializable: ^2.0.0 而後配合android studio 工具自動生成bean對象 app
刷新這塊用的是 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(
));
});
}
複製代碼
解決: 從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>
複製代碼
解決: 外面套一層
GestureDetector
onTap: () {
Navigator.push(context,new MaterialPageRoute(
builder: (context) =>
new DetailsPage(item.link, item.name)));
});
複製代碼
packages: pub.flutter-io.cn/flutter
#源碼地址