1. flutter项目的创建
1.1 创建方式
- 第一种创建方式:在终端输入命令
flutter create projectname 即可,创建完成使用自己喜欢的工具打开项目即可 - 第二种创建方式:通过开发工具来创建项目, 比如
Android Studio 进行创建,选择Start a new Flutter project 之后,填写相关信息即可 - 注意:Flutter的名称不要包含特殊的字符,另外不可以使用驼峰标识,如果多个单词可以使用 _ 来连接
1.2 默认程序分析
-
在目录下有一个lib 文件夹,里面会存放我们编写的Flutter代码; -
打开发现里面有一个main.dart ,它是我们Flutter 启动的入口文件,里面有main 函数; -
我们第一次接触main.dart中的代码,可能会发现很多不认识的代码,作为初学者,我的建议是将其中所有的代码全部删除掉,从零去创建里面的代码,这样我们才能对Flutter应用程序的结构非常清晰; -
快捷调试按钮:
- 热更新(hot reload):主要是执行build方法,如果写的代码不在build方法中, 则热更新是不起做的
- 热重启(hot restart) : 重新运行整个app
2. 编写flutter代码
需求:在界面中心位置,显示一个Hello World
2.1 helloworld的实现
import 'package:flutter/material.dart';
void main() {
runApp(
Text(
"hello world",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30
),
)
);
}
- 上面的代码已经能够显示在界面上,但是还存在一些问题
- 上面的代码我们有一些熟悉,但是还有一些是我们并不清楚的:
- 比如我们知道
Dart程序的入口都是main函数 ,而Flutter是Dart编写的,所以入口也是main函数 - 但是我们导入的
Material是什么呢? - 另外,我们在main函数中调用了一个
runApp()函数 又是什么呢?
2.2 代码分析
2.2.1 runApp和Widget
runApp 是Flutter 内部提供的一个函数,当我们启动一个Flutter应用程序时就是从调用这个函数开始的
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
- 由源码可知,该函数让我们传入一个东西:
Widget ,那么什么是Widget ?
- 我们先说Widget的翻译,有的人翻译成控件,有的人翻译成组件,有的人翻译成小部件,那么Widget到底是什么东西了
- 我们学习Flutter,从一开始就可以有一个基本的认识:
Flutter中万物皆Widget - 在我们iOS或者Android开发中,我们的界面有很多种类的划分:应用(Application)、视图控制器(View Controller)、活动(Activity)、View(视图)、Button(按钮)等等;
但是在Flutter中,这些东西都是不同的Widget而已 - 也就是我们整个应用程序中
所看到的内容几乎都是Widget ,甚至是内边距的设置,我们也需要使用一个叫Padding的Widget 来做 runApp 函数让我们传入的就是一个Widget :我们可以导入Flutter默认已经给我们提供的Material 库,来使用其中的很多内置Widget;
2.2.2 Material设计风格
-
material 是什么呢?
material 是Google公司推行的一套设计风格 ,或者叫设计语言 、设计规范 等;- 里面有非常多的设计规范,比如
颜色、文字的排版、响应动画与过度、填充 等等; - 在Flutter中高度集成了
Material风格的Widget ; - 在我们的应用中,我们可以直接使用这些Widget来创建我们的应用(后面会用到很多)
-
Text小部件分析:
- 我们可以使用Text小部件来完成文字的显示
- 我们发现Text小部件继承自
StatelessWidget ,StatelessWidget 继承自Widget ; - 所以我们可以将Text小部件传入到runApp函数中
- 属性非常多,但是我们已经学习了Dart语法,所以你会发现只有this.data属性是必须传入的。
class Text extends StatelessWidget {
const Text(
this.data, {
Key key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
});
}
- StatelessWidget简单介绍:
- StatelessWidget继承自Widget;
- 后面我会更加详细的介绍它的用法;
abstract class StatelessWidget extends Widget {
}
2.3 代码改进
2.3.1 改进界面样式
- 居中显示: 需要使用另外一个Widget,
Center ; - 我们修改如下:
- 我们在Text小部件外层包装了一个Center部件,让Text作为其child;
import 'package:flutter/material.dart';
void main() {
runApp(
Center(
child: Text(
"hello world",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30
),
),
)
);
}
运行效果如下:
2.3.2 改进界面结构
目前我们虽然可以显示HelloWorld,但是我们发现最底部的背景是黑色,并且我们的页面并不够结构化。 正常的App页面应该有一定的结构,比如通常都会有导航栏 ,会有一些背景颜色 等
在flutter开发当中 ,我们并不需要从零去搭建这种结构化的界面,我们可以使用Material库,直接使用其中的一些封装好的组件来完成一些结构的搭建。
我们通过下面代码来实现:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
"Hello Flutter"
),
),
body: Center(
child: Text(
"hello world",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30
),
),
),
)
)
);
}
运行效果图如下:
- 最外层包裹一个
MaterialApp
- 这意味着整个应用我们都会采用MaterialApp风格的一些东西,方便我们对应用的设计,并且目前我们使用了其中两个属性;
title :这个是定义在Android系统中打开多任务切换窗口时显示的标题;(暂时可以不写)- home:是该应用启动时显示的页面,我们传入了一个Scaffold;
Scaffold 是什么呢?
- 翻译过来是
脚手架 ,脚手架的作用就是搭建页面的基本结构; - 所以我们给MaterialApp的home属性传入了一个Scaffold对象,作为启动显示的Widget;
Scaffold 也有一些属性,比如appBar 和body ;appBar 是用于设计导航栏的,我们传入了一个title 属性body 是页面的内容部分,我们传入了之前已经创建好的Center中包裹的一个Text的Widget
2.3.3 进阶案例实现
我们可以让界面中展示更多的界面元素
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
"Hello Flutter"
),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(value: true,
onChanged: (value){
print("checkbox 点击事件");
}),
Text(
"同意协议",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30
),
),
],
)
),
)
)
);
}
运行效果:
2.4 代码重构
2.4.1 创建自己的Widget
我们可以发现上面的代码嵌套已经很多,很多学习Flutter的人,都会被Flutter的嵌套 劝退,当代码嵌套过多时,结构很容易看不清晰。
- Flutter整个开发过程中就是形成一个Widget树,所以形成嵌套是很正常的。
- 我们开发一个这么简单的程序就出现如此多的嵌套,如果应用程序更复杂呢?
- 我们可以对我们的代码进行封装,将它们封装到自己的Widget中,创建自己的Widget;
- 在Flutter开发中,我们可以继承自
StatelessWidget 或者StatefulWidget 来创建自己的Widget类; StatelessWidget : 没有状态改变的Widget,通常这种Widget仅仅是做一些展示工作而已;StatefulWidget : 需要保存状态,并且可能出现状态改变的Widget;
在上述案例中对代码的重构我们使用StatelessWidget 即可
2.4.2 StatelessWidget
class MyStatelessWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return <返回我们的Widget要渲染的Widget,比如一个Text Widget>;
}
}
- build方法的解析:
- Flutter在拿到我们自己创建的StatelessWidget时,就会执行它的build方法
- 我们需要在build方法中告诉Flutter,我们的Widget希望渲染什么元素,比如一个Text Widget;
- StatelessWidget没办法主动去执行build方法,当我们使用的数据发生改变时,build方法会被重新执行;
- build方法什么情况下被执行呢?:
- 当我们的StatelessWidget第一次被插入到Widget树中时(也就是第一次被创建时)
- 当我们的父Widget(parent widget)发生改变时,子Widget会被重新构建;
- 如果我们的Widget依赖InheritedWidget的一些数据,InheritedWidget数据发生改变时;
2.4.3 重构案例代码
我们可以通过StatelessWidget 对我们的代码进行重构
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
"Hello Flutter"
),
),
body: GYHomeContent()
)
);
}
}
class GYHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(value: true,
onChanged: (value){
print("checkbox 点击事件");
}),
Text(
"同意协议",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30
),
),
],
)
);
}
}
2.4.4 实现checkBox的状态改变
有得人可能想这样实现(错误代码 ):
class GYHomeContent extends StatelessWidget {
var flag = true;
@override
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(value: flag,
onChanged: (value){
flag = value;
print("checkbox 点击事件 $value");
}),
Text(
"同意协议",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30
),
),
],
)
);
}
}
上述代码定义一个变量是错误的写法, 我们可以看到该类的父类是 使用 @inmutable 这个注解标注过, 表示这个类是不可变的, 该注解主要的作用是,修改的当前的类是不可变的 ,一旦创建完成里面所有的东西都是不可变的,里面定义的所有的东西, 属性都是final的,所以里面定义的属性都必须使用final来修饰
所有的Widget里面都是不可以写状态代码的,因为@inmutable修饰的Widget,所以所有的Widget都是不可变的
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
"Hello Flutter"
),
),
body: GYHomeContent()
)
);
}
}
class GYHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GYHomeContentBody();
}
}
class GYHomeContentBody extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return GYHomeContentBodyState();
}
}
class GYHomeContentBodyState extends State<GYHomeContentBody> {
var flag = true;
@override
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(value: flag,
onChanged: (value){
setState(() {
flag = value ?? true;
});
print("checkbox 点击事件 $flag");
}),
Text(
"同意协议", style: TextStyle(
fontSize: 30
),
),
],
)
);
}
}
运行打印效果:
3. 案例练习
这个案例中可能有用到了一些我们不熟悉的Widget,该案例的主要目的还是让我们更加熟悉Flutter的开发模式以及自定义Widget的封装过程;
- 最终效果图:
3.1 自定义Widget
- 在我们的案例中,很明显一个产品的展示就是一个大的Widget,这个Widget包含如下Widget:
- 标题的Widget:使用一个Text Widget完成;
- 描述的Widget:使用一个Text Widget完成;
- 图片的Widget:使用一个Image Widget完成;
- 上面三个Widget要垂直排列,我们可以使用一个Column的Widget(上一个章节中我们使用了一次Row是水平排列的)
- 另外,三个展示的标题、描述、图片都是不一样的,所以我们定义一个产品的Widget
class GYProductItem extends StatelessWidget {
final String title;
final String desc;
final String imageURL;
GYProductItem(this.title, this.desc, this.imageURL);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(title, style: TextStyle(fontSize: 24)),
Text(desc, style: TextStyle(fontSize: 18)),
Image.network(imageURL)
],
);
}
3.2 列表数据展示
- GYHomeContent中,我们使用了一个Column,因为我们创建的三个GYProductItem是垂直排列的
class GYHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
GYProductItem("Apple1", "Macbook Product1", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
GYProductItem("Apple2", "Macbook Product2", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
GYProductItem("Apple3", "Macbook Product3", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg")
],
);
}
}
运行效果如下:
- 错误信息: 下面出现了黄色的斑马线;
- 这是因为在Flutter的布局中,内容是不能超出屏幕范围的,当超出时不会自动变成滚动效果,而是会报下面的错误
- 如何解决这个问题?
- 将Column换成ListView即可;
- ListView可以让自己的子Widget变成滚动的效果;
3.3 案例的细节调整
- 界面整体边距
- 我们需要使用另外一个Widget:Padding,它有一个padding属性用于设置边距大小
- 商品内边距和边框
- 我们可以使用一个Container的Widget,它里面有padding属性,并且可以通过decoration来设置边框;
- 文字图片的间距
- 方式一:给图片或者文字添加一个向上的内边距或者向下的内边距;
- 使用SizedBox的Widget,设置一个height属性,可以增加一些距离
最终实现代码:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
"Hello Flutter"
),
),
body: GYHomeContent()
)
);
}
}
class GYHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: [
GYProductItem("Apple1", "Macbook Product1",
"https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
GYProductItem("Apple2", "Macbook Product2",
"https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
GYProductItem("Apple3", "Macbook Product3",
"https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg")
],
),
);
}
}
class GYProductItem extends StatelessWidget {
final String title;
final String desc;
final String imageURL;
GYProductItem(this.title, this.desc, this.imageURL);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border.all()
),
child: Column(
children: [
Text(title, style: TextStyle(fontSize: 24)),
Text(desc, style: TextStyle(fontSize: 18)),
Image.network(imageURL)
],
),
);
}
}
|