学习Flutter自己撸的一个项目,用来显示王者荣耀英雄列表的。
项目地址:https://github.com/flywo/HeroList
首页详解
首先,我们来看首页的最终样式:
从上到下分析一下首页:
- 导航栏
- 内容
- 底部导航栏
对应到Flutter的Widget分别是:
- AppBar
- GridView
- BottomNavigationBar
由于项目需要一个AppBar与BottomNavigationBar来管理页面的跳转,所以,上面这些Widget都应该放到Flutter的Scaffold中,该Widget带了AppBar和BottomNavigationBar的属性,可以拿来直接用。
所以最终Widget树关系应该为:
MaterialApp -> Scaffold -> AppBar、GridView、BottomNavigationBar。
实现
搭建MaterialApp
由于要用到界面跳转,于是集成了第三方fluro来管理路由。在pubspec.yaml文件中设置依赖:
dio: ^2.1.0
然后下载:
flutter packages get
为了方便管理,我新建了一个router管理类,如下:
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import '../View/AppHome.dart';
import '../View/HeroInfo.dart';
import '../View/AppComponent.dart';
import '../View/HeroVideo.dart';
class Application {
static Router router;
static buildRouter() {
router = Router();
router.define('/', handler: Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> parameters) {
return AppHome();
}
));
router.define('/hero_info', handler: Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> parameters) {
int index = int.parse(parameters['heroIndex'].first);
return HeroInfo(
hero: AppComponent.heros[index],
);
}
));
router.define('/hero_info/hero_video', handler: Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> parameters) {
int index = int.parse(parameters['heroIndex'].first);
return HeroVideo(
hero: AppComponent.heros[index],
);
}
));
}
}
里面定义了一个类属性router用来管理路由,buildRouter()方法来初始化路由。
创建了一个基础Widget类AppComponent,如下:
import 'package:flutter/material.dart';
import '../Router/AppRouter.dart';
import '../Model/ArticleData.dart';
import '../Model/HeroData.dart';
class AppComponent extends StatefulWidget {
static List<ArticleData> articles;
static List<HeroData> heros;
static List<CommonSkill> commonSkills;
AppComponent({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _AppComponetState();
}
}
class _AppComponetState extends State<AppComponent> {
_AppComponetState() {
Application.buildRouter();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '王者荣耀',
theme: ThemeData(
primarySwatch: Colors.orange,
),
onGenerateRoute: Application.router.generator,
);
}
}
该类的作用是存储了数据和创建基础Widget。具体页面内容在具体的类中实现。
搭建Home
创建出Home页面内容的Widget。通过Scaffold我们依次创建出AppBar、BottomNavigationBar。
import 'package:flutter/material.dart';
import '../View/HomeContent.dart';
import '../View/ArticleContent.dart';
import '../View/CommonContent.dart';
class AppHome extends StatefulWidget {
AppHome({Key key}): super(key: key);
@override
State<StatefulWidget> createState() {
return _AppHomeState();
}
}
class _AppHomeState extends State<AppHome> {
// var _itemWidth = (MediaQueryData.fromWindow(window).size.width - 40)/3;
// var _itemWidth = (GlobalKey().currentContext.size.width - 40)/3;
int _currentTabbarIndex = 0;
Widget _getCurrentContent() {
Widget result;
switch (_currentTabbarIndex) {
case 0:
result = HomeContent();
break;
case 1:
result = ArticleContent();
break;
case 2:
result = CommonContent();
break;
}
return result;
}
String _getCurrentTitle() {
String result;
switch (_currentTabbarIndex) {
case 0:
result = "英雄列表";
break;
case 1:
result = "物品列表";
break;
case 2:
result = "召唤师技能";
break;
}
return result;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_getCurrentTitle(), style: TextStyle(color: Colors.white),),
),
body: _getCurrentContent(),
bottomNavigationBar: BottomNavigationBar(
unselectedItemColor: Colors.grey,
selectedItemColor: Colors.white,
backgroundColor: Theme.of(context).primaryColor,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home,),
title: Text(
'英雄'
)
),
BottomNavigationBarItem(
icon: Icon(Icons.apps,),
title: Text(
'物品'
)
),
BottomNavigationBarItem(
icon: Icon(Icons.insert_emoticon,),
title: Text(
'技能'
)
),
],
currentIndex: _currentTabbarIndex,
onTap: (int index) {
setState(() {
_currentTabbarIndex=index;
});
},
),
);
}
}
内容页面放在了HomeConten类中去实现。
- 之所以把内容也放到对应的类中去实现,是为了方便下方BottomNavigationBar切换时,方便界面的切换和代码管理。
HomeConten类
import 'package:flutter/material.dart';
import '../Model/HeroData.dart';
import '../Net/Net.dart';
import '../Router/AppRouter.dart';
import 'package:fluro/fluro.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../View/AppComponent.dart';
class HomeContent extends StatefulWidget {
HomeContent({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _HomeContentState();
}
}
class _HomeContentState extends State<HomeContent> {
List<HeroData> _heroList = [];
@override
void initState() {
if (AppComponent.articles == null) {
final future = getArticle();
future.then((value) {
AppComponent.articles = value;
});
}
if (AppComponent.heros != null) {
_heroList = AppComponent.heros;
return;
}
final future = getMain();
future.then((value) {
setState(() {
_heroList = value;
AppComponent.heros = value;
});
});
}
Widget _getItem(double width, int index, HeroData hero) {
return GestureDetector(
onTap: () {
Application.router.navigateTo(
context,
Uri.encodeFull('/hero_info?heroIndex=$index'),
transition: TransitionType.native
);
},
child: Column(
children: <Widget>[
CachedNetworkImage(
width: width,
height: width,
fit: BoxFit.fill,
imageUrl: 'https:${hero.href}',
placeholder: (BuildContext context, String url) {
return CircularProgressIndicator();
},
errorWidget: (BuildContext context, String url, Object error) {
return Icon(Icons.error_outline);
},
),
Text(hero.name),
],
),
);
}
Widget loading() {
return Center(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
Padding(
padding: EdgeInsets.only(top: 20),
child: Text('加载中...'),
),
],
),
);
}
Widget listView() {
final width = (MediaQuery.of(context).size.width-40)/4;
final aspect = width/(width+20);
return GridView.builder(
padding: EdgeInsets.all(5),
itemCount: _heroList.length,
itemBuilder: (BuildContext context, int index) {
return _getItem(width, index, _heroList[index]);
},
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: aspect
),
);
}
@override
Widget build(BuildContext context) {
return _heroList.length==0? loading() : listView();
}
}
在该类中,我们只需要专注于内容的实现就行。
结束
在UI层面与在文件层面,他们的对应关系是:
MaterialApp -> Scaffold -> AppBar、GridView、BottomNavigationBar
AppComponent -> AppHome -> HomeContent
之所以这样分层次分开,主要还是为了后面代码量越来越大,方便管理。
相信代码请查看:https://github.com/flywo/HeroList
喜欢的朋友给个star,非常感谢。