经过这一段对 Flutter 的了解和接触,掌握如何完整使用 Flutter 开发一个项目。实际上,在 Flutter 中,一切皆 widget,我们的界面都是由各种 widget 堆叠出来的。
一个 Flutter 工程涉及以下几个要点:
- 工程项目代码分层
- 主题风格
- 插件
- 路由
- 网络数据交互
- 界面布局及刷新
一、工程项目代码分层
一个正式的工程项目,它的代码必须做到分层,代码的分层体现了开始者的架构能力。
Flutter 工程的主要工作 lib 目录及 pubspec.yaml :
- main.dart:Flutter 的入口函数
- loading.dart:启动页,一般生存周期为3-5秒
- app.dart:工程主文件
- conf : 配置文件目前或一些宏定义数据文件目录
- model : 数据模型目录
- pages : 各 UI ,即 Widget 文件
- service : 网络请求目录
- style : 自定义风格文件(颜色、字体等)
- utils : 工具目录
代码分层设计设计的合不合理,直接影响代码的可维护性和稳定性。
二、主题风格
Flutter 默认的主题是 蓝白 的风格,其他主题则需要配置。依项目而定,根据当前需要,配置一个 红灰 风格:
#main.dart
void main() => runApp(MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter实战',
//自定义主题
theme: mDefaultTheme,
));
//自定义主题
final ThemeData mDefaultTheme = ThemeData(
primaryColor: Colors.redAccent,
);
Colors.redAccent 为系统主题,在 colors.dart 中定义:
static const MaterialAccentColor redAccent = MaterialAccentColor(
_redAccentValue,
<int, Color>{
100: Color(0xFFFF8A80),
200: Color(_redAccentValue),
400: Color(0xFFFF1744),
700: Color(0xFFD50000),
},
);
static const int _redAccentValue = 0xFFFF5252;
当然也可以自定义一些风格或颜色,在工程 style 中 color.dart:
//产品颜色
class ProductColors{
static const Color bgColor = Color(0xFFFFFFFF);
static const divideLineColor = Color.fromRGBO(245, 245, 245, 1.0);
static const typeColor = Color.fromRGBO(182, 9, 9, 1.0);
static const piontColor = Color.fromRGBO(132, 95, 63, 1.0);
}
三、插件
开发者不可能对每个功能都自已造轮子,选取合适的轮子,对于项目来说,可以达到事半功倍的效果。
3.1 添加第三方库
打开 pubspec.yaml 文件添加三方库,可以 在 https://pub.dev/flutter (需要翻墙)上找到许多开源软件包
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
http: ^0.12.0
flutter_webview_plugin: ^0.3.0
flutter_swiper: 1.1.4
- http : 网络请求插件
- flutter_webview_plugin : 一些静态页面的加载,使用webview
- flutter_swiper : 动画轮播效果
3.2 导入
swiper 插件
点击 Packages get 获取刚添加的包。
这样就可以使用这个库了。
四、路由
路由主要用于界面的切换及跳转,分为 静态路由 和 动态路由
- 静态路由:界面的跳转不带附加参数
- 动态路由:界面的跳转可携带附加参数
4.1 路由初始化
void main() => runApp(MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter实战',
//自定义主题
theme: mDefaultTheme,
//添加路由
routes: <String,WidgetBuilder>{
"app": (BuildContext context) => App(),
"company_info":(BuildContext context) => WebviewScaffold(
url: "https://www.baidu.com",
appBar: AppBar(
title: Text('公司介绍'),
leading: IconButton(
icon: Icon(Icons.home),
onPressed: (){
//路由到主界面
Navigator.of(context).pushReplacementNamed('app');
},
),
),
),
},
//指定加载页面
home: LoadingPage(),
));
4.2 静态路由
Navigator.of(context).pushReplacementNamed('company_info');
4.3 动态路由
Navigator.push(context,MaterialPageRoute(builder: (context) => AboutContactPage()));
或者
Navigator.push(context,MaterialPageRoute(builder: (context) => NewsDetailPage(item: item)),
数据接收处理:
class NewsDetailPage extends StatelessWidget{
final NewsItemModal item;
NewsDetailPage({Key key,@required this.item}) : super(key:key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(item.title),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(item.content),
),
);
}
}
五、网络数据交互
网络数据交互一般涉及 URL、数据模型、数据交互模式(Get、Post)等
5.1 URL 定义
在配置中定义宏变量
class Config{
static const String IP = '192.168.2.5';
static const String PORT = '8080';
}
5.2 数据模型
//新闻列表项数据转换
class NewsItemModal{
String author;//作者
String title;//标题
String content;//内容
NewsItemModal({
this.author,
this.title,
this.content,
});
factory NewsItemModal.fromJson(dynamic json){
return NewsItemModal(
author: json['author'],
title: json['title'],
content: json['content'],
);
}
}
//新闻列表数据转换
class NewsListModal{
List<NewsItemModal> data;
NewsListModal(this.data);
factory NewsListModal.fromJson(List json){
return NewsListModal(
json.map((i) => NewsItemModal.fromJson((i))).toList()
);
}
}
5.3 数据交互模式
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../conf/configure.dart';
//获取新闻数据
getNewsResult() async {
String url = 'http://' + Config.IP + ':' + Config.PORT + '/?action=getNews';
var res = await http.get(url);
String body = res.body;
var json= jsonDecode(body);
print(json);
return json['items'] as List;
}
六、界面布局及刷新
6.1 启动页加载
一个存在 3 - 5 秒的界面
class LoadingPage extends StatefulWidget{
@override
_LoadingState createState() => _LoadingState();
}
class _LoadingState extends State<LoadingPage>{
@override
void initState(){
super.initState();
//在加载页面停顿3秒
Future.delayed(Duration(seconds: 3),(){
print('Flutter企业站启动...');
Navigator.of(context).pushReplacementNamed("app");
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Center(
child: Stack(
children: <Widget>[
//加载页面背景图
Image.asset(
'assets/images/loading.jpeg'
),
Center(
child: Text(
'Flutter',
style: TextStyle(
color: Colors.white,
fontSize: 36.0,
decoration: TextDecoration.none
),
),
),
],
),
),
);
}
}
6.2 轮播图片
轮播图片应用很广泛,如广告宣传之类。
在资源配置图片中,添加图片
# To add assets to your application, add an assets section, like this:
assets:
- assets/images/loading.jpeg
- assets/images/company.jpg
#轮播图片
- assets/images/banners/1.jpeg
- assets/images/banners/2.jpeg
- assets/images/banners/3.jpeg
- assets/images/banners/4.jpeg
使用 Swiper 插件
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
class BannerWidget extends StatelessWidget{
//图片路径
List<String> banners = <String>[
'assets/images/banners/1.jpeg',
'assets/images/banners/2.jpeg',
'assets/images/banners/3.jpeg',
'assets/images/banners/4.jpeg',
];
@override
Widget build(BuildContext context) {
//计算宽高 按比例
double width = MediaQuery.of(context).size.width;
double height = width * 540.0 / 1080.0;
return Container(
width: width,
height: height,
//轮播组件
child: Swiper(
itemBuilder: (BuildContext context, index){
return Container(
//图片左右内边距
margin: EdgeInsets.only(left: 5, right: 5),
child: Image.asset(
banners[index],
width: width,
height: height,
fit: BoxFit.cover,
),
);
},
//轮播数量
itemCount: banners.length,
//方向
scrollDirection: Axis.horizontal,
//是否自动播放
autoplay: true,
),
);
}
}
6.3 主界面(含导航栏)
import 'package:flutter/material.dart';
import 'pages/about_us_page.dart';
import 'pages/home_page.dart';
import 'pages/news_page.dart';
import 'pages/product_page.dart';
class App extends StatefulWidget {
@override
AppState createState() => AppState();
}
class AppState extends State<App> {
//当前选择页面索引
var _currentIndex = 0;
HomePage homePage;
ProductPage productPage;
NewsPage newsPage;
AboutUsPage aboutUsPage;
//根据当前索引返回不同的页面
currentPage(){
switch(_currentIndex){
case 0:
if(homePage == null){
homePage = HomePage();
}
return homePage;
case 1:
if(productPage == null){
productPage = ProductPage();
}
return productPage;
case 2:
if(newsPage == null){
newsPage = NewsPage();
}
return newsPage;
case 3:
if(aboutUsPage == null){
aboutUsPage = AboutUsPage();
}
return aboutUsPage;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter企业站实战'),
leading: Icon(Icons.home),
actions: <Widget>[
//右侧内边距
Padding(
padding: EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () {},
child: Icon(
Icons.search,
),
),
),
],
),
body: currentPage(),
//底部导航栏
bottomNavigationBar: BottomNavigationBar(
//通过fixedColor设置选中item 的颜色
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
onTap: ((index) {
setState(() {
_currentIndex = index;
});
}),
//底部导航栏
items: [
BottomNavigationBarItem(
title: Text(
'首页',
),
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
title: Text(
'产品',
),
icon: Icon(Icons.apps),
),
BottomNavigationBarItem(
title: Text(
'新闻',
),
icon: Icon(Icons.fiber_new),
),
BottomNavigationBarItem(
title: Text(
'关于我们',
),
icon: Icon(Icons.insert_comment),
),
]),
);
}
}
6.4 ListView 的应用
import 'package:flutter/material.dart';
import '../model/news.dart';
import '../services/news.dart';
import 'news_detail_page.dart';
//新闻页面
class NewsPage extends StatefulWidget {
@override
NewsPageState createState() => NewsPageState();
}
class NewsPageState extends State<NewsPage> {
NewsListModal listData = NewsListModal([]);
//获取新闻列表数据
void getNewsList() async {
var data = await getNewsResult();
NewsListModal list = NewsListModal.fromJson(data);
setState(() {
listData.data.addAll(list.data);
});
}
@override
void initState() {
getNewsList();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
//带分隔线的List
body: ListView.separated(
//排列方向 垂直和水平
scrollDirection: Axis.vertical,
//分隔线构建器
separatorBuilder: (BuildContext contex, int index) => Divider(
height: 1.0,
color: Colors.grey,
),
itemCount: listData.data.length,
//列表项构建器
itemBuilder: (BuildContext contex, int index) {
//新闻列表项数据
NewsItemModal item = listData.data[index];
return ListTile(
title: Text(item.title),
subtitle: Text(item.content),
leading: Icon(Icons.fiber_new),
trailing: Icon(Icons.arrow_forward),
contentPadding: EdgeInsets.all(10.0),
enabled: true,
//跳转至新闻详情页面
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewsDetailPage(item: item)),
);
},
);
},
),
);
}
}
6.5 文本框操作
含文本框配置及数据操作
import 'package:flutter/material.dart';
import '../services/contact.dart';
class AboutContactPage extends StatefulWidget{
@override
AboutContactPageState createState() => AboutContactPageState();
}
class AboutContactPageState extends State<AboutContactPage>{
//文本编辑控制器
TextEditingController controller = TextEditingController();
//提交数据
void commit(){
if(controller.text.length == 0){
showDialog(context: context,builder: (context) => AlertDialog(title: Text('请输入内容'),),);
} else{
var info = contactCompany(controller.text);
print(info);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('给我留言'),
),
body: Container(
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/images/company.jpg',
fit: BoxFit.cover,
),
SizedBox(
height: 40.0,
),
SizedBox(
width: 380.0,
child: TextField(
controller: controller,
decoration: InputDecoration(
hintText: '请留言',
labelText: '请留言',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person),
),
),
),
SizedBox(
height: 40.0,
),
SizedBox(
width: 220.0,
height: 48.0,
child: RaisedButton(
child: Text('给我们留言',style: TextStyle(fontSize: 16.0),),
color: Theme.of(context).primaryColor,//Colors.redAccent,
colorBrightness: Brightness.dark,
textColor: Colors.white,
padding: EdgeInsets.only(
left: 20.0,
right: 20.0,
top: 5.0,
bottom: 5.0,
),
shape: RoundedRectangleBorder(
side: BorderSide(
width: 1.0,
color: Colors.white,
style: BorderStyle.solid,
),
borderRadius: BorderRadius.only(
topRight: Radius.circular(4.0),
topLeft: Radius.circular(4.0),
bottomLeft: Radius.circular(4.0),
bottomRight: Radius.circular(4.0),
),
),
onPressed: (){
commit();
},
),
),
],
),
),
);
}
}
七、注意点
- => 是 Dart 中单行函数的简写
- StatelessWidget 代表只有一种状态的组件,与之对应的是 StatefulWidget(表示可能有多种状态)。
- 在 Widget 组件中都是通过 build 方法来描述自己的内部结构。这里的 build 表示构建 MyApp 中使用的是 MaterialApp 的系统组件。
- home标签的值:Scaffold 是 Material library 中提供的一个组件,我们可以在里面设置导航栏、标题和包含主屏幕 widget 树的 body 属性。可以看到这里是在页面上添加了AppBar 和一个 Text。
- Center 是一个可以把子组件放在中心的组件
Refer