Flutter练手项目--玩安卓
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: https://pub.flutter-io.cn/flutter