前言
新手教学通过了,该打一场试试手了,不过先从人机开始吧。
上篇文章,已经能跑demo了,看了看代码,其实有点头大,怎么这么多括号????这么多嵌套有点像初学C语言的样子(当年有个同学嵌套了20层,那代码就像一幅抽象派大作。。)。
我上学的时候,就是不喜欢背东西,总觉得背下来的东西只是背下来而已,并不是真正的理解了其中的意义,所以作为一个资zhuang深bi程序员,先不着急把代码看懂,我们接着教程,先实实在在的把第一个APP写出来,写着写着,自然能明白一些。
代码
代码我就不啰嗦了 直接看教程就行了
https://flutterchina.club/get-started/codelab/
下面我就分布梳理一下
启动
//启动一个app
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
//dart 自动省略 new 关键字
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
primaryColor: Colors.white
),
home: MyHomePage(title: 'Flutter Demo Home Page ' + WordPair
.random()
.asPascalCase)
);
}
}
可以看到,app入口就是一个main函数,里面new了一个MyApp类。这里和java版的不一样,java的入口在Application类里面,而flutter的app则是继承了StatelessWidget这么一个东西。这个东西是什么,稍后再说。
还有就是flutter完全省略了xml,theme和layout完全写在代码当中。当然,带来的问题就是嵌套太多,虽然AS自动加了一段注释,但感觉还是没什么帮助。但是这给flutter带来了一项很重要的特性 – 热重载!
然后就是属性,像其中的MaterialApp,就相当于原来的theme,里面的各种属性都是通过类似于方法参数的方式进行添加,比如添加一个app标题,叫“Flutter Demo”。
从这部分我们可以看出,flutter的UI层级基本就是Widget,从根儿上就是一个widget。其实原版的android本来也就是从View开始,只不过我们实际上比较少直接处理rootView。app的启动就是从main入口启动一个APP级别的Widget。
首页
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
// 类 方法前 加上"_"前缀 将其标记为私有 peivate
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
///文档注释用三个斜杠 比java简单多了
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
void gotoStatefulWidget() {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return RandomWords();
}
)
);
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme
.of(context)
.textTheme
.display1,
),
IconButton(
icon: Icon(Icons.input),
tooltip: "Stateful widget",
onPressed: gotoStatefulWidget,
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
这是初始demo的首页,我们可以看到两个类,一个继承了StatefulWidget,另一个继承了State
<T>。StatefulWidget就是一个壳,State才是真正的核心。而State的核心就在于build函数return了什么东西。
StatefulWidget表示了一个可更新的查件,用过调用setState通知Widget进行重绘。setState里面实现具体重绘的逻辑。
Scaffold大概就是了一个封装好的UI框架,里面可以添加appBar、drawer、bottomBar等东西。body则是里面的实际内容。
Text、Button这些控件的各种属性也都是直接写在构造函数里的,这个之前的方式完全不一样。
跳转事件是用Navigator去处理的,和之前的方式也大相径庭。
另外就是一些语法的东西了,比如三斜杠注释、构造函数省略new、下划线前缀表示私有等等。
我这里和教学里的demo不大一样,保留了初始demo的首页,添加了一个按钮跳转到教学demo里的首页。
列表页
class RandomWords extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return RandomWordsState();
}
}
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _saved = Set<WordPair>();
TextStyle _biggerFont = TextStyle(fontSize: 18);
@override
Widget build(BuildContext context) {
// return Text(WordPair
// .random()
// .asPascalCase);
return Scaffold(
appBar: AppBar(title: Text("RandomWords"),
actions: <Widget>[IconButton(icon: Icon(Icons.list), onPressed: _pushSaved)],),
body: _buildSuggestions(),
// body: Center(
// child: Text(WordPair
// .random()
// .asPascalCase),
);
}
Widget _buildSuggestions() {
return ListView.builder(itemBuilder: (context, i) {
if (i.isEven) {
return Divider();
}
int index = i ~/ 2; // dart语法 做除法 取商数 但是向下取整 不加"~" 就变成了
//下面报错 type 'double' is not a subtype of type 'int' in type cast
//int index = (5 / 2) as int;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
},
padding: EdgeInsets.all(16),);
}
Widget _buildRow(WordPair pair) {
bool b = _saved.contains(pair);
return ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,),
trailing: Icon(
b ? Icons.favorite : Icons.favorite_border),
onTap: () {
setState(() {
//方法调用也能放进三目
b ? _saved.remove(pair) : _saved.add(pair);
});
},);
}
void _pushSaved() {
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
var tiles = _saved.map((pair) {
return ListTile(title: new Text(pair.asPascalCase, style: _biggerFont));
});
var divided = ListTile.divideTiles(tiles: tiles, context: context).toList();
return Scaffold(appBar: AppBar(title: Text("Saved Suggestions")),
body: new ListView(children: divided));
}));
}
}
到了这个界面,看到列表就惊呆了。写一个列表,竟然只要区区几行代码,如果是java版,呵呵,至少三个类吧(xml,activity,adapter,我说的是常规写法,就别跟我拧了)。一个ListView.builder就搞定了,嗯,真香!
看到后面,哇!更香了!利用Scaffold+ListView竟然就能直接启动一个列表页!
ListTile代表了一个item,分别用ListView.builder(itemBuilder)和new ListView(children: ListTile.divideTiles())两种方式创建列表。
不知道还有个地方你们有没有注意到,在dart语言中,方法直接可以当做一个函数的参数,方法的实现不依赖于某个类,这个很像lambda表达式。
总结
1.flutter通过main入口,new一个app的widget来启动app。
2.flutter的嵌套是一个特点,java转型的话比较费劲。
3.flutter的UI是通过widget来嵌套封装的。widget有两种:
- StatelessWidget
实现了一个不可变的控件,通过重写build函数实现。
- StatefulWidget
实现了一个可变的控件,通过重写createState函数返回一个继承State的类来实现。在需要改变的时候,调用setState实现改变的逻辑。
4.控件的各种属性直接在构造函数里写就完了。
5.dart的语法还是很方便的:
//单行函数
void main() => runApp(MyApp());
//构造函数省略new关键字
return MaterialApp();
//下划线前缀表示private
int _counter = 0;
//方法(function)可以直接当做一个参数
setState(() { _counter++; });
// $ 引用变量进行打印
Text('$_counter')
// 无参无返回值的函数 调用可以省略最后的空括号。
IconButton(onPressed: gotoStatefulWidget,)
//方法调用也能放进三目
b ? _saved.remove(pair) : _saved.add(pair);
///文档注释用三个斜杠 比java简单多了
void _incrementCounter() {}
目前这些东西也算是开眼了,知道了flutter的大体是如何编码的。
下一篇还是不急着研究flutter,先熟悉熟悉语法,把java和dart不一样的东西整理整理,正所谓磨刀不误砍柴工。