先上图吧,就是so easy!当你能创建一个最小APP单元后,你就跨过了flutter门槛了,剩下的就逐渐完善APP功能了,相信任何其他复杂功能也只不过需要慢慢积累而已。

首先我们创建首页三个Tab切换的页面结构,这用到了flutter组件BottomNavigationBar,先上一个主页全部的代码,方便您进行代码的编译运行。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import './tabs/Home.dart';
import './tabs/Movie.dart';
import './tabs/Personal.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage>{
int _index = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("豆瓣电影"),
),
body: IndexedStack(
index: _index,
children: <Widget>[
HomePage(),
MoviePage(),
PersonalPage(),
],
),
drawer: mDrawerLayout,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _index,
onTap: (index){
setState(() {
this._index = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text("首页")),
BottomNavigationBarItem(
icon: Icon(Icons.movie_filter), title: Text("正在热映")),
BottomNavigationBarItem(
icon: Icon(Icons.movie_creation), title: Text("Top250")),
],
));
}
}
Drawer mDrawerLayout = Drawer(
child: ListView(
padding: EdgeInsets.all(0), // 干掉侧边栏灰色状态栏
children: <Widget>[
UserAccountsDrawerHeader(
accountEmail: Text("123456789@qq.com"),
accountName: Text("pickahu"),
currentAccountPicture: CircleAvatar(
backgroundImage: NetworkImage(
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2F52%2Fc9%2F24%2F52c924bd5db63b23aeb4ebb41de4192e.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1656040583&t=1d217c27eb386c97fd96d53224cff174"),
),
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(
"https://img0.baidu.com/it/u=380810917,3888082992&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=600",
))),
),
ListTile(
title: Text(
"用户反馈",
style: TextStyle(fontSize: 16, color: Colors.black54),
),
trailing: Icon(Icons.feedback),
),
Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: Divider(
color: Colors.grey,
),
),
ListTile(
title: Text("系统设置"),
trailing: Icon(Icons.settings),
),
Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: Divider(
color: Colors.grey,
),
),
ListTile(
title: Text("我要发布"),
trailing: Icon(Icons.publish),
),
Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: Divider(
color: Colors.grey,
),
),
ListTile(
title: Text("注销"),
trailing: Icon(Icons.exit_to_app),
),
],
),
);
OK,看完了全部代码,我们拆分来看,首先需要一个BottomNavigationBar配合三个子页面,这一部分代码很简单
class _MyHomePageState extends State<MyHomePage>{
int _index = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("豆瓣电影"),
),
body: IndexedStack(
index: _index,
children: <Widget>[
HomePage(),
MoviePage(),
PersonalPage(),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _index,
onTap: (index){
setState(() {
this._index = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text("首页")),
BottomNavigationBarItem(
icon: Icon(Icons.movie_filter), title: Text("正在热映")),
BottomNavigationBarItem(
icon: Icon(Icons.movie_creation), title: Text("Top250")),
],
));
}
}
之所以用StatefulWidget 组件是因为我们要进行页面切换并保持,我们通过setState函数进行状态更新,这里用IndexedStack容纳三个页面的原因是flutter页面状态保持需要,后续会提到这一点。
而这个从左边弹出的状态栏则是scaffold的一个属性决定的,就是drawer,而真正的View就是我们的mDrawerLayout,一个控件加上基本的布局,没啥好说的。
然后我们首页页面,首页页面就是一个类似于新闻列表页,有刷新有加载更多,有网络获取数据,数据解析,是一个APP不可获取的组成部分。先上代码:
import 'package:douban_flutter_demo/Detail.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
Dio dio = new Dio();
class _HomePageState extends State<HomePage>
with AutomaticKeepAliveClientMixin {
int page = 1;
int pagesize = 10;
var mList = [];
var total = 0;
ScrollController _scrollController = new ScrollController();
@override
void initState() {
// TODO: implement initState
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
getMovieList(false);
}
});
super.initState();
getMovieList(true);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
onRefresh: _pullToRefresh,
child: ListView.builder(
controller: _scrollController,
itemCount: mList.length,
itemBuilder: (BuildContext context, int index) {
var item = mList[index];
return ItemHolder(item);
}),
),
);
}
Future _pullToRefresh() async {
getMovieList(true);
return null;
}
@override
void dispose() {
super.dispose();
// 释放资源
_scrollController.dispose();
}
getMovieList(bool refresh) async {
if (refresh) {
page = 1;
} else {
page++;
}
var response = await dio.get(
"http://api.tianapi.com/film/index?key=你的key(可以去天行API申请)&num=10&page=$page");
setState(() {
if (refresh) {
this.mList.clear();
this.mList.addAll(response.data["newslist"]);
} else {
this.mList.addAll(response.data["newslist"]);
}
});
print(mList);
}
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
}
class ItemHolder extends StatelessWidget {
var itemData = null;
ItemHolder(this.itemData);
@override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
print("aaaaa");
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return new MovieDetail(itemData["id"], itemData["title"]);
}));
},
child: Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
child: Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(10),
child: Image.network(
itemData["picUrl"],
width: 80,
height: 110,
fit: BoxFit.cover,
),
),
Expanded(
//增加Expanded包裹,为了约束text太长溢出,测试溢出作用控件可以给控件增加宽高限制进行测试
child: Container(
height: 110,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(
itemData["title"],
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyle(fontSize: 16),
),
Text(
itemData["description"],
overflow: TextOverflow.clip,
maxLines: 2,
softWrap: true,
style: TextStyle(
fontSize: 14,
),
),
Text(
itemData["source"],
softWrap: true,
overflow: TextOverflow.clip,
),
Text(
itemData["ctime"],
maxLines: 1,
softWrap: true,
overflow: TextOverflow.clip,
)
],
),
),
),
],
),
),
);
}
}
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _HomePageState();
}
}
最基础的组件是ListView,ListView的每一个子View通过ItemHolder构建,数据存储在mList中,我们写了一个getMovieList方法,可以获取刷新数据也可以获取加载更多数据,这是一个异步的方法,需要async 修饰,数据获取简单得通过Dio获取,这里有一个特别的地方就是页面 的状态保持,我们通过_HomePageState with AutomaticKeepAliveClientMixin来实现,然后我们复写wantKeepAlive 方法并且返回true,状态保持就是当我们在列表页滑到一个位置的时候切换去别的页面,再切换回来时候我们原来的列表页可以保持在原来的位置,其实就是状态不变,页面也不重绘。下拉刷新采用RefreshIndicator包裹listView实现,刷新回调方法需要返回Feature对象,然后我们给listview加上_scrollController对象来判断上拉加载更多的触发时机。。。
这里只得一说的是我们的列表每一个子View文字部分我们通过Expanded包裹,又嵌套一层Container,目的是为了解决文字超过边界预警的问题。
好了,最后我们通过一段路由
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return new MovieDetail(itemData["id"], itemData["title"]);
将id和标题传递到电影详情页面,没啥好说的,最后电影详情页展示出了id和title,说明传递成功,后边具体的数据可以通过id去获取,不加累述。一段详情页的代码,so easy!
import 'package:flutter/material.dart';
class MoviePage extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("热映"),
),
);
}
最后两个页面就随便搞个View就搞定啦,本篇文档到此结束啦。
网友评论