上一篇主要介绍了
Dart
语言的语法基础,从这一篇开始就要真正涉及到Flutter的开发了,但愿本身在写做的过程当中能温故知新,同时给Flutter初学者带来一些帮助。html
还记得在上一篇中,咱们使用Android Studio建立了一个Flutter项目吗?新建立的Flutter项目自动为咱们生成了一些代码,代码在/lib/main.dart
文件中,这里咱们先清空/lib/main.dart
文件中的代码,用下面的代码代替:android
// main.dart文件内容
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('First App')
),
body: new Center(
child: new Text('Hello world'),
),
),
);
}
}
复制代码
为了在手机上跑起咱们的App来,首先咱们得运行一个模拟器(固然你也能够用真机调试)。若是你的电脑上Flutter开发环境配置得没有问题,该装的都装了(运行flutter doctor
命令检查依赖是否安装,AndroidStudio的dart和flutter插件也必须安装),那么在Android Studio的工具栏上,应该能够看到以下图的图标: ios
这里因为个人电脑上尚未任何运行的Android / iOS模拟器,因此这里显示的是<no devices>
,点击该按钮,选择Open iOS Simulator
便可启动一个iOS模拟器(确保你的电脑上安装了Xcode)以下图所示:git
若是你想建立Android模拟器,必须先确保你有可用的Android模拟器,在AndroidStudio的工具栏上找到AVD Manager
图标,以下图:github
点击打开Android模拟器管理对话框,以下图: bash
Create Virtual Devices...
建立模拟器便可。
第一步中咱们已经写好了代码,第二步中咱们的模拟器也启动了,点击AndroidStudio工具栏中的Run
按钮便可运行Flutter项目到咱们的模拟器中了,Run
按钮在下图所示位置: markdown
能够看到,咱们仅仅用了20多行代码,就完成了一个精美的Demo App(虽然没有实现任何功能,可是对比下若是要用Android或iOS原生开发方式,能够作到这么简单实现吗),这一切都归功于Flutter为咱们提供的Widgets,下面的篇幅里会针对经常使用的Widgets作一些讲解。网络
新建立的Flutter项目的结构以下图所示: app
各个目录/文件说明以下:框架
.
├── README.md ---markdown项目描述文件
├── android ---Android源代码目录
├── build ---项目构建后输出的相关文件目录
├── flutter_app.iml ---项目相关的配置文件
├── flutter_app_android.iml ---Android相关的配置文件
├── ios ---iOS源代码目录
├── lib ---Dart源码目录
├── pubspec.lock ---安装锁定文件
├── pubspec.yaml ---flutter依赖配置文件,相似Android中的build.grale
└── test ---测试代码目录
复制代码
咱们开发的代码主要存放在lib/
目录下,项目的入口文件main.dart
也在lib/
目录下。
关于一个Flutter App,你须要了解以下几个点:
在移动开发中,咱们常常会跟按钮、文本输入框、图片等打交道,Flutter中也不例外,使用Flutter开发的App,界面上的每个UI元素都是一个Widget,经过不一样的Widget组合造成一整个页面。除了按钮、输入框、图片等Widget外,Flutter还给咱们提供了不少功能强大,界面美观的Widget,好比在本文最开始的一段代码:
// main.dart文件内容
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('First App')
),
body: new Center(
child: new Text('Hello world'),
),
),
);
}
}
复制代码
在上面的代码中,MyApp
是咱们自定义的一个类,它继承自StatelessWidget,表明它是一个无状态的组件,UI不会发生改变。build
方法是父类的一个方法,被MyApp
类重写了,继承自StatelessWidget的类必须实现build
方法并返回一个Widget对象。因此在上面的代码中,MaterialApp
也是一个Widget,若是你用AndroidStudio查看源码,会发现MaterialApp的参数home
也是一个Widget对象,因此上面的Scaffold也是一个Widget。
StatefulWidget和StatelessWidget是Flutter中全部Widget的两个分类,StatefulWidget的内部保存有状态,当状态发生改变时,Widget的界面也会随之改变(这点跟React相似);StatelessWidget的内部没有保存状态,它的界面也不会发生改变。上面的代码中已经展现了定义一个无状态Widget的步骤:继承StatelessWidget并实现build方法便可。若是是定义一个有状态的Widget,代码会稍微多一点,以下代码所示:
import 'package:flutter/material.dart';
void main() => runApp(new MyStatefulWidget());
// 定义一个有状态的组件
class MyStatefulWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new MyStatefulWidgetState();
}
}
// 定义一个有状态的组件时,必须为该组件建立一个状态类,这个类继承自State类
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String text = "Click Me!";
changeText() {
if (text == "Click Me!") {
setState(() {
text = "Hello World!";
});
} else {
setState(() {
text = "Click Me!";
});
}
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Test",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Center(
// InkWell是Flutter内置的一个Widget,用于给其余Widget添加点击事件,而且在点击时会有水波纹扩散效果
child: new InkWell(
child: new Text(text),
onTap: () {
this.changeText();
},
),
),
),
);
}
}
复制代码
上面的代码运行后,会在页面中央显示文本,点击该文本时,文本内容会在"Click Me!"和"Hello World!"间切换,以下图所示:
createState
方法,注意,这里跟StatelessWidget不一样了,不是实现build
方法。createState
方法返回的是一个状态State。createState
方法有返回值,还须要建立一个状态类继承自State类,State类是个泛型类,你须要将第一步中建立的类传给State。build
方法,并返回你所须要的Widget。text
变量保存组件的文本值,当点击按钮时,经过调用State组件的setState()
方法,从新为text
变量赋值,从而达到改变文本的目的。若是你了解Reactjs,那么对于Flutter的这种状态机制确定也不陌生。React中也是经过一个state对象保存Component的状态,当状态须要改变时,调用setState()方法修改状态,组件就会自动刷新。
MaterialApp和Scaffold是Flutter提供的两个Widget,其中:
在基于Flutter的开源中国客户端App中,我也使用到了MaterialApp和Scaffold两个组件,下面是部分代码:
@override
Widget build(BuildContext context) {
initData();
return new MaterialApp(
theme: new ThemeData(
// 设置主题颜色
primaryColor: const Color(0xFF63CA6C)
),
home: new Scaffold(
// 设置App顶部的AppBar
appBar: new AppBar(
// AppBar的标题
title: new Text(appBarTitles[_tabIndex],
// 标题文本的颜色
style: new TextStyle(color: Colors.white)),
// AppBar上的图标的颜色
iconTheme: new IconThemeData(color: Colors.white)
),
body: _body,
// 页面底部的导航栏
bottomNavigationBar: new CupertinoTabBar(
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: getTabIcon(0),
title: getTabTitle(0)),
new BottomNavigationBarItem(
icon: getTabIcon(1),
title: getTabTitle(1)),
new BottomNavigationBarItem(
icon: getTabIcon(2),
title: getTabTitle(2)),
new BottomNavigationBarItem(
icon: getTabIcon(3),
title: getTabTitle(3)),
],
currentIndex: _tabIndex,
// 底部Tab的点击事件处理
onTap: (index) {
setState((){
_tabIndex = index;
});
},
),
// 侧滑菜单,这里的MyDrawer是自定义的Widget
drawer: new MyDrawer(),
),
);
}
复制代码
Text组件是很是经常使用的组件,任何须要显示文本的地方基本都会用到。经过查看Text类的源码,能够发现Text是一个无状态的组件,下面的代码演示了如何修改Text组件的字号、颜色,给字体加粗、设置下划线、设置斜体等:
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(
title: "Text Demo",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Text Demo"),
),
body: new Center(
child: new Text(
"Hello Flutter",
style: new TextStyle(
color: Colors.red, // 或者用这种写法:const Color(0xFF6699FF) 必须使用AARRGGBB
fontSize: 20.0, // 字号
fontWeight: FontWeight.bold, // 字体加粗
fontStyle: FontStyle.italic, // 斜体
decoration: new TextDecoration.combine([TextDecoration.underline]) // 文本加下划线
),
),
),
),
));
复制代码
注意:
TextFiled组件用于文本的输入,示例代码以下:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Test",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test")
),
body: new Padding(
padding: const EdgeInsets.all(8.0),
child: new TextField(
maxLines: 8, // 设置输入框显示的最大行数(不是可输入的最大行数)
maxLength: 30, // 设置输入框中最多可输入的字符数
decoration: new InputDecoration( // 给输入框添加样式
hintText: "Input something...", // 输入框中placeholder文本
border: new OutlineInputBorder( // 输入框的边框
borderRadius: const BorderRadius.all(Radius.circular(1.0))
)
),
)
)
),
);
}
}
复制代码
在模拟器中运行界面以下图:
这两个组件放到一块儿说,是由于在处理组件的点击事件时,会常常用到它们。 好比某个列表的item的点击事件,某个图标的点击事件等等。Flutter有专门设计MaterialDesign风格的按钮,可是更多时候咱们但愿自定义按钮样式或者为某个组件添加点击事件,因此在处理点击事件时,最多见的作法是,用InkWell或者GestureDetector将某个组件包起来。
InkWell的使用方法以下:
new InkWell(
child: new Text("Click me!"),
onTap: () {
// 单击
},
onDoubleTap: () {
// 双击
},
onLongPress: () {
// 长按
}
);
复制代码
GestureDetector用法与InkWell相似,不过GestureDetector有更多处理手势的方法,这里暂时不作介绍(其实我也用得很少)。
Flutter提供了几种类型的按钮组件:RaisedButton
FloatingActionButton
FlatButton
IconButton
PopupMenuButton
,下面用一段代码说明这几种按钮的用法:
import 'package:flutter/material.dart';
main() {
runApp(new MyApp());
}
enum WhyFarther { harder, smarter, selfStarter, tradingCharter }
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Test')
),
body: new Column(
children: <Widget>[
new RaisedButton(
child: new Text("Raised Button"),
onPressed: (){},
),
new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: (){},
),
new FlatButton(
onPressed: (){},
child: new Text("Flat Button")
),
new IconButton(
icon: new Icon(Icons.list),
onPressed: (){}
),
new PopupMenuButton<WhyFarther>(
onSelected: (WhyFarther result) {},
itemBuilder: (BuildContext context) => <PopupMenuEntry<WhyFarther>>[
const PopupMenuItem<WhyFarther>(
value: WhyFarther.harder,
child: const Text('Working a lot harder'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.smarter,
child: const Text('Being a lot smarter'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.selfStarter,
child: const Text('Being a self-starter'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.tradingCharter,
child: const Text('Placed in charge of trading charter'),
),
],
)
],
)
)
);
}
}
复制代码
在模拟器中上面的代码运行效果以下图所示:
Flutter提供了两种类型的对话框:SimpleDialog和AlertDialog。SimpleDialog是一个能够显示附加的提示或操做的简单对话框,AlertDialog则是一个会中断用户操做的对话框,须要用户确认的对话框,下面用代码来讲明其用法:
import 'package:flutter/material.dart';
main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Test')
),
// body: new MyAlertDialogView()
body: new MySimpleDialogView(),
),
);
}
}
class MyAlertDialogView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('显示AlertDialog'),
onPressed: () {
showDialog<Null>(
context: context,
barrierDismissible: false, // 不能点击对话框外关闭对话框,必须点击按钮关闭
builder: (BuildContext context) {
return new AlertDialog(
title: new Text('提示'),
content: new Text('微软重申Windows 7将在2020年1月到达支持终点,公司但愿利用这个机会说服用户在最新更新发布以前升级到Windows 10。'),
actions: <Widget>[
new FlatButton(
child: new Text('明白了'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
);
}
}
class MySimpleDialogView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('显示SimpleDialog'),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext ctx) {
return new SimpleDialog(
title: new Text('这是SimpleDialog'),
children: <Widget>[
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text('肯定'),
),
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text('取消'),
),
],
);
}
);
},
);
}
}
复制代码
上面的代码分别展现了SimpleDialog和AlertDialog的基本用法。须要注意的是,这里并无直接将按钮和显示对话框的逻辑写到MyApp类中,而是分两个StatelessWidget来写的,若是你直接将按钮及显示对话框的逻辑写到MyApp的build
方法里,是会报错的,具体报错信息为:
Navigator operation requested with a context that does not include a Navigator.
复制代码
意思是导航操做须要一个不包含Navigator的上下文对象,而若是咱们将showDialog的逻辑写到MyApp的build
方法中时,使用的是MaterialApp的上下文对象,这个上下文对象是包含Navigator的,因此就会报错。上面的代码在模拟器中运行效果以下图:
Image组件用于显示一张图片,能够加载本地(项目中或手机存储中)或网络图片。
使用下面的方法加载一张项目中的图片:
new Image.asset(path, width: 20.0, height: 20.0, fit: BoxFit.cover)
复制代码
其中path是项目中的图片目录。
加载项目中的图片必定要注意编辑pubspec.yaml文件:
假设当前咱们在跟
lib/
同级的目录下建立了images/
目录,在images/
目录下存放了若干图片供项目使用,那么必定要记得在项目根目录下(也是跟images/
同级的目录)编辑pubspec.yaml文件,打开pubspec.yaml文件,默认状况下assets是被注释了的,这里咱们要取消注释assets并添加images/目录下的每一个图片的路径,以下图所示:
在上图中咱们配置了文件路径images/ic_nav_news_normal.png
,因此能够用下面的代码来加载图片了:
new Image.asset('images/ic_nav_news_normal.png', width: 20.0, height: 20.0, fit: BoxFit.cover)
复制代码
width
和height
是图片长宽,为double类型,若是你传整型20
则会报错。 若是要加载手机存储中的图片,使用下面的方法:
new Image.file(path, width: 20.0, height: 20.0, fit: BoxFit.cover)
复制代码
fit属性指定了图片显示的不一样方式,有以下几个值:
contain
的状况相同,不然它与没有同样。
加载网络图片使用下面的方法:
new Image.network(imgUrl, width: 20.0, height: 20.0, fit: BoxFit.cover)
复制代码
ListView组件用于显示一个列表,在基于Flutter的开源中国客户端App中,新闻列表、动弹列表等都须要用到ListView,一个最简单的ListView能够用以下代码实现:
import 'package:flutter/material.dart';
void main() {
List<Widget> items = new List();
for (var i = 0; i < 20; i++) {
items.add(new Text("List Item $i"));
}
runApp(new MaterialApp(
title: "Text Demo",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Text Demo"),
),
body: new Center(
child: new ListView(children: items)
),
),
));
}
复制代码
运行上面的代码,结果以下图所示:
这样的ListView显示不是咱们须要的,太难看,每一个item没有边距并且没有分割线,因此咱们用下面的代码改造一下:
import 'package:flutter/material.dart';
void main() {
// 装有ListView中全部item的集合
List<Widget> items = new List();
for (var i = 0; i < 20; i++) {
var text = new Text("List Item $i");
// Padding也是一个Widget,是一个有内边距的容器,能够装其余Widget
items.add(new Padding(
// 内边距设置为15.0,上下左右四边都是15.0
padding: const EdgeInsets.all(15.0),
// Padding容器中装的是Text组件
child: text
));
}
runApp(new MaterialApp(
title: "Text Demo",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Text Demo"),
),
body: new Center(
// build是ListView提供的静态方法,用于建立ListView
child: new ListView.builder(
// itemCount是ListView的item个数,这里之因此是items.length * 2是由于将分割线也算进去了
itemCount: items.length * 2,
itemBuilder: (context, index) {
// 若是index为奇数,则返回分割线
if (index.isOdd) {
return new Divider(height: 1.0);
}
// 这里index为偶数,为了根据下标取items中的元素,须要对index作取整
index = index ~/ 2;
return items[index];
},
)
)
),
));
}
复制代码
此时再次运行上面的代码,UI就好看多了:
关于ListView的用法,上面的代码中已有相关注释,更详细的用法会在后面的篇幅中介绍,好比ListView中的item实现不一样的布局,下拉刷新,加载更多等等。
关于Flutter经常使用的部分Widget,在上面已有相关示例代码和说明,你还能够在Flutter中文网上查看更多组件及其用法。下一篇中我将记录Flutter中的布局,任何移动开发,甚至Web开发和桌面端应用开发中都不可避免的须要了解布局的知识。
上一篇 | 下一篇 |
---|---|
从0开始写一个基于Flutter的开源中国客户端(2) ——Dart语法基础 |
从0开始写一个基于Flutter的开源中国客户端(4)——Flutter布局基础 |