二、Flutter之初识Widget
一、Dart学习
开始写Flutter项目前,建议先学一下Dart,尽管和java很相似,如果学过kotlin会更有帮助。但系统的学习更有利于后面项目的上手,推荐 https://www.jianshu.com/p/3d927a7bf020 系列。当然干读教程是枯燥且低效的,建议和Widget的学习同时进行。
二、flutter文件结构
如上图所示,我们在AS创建一个新的flutter application后生成的文件结构如图:
其中android、ios是对应android、ios平台的,暂时不用管;
我们写的主要代码在lib下,目前就一个main.dart;
图中的images是我自己新建的一个文件夹,用来放资源图片的;
pubspec.yaml是主要的配置文件。
三、flutter布局学习
参考:
官方英文版 https://flutter.io/docs/get-started/flutter-for/android-devs
中文版 https://flutterchina.club/flutter-for-android/
官方对Widget的介绍比较系统,可以慢慢学习。
以及别人的flutter学习教程 https://blog.csdn.net/duo_shine/article/category/7882610/2
- 1、一个简单的demo
在上一步生成的demo对于我这种零基础的看起来还是十分头大,遂重新写了个更简洁的demo一步步深入,如下是一个最简单的只有一个居中text。
import 'package:flutter/material.dart';
void main() {
//runApp接受的widget将成为widget树的根容器
runApp(
//此处的Center即顶层容器,Text是它的子容器,这里就显示了一行text
new Center(
child: new Text(
'Hello World',
textDirection: TextDirection.ltr,//左对齐,必须
),
),
);
}
- 2、嵌套widget
拓展一下,顶层容器改为demo中出现过的MaterialApp,这是Material Design风格的容器,里面包含MD风格的各种控件,详见 https://www.jianshu.com/p/1d44ae246652,修改runApp()内代码:
runApp(new MaterialApp(
//title用于任务管理器切换时
title: "我的应用",
home: new Center(
child: new Text(
'Hello World',
textDirection: TextDirection.ltr, //左对齐,必须
),
),
));
- 3、用MaterialApp和Scaffold
UI上变化也不大,还是一行文字,样式跟着MaterialApp修改了,但是这里可以看出flutter是如何嵌套包裹widget的。注意这里因为修改void main()里的内容,所以热重载无法使用,需要重新运行编译才行。
现在的UI看起来还是很简陋,而dmeo里MaterialApp.home用的是Scaffold,我们也替换试试,顺便添加theme控制主题颜色:
void main() {
runApp(
new MaterialApp(
//title用于任务管理器切换时
title: "我的应用",
//使用主题更改Ui
theme: new ThemeData(
primaryColor: Colors.deepOrange,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text("It is Scaffold AppBar"),
),
body: new Center(
//Text控件显示各种文本
child: new Text("Hello World"),
)),
),
);
}
生成了一个有状态栏,白色底,中间单行文字的页面。
- 4、自定义类,将APP和根容器封装
上面的代码都挤在void main()里很不优雅,我们把它抽出来一个个封装自定义,先拿出home对应的new Scaffold()封装成MyRootView,再把runApp()里的MaterialApp()抽出封装成MyApp:
import 'package:flutter/material.dart';
void main() {
runApp(
new MyApp()
);
}
//封装app
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
//title用于任务管理器切换时
title: "我的应用",
//使用主题更改Ui
theme: new ThemeData(
primaryColor: Colors.orange,
),
home: new MyHomeView(),
);
}
}
//封装home里的容器
class MyHomeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Material(
child: new Scaffold(
appBar: new AppBar(
title: new Text("It is Scaffold AppBar"),
),
body: new Center(
//Text控件显示各种文本
child: new Text("Hello World 2"),
)),
);
}
}
此时你去修改文字或修改主题色,再ctrl+s都可以热重载直接更新了。
四、Widget学习
Widget类似Android中的View,它是flutter中所有控件的基础,上面的Text、MaterialApp等都是Widget。官方的Widget目录:https://flutterchina.club/widgets/。
class RowSample extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
new Text('text 1', textAlign: TextAlign.center),
new Text( "text 2",
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis, // 溢出显示。。。
style: TextStyle(fontSize: 30.0,// 文字大小
color: Colors.yellow),// 文字颜色
),
new RichText(
text: TextSpan(
text: 'textSpan ',
style: DefaultTextStyle.of(context).style,
children: <TextSpan>[
TextSpan(text: 'bold', style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: ' world!'),
],
),
),
],
);
}
}
这是自定义的Row布局,MainAxisAlignment.spaceAround设置为每个子widget都被空白包裹且空白间距一样;子Widget包括Text、RichText,相关属性见注释。
Column也差不多类似,只是排列方向为竖向的。一般可用Row、Column作为基本布局进行排列,像Android里的LinearLayout。
- 2、输入框TextField、按钮RaisedButton
/**
* demo TextFiled + RaiseButton
*/
//这是TextController,通过它可以从外部访问到该Text的值。下划线前缀标识符,会强制其变成私有的
final TextEditingController _controller =
new TextEditingController.fromValue(new TextEditingValue(text: "Leon"));
//把TextFiled和RaiseButton控件放在Row里
class TextFieldSample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Container(
//此处必须把TextField用Container包裹,否则会报错
width: MediaQuery.of(context).size.width - 100.0,
// Subtract sums of paddings and margins from actual width
child: new TextField(
//TextField的设置
controller: _controller,
decoration: new InputDecoration(
labelText: '请你输入姓名',
labelStyle:
new TextStyle(fontSize: 13.0, color: Colors.redAccent),
),
textAlign: TextAlign.start,
),
),
//自定义按钮
new RaisedButton(
onPressed: _printName,
color: Colors.yellow,
textColor: Colors.red,
highlightElevation: 20,
//Button里的文字也是需要自己构建一个Text
child: new Text(
"ClickMe",
style: new TextStyle(
color: Colors.red,
),
),
),
],
);
}
}
//自定义方法在Console输出。下划线前缀标识符,会强制其变成私有的
void _printName() {
print("Your name is ${_controller.text}");
}
在一个Row里就放了一个文本输入框和一个按钮,具体属性后面再慢慢摸索,要注意:
a、flutter没有像Android的findViewById,要获取输入框的text值怎么办呢,用TextEditingController,并且得在布局前先声明好,在布局中设置controller: _controller,
才行;
b、很多时候组件是不能直接放在布局里的,要先装在容器里,像这里的TextField就要先用Container包裹,很多时候布局报错是因为这个原因;
c、dart里没有private、public关键字,在变量、方法名加前缀"_'就表示强制性转为私有的;
d、dart是面向对象的语言,数值、变量、方法都是对象,我们的布局也是对一个个对象进行操作,所以像
labelStyle:new TextStyle(fontSize: 13.0, color: Colors.redAccent),
是需要我们自己构建一个TextStyle对象。
child: new Text(
"ClickMe",
style: new TextStyle(
color: Colors.red,
),
),
不像android里text="ClickMe"就行了,而是要给它一个Text对象,Text对象要你自定义。
- 3、ListView
/**
* demo ListView
*/
List _listItems = List.generate(20, (j) => j * j);
class ListViewSample extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (null == _listItems || _listItems.length == 0) {
return null;
}
return new Expanded(
child: new ListView.builder(
scrollDirection: Axis.vertical,
itemCount: _listItems.length,
itemBuilder: (context, index) {
return new ListTile(
title: new Text('${_listItems[index]}'),
);
},
),
);
}
}
这是一个最简单的ListView,代码量相对于Andoird里要设置Adapter等省了很多。注意这里ListViewSample我是装在Column里的,所以要用Expanded包裹,否则会报错Vertical viewport was given unbounded
。而数组初始化用了lamada表达式写的闭包函数,非常简洁。
- 4、页面跳转返回
Flutter里一般用Navigator控制页面跳转返回,对之前代码稍加修改,在FloatActionButton里添加跳转逻辑:
/**
* 根页面
*/
class MyHomeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Material(
child: new Scaffold(
appBar: new AppBar(
title: new Text("It is Scaffold AppBar"),
),
//主体
body: new Column(
children: <Widget>[
new TextSample(), //Text
new TextFieldSample(), //TextField输入框
],
),
//FAB按钮
floatingActionButton: new FloatingActionButton(
tooltip: "跳转",
child: new Icon(Icons.arrow_forward),
onPressed: () {
//跳转到第二页面
Navigator.push(context,
new MaterialPageRoute(builder: (context) => new SecondScreen()));
},
backgroundColor: Colors.pink,
),
));
}
}
/**
* 第二个页面
*/
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Material(
child: new Scaffold(
appBar: new AppBar(
title: new Text("It is SecondScreen"),
),
//主体
body: new Flex(
direction: Axis.vertical,
children: <Widget>[
new ListViewSample(),
],
),
//FAB按钮
floatingActionButton: new FloatingActionButton(
tooltip: "返回",
child: new Icon(Icons.arrow_back),
onPressed: () {
//这里是返回到上一页
Navigator.pop(context);
},
backgroundColor: Colors.pink,
),
));
}
}
推荐一个Flutter快捷键汇总:https://react-juejin.foreversnsd.cn/post/5c5d970e6fb9a049af6db7cd 让开发事半功倍