Flutter第1天--初始分析+Dart方言+Canvas简绘

Flutter七日游第一天:2018-12-16 天气:冷

零前言:

作为一名资深安卓业余爱好者(自诩),感觉应该入一下Flutter的坑了,
不管怎么说,新技术多少要了解一点,本系列就作为我的学习笔记吧
先把今天入坑的感觉写一写:

1
2
3
4
5
6
1.环境的搭建前人把雷踩得差不多了,也不是很麻烦
2.什么都没干呢,TM安装包28M...真把我吓一跳-----于是Flutter的"胖子"形象深入我心(不过非debug版能在10M之内,还是可以接受的)
3.Flutter热加载爽到爆,对于喜欢用真机的我,以前每次修改后-->确定安装-->打开...
4.单引号亮了,总算能像写其他语言那样少按个Shift了,字符串插值也很良心
5.flutter支持canvas,so我的四大战将(canvas,path,paint,贝塞尔)又能大显身手了,不过Api略有不同,也略显单薄
6.程序员有三件法宝:Ctrl+ Z(大胆改) , debug(细心查) , 类比(善分析)

一、Flutter初体验

1、下载Flutter的SDK

Android 的SDK要在环境变量配置一下:ANDROID_HOME
有什么问题可以在cmd用flutter doctor命令检查一下,对症下药

1
git clone -b beta https://github.com/flutter/flutter.git

2、配置环境变量

Flutter环境变量.png

1
2
PUB_HOSTED_URL=https://pub.flutter-io.cn
FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

3、AndroidStudio安装Dart和Flutter插件
1
setting-->plugins-->下方第二个-->搜索-->安装-->重启

4、新建项目

打开AS后就能看到新建一个Flutter项目,然后就写名字
initializing gradle 如果一直不动,android/gradle/wrapper/gradle-wrapper.properties
对应的gradle版本在http://services.gradle.org/distributions/自己下载,放在本地

第一个Flutter项目.png


二、第一次看初始项目的内心戏

1
2
3
4
5
6
7
8
android:我最熟悉的android
|---app
|---src
ios:暂时不鸟它
lib:
|---main.dart
test:顾名思义,测试包
.gitignore .metadata .packages pubspec.lock pubspec.yaml README.md 连包都没有,暂时不睬

1.看一下:android/app/src/main/AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<application
android:name="io.flutter.app.FlutterApplication"
android:label="my_flutter"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
//略...
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>

2.可见程序入口是:MainActivity.java

让我有一种libgdx的即视感

1
2
3
4
5
6
7
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}

3.GeneratedPluginRegistrant.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Generated file. Do not edit.
自动生成的文件,不要修改
*/
public final class GeneratedPluginRegistrant {
public static void registerWith(PluginRegistry registry) {
if (alreadyRegisteredWith(registry)) {//如已结婚,直接走人
return;
}
}
//貌似是判断是否已经和registry结成连理
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
if (registry.hasPlugin(key)) {//有结婚戒指,直接走人
return true;
}
registry.registrarFor(key);//带上戒指
return false;
}
}

4、是谁弄脏了我雪白的界面(显示)
4.1.MainActivity显然不是,怎么查在哪呢?

好吧在:main.dart里

搜索已有字符.png

1
2
3
4
5
6
7
8
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

------------//内心戏--------------
开面相对象的天眼一看:`void main() => runApp(MyApp());`
什么鬼,不像Python,不像JavaScript,更不像Java,但我仿佛知道它想对我什么:
我是入口函数,执行runApp函数,里面传入了个MyApp(),so,我是清白的,熊孩子是MyApp()

4.2.对MyApp的认知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

------------//内心戏--------------
看到class有种他乡遇故知的感觉,继承了StatelessWidget类并重写了其build方法
然后返回了一个Widget对象,并可以推理出MaterialApp()是一个Widget类对象
其中括号里的感觉非常像Python的字典或JavaScript的对象,不过用()包起来真怪怪的

按照一般的套路,左边是属性,右边是属性值,既然如此,玩玩呗.下面改了一下theme颜色
home里传入了一个MyHomePage,估计就是我们要找的人了,title改一下

flutter1.png


4.3.对MyHomePage的认知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

------------//内心戏--------------
MyHomePage也是StatefulWidget家的,第一句话感觉挺诡异,先mark一下
super(key: key)应该是说,key用它爸(即StatefulWidget)的,从上一步的入参title来看
this.title应该是入参的关键,so,这句话好像在说,我要两个参数,key从我爸那里拿

@override可以看出createState()是一个父类方法,_MyHomePageState是一个类
也就说明 _MyHomePageState()是一个对象,(ps:看到State直接想到React)

4.4.对_MyHomePageState的认知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;//定义变量

void _incrementCounter() {
setState(() {
_counter++;//定义变量++
});
}

------------//内心戏--------------
//结合JS和Python的经验,从这里可以看出,貌似加_的,是不想暴露在外的内部成员
_incrementCounter()显然是一个累加的方法,setState()里的东西让_counter++
setState个React这是一模一样,mark一下,估计会刷新界面

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),

------------//内心戏--------------
abstract class State<T extends StatefulWidget> extends Diagnosticable
State有一个StatefulWidget的泛型,也重写了build方法
[class Scaffold extends StatefulWidget] Scaffold也是StatefulWidget
现在焦点应该汇聚在StatefulWidget身上,很多地方都出现了,mark一下

Text(widget.title)----这里应该就是标题了,AppBar,顾名思义
body应该是身体,Center,中间,child,孩子,Column列,mainAxisAlignment,主轴对齐,
center中间,children孩子,Text文字:You have pushed the button this many times:
感觉蛮好玩的,拼在一起大概是,列作为孩子居中,并将文本作为孩子主轴对齐方式居中
4.5.floatingActionButton,这个安卓元素有
1
2
3
4
5
6
7
8
9
10
      floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

//onPressed:点击响应的函数 tooltip--长按显示文字 child--Icon加号图片

ok,这就是我第一次看Flutter代码时的感觉,mark了三处,
下面带着问题正式学一下Dart方言。


三、Dart语法一探

1、圆的周长
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const PI = 3.141592654; //const:编译时就是常量
//const double PI = 3.141592654;

final x = 50; //final修饰的变量只能被赋值一次(运行时)
//final int x = 100;

main() {
// int radius = 10;
var radius = 10;
//radius = 10.0;//Error--A value of type 'double' can't be assigned to a variable of type 'int'.
double c = getC(radius);
//支持三目运算符
bool isBig = c > x;
print(isBig ? "圆的周长大于${x}" : r"圆的周长\n小于${x}"*2);
//x=100 圆的周长\n小于${x}圆的周长\n小于${x}
//x=50 圆的周长大于50
}

// 获取圆的周长 radius : 半径
double getC(int radius) {
var c = 2 * PI * radius;
return c;
}

1.感觉const就像英雄天生天赋,final就像等级到了,选择英雄职业(不能转职)
2.r会将里面字符串原样打出,无视各空白符
3.字符串*2就打印两次,有点意思,差值表达式:${}和JS,kotlin相似
4.可以省略类型,但是若初始时赋值就不能再赋值其他类型,所以Dart并非弱类型语言!!!
但说它强又不怎么严谨,看下图,无力吐槽…(PS:原因:见后面dynamic类型)

mark2018-12-15 22-52-33.png


2.List的使用

支持多类型,API比java多一些
可以看成Java的ArrayList和数组的结合体,any,join等操作更像Python或js中的list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void baseUse() {
var list = [1, "a", "b", "c", true]; //支持多种类型
// var list=const[1,"a","b","c",true];
// var list =new List();

list[0] = "10"; //数组元素可修改成不同类型
var el = list[list.length - 1]; //获取--true
list.add("toly"); //尾增--[10, a, b, c, true, toly]
list.insert(1, true); //定点增--[10, true, a, b, c, true, toly]
list.remove("10"); //删除元素--[true, a, b, c, true, toly]
list.indexOf(true); //首出索引--1
list.lastIndexOf(true); //尾出索引--4
list.removeLast(); //移除尾--[true, a, b, c, true]
print(list.sublist(2)); //截取--[b, c, true]
print(list.sublist(2, 4)); //截取--[b, c]
print(list);
print(list.join("!")); //true!a!b!c!true
}

forEach、any、every、map
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void op() {
var numList = [3, 2, 1, 4, 5];
numList.sort();
print(numList); //排序--[1, 2, 3, 4, 5]

for (var value in numList) {
print(value); //1,2,3,4,5
}

numList.forEach(addOne); //2,3,4,5,6
numList.forEach((num) => print(num + 1)); //同上

var any = numList.any((num) => num > 3);
print(any); //只要有>3的任何元素,返回true

var every = numList.every((num) => num < 6);
print(every); //全部元素<6,返回true

var listX5 = numList.map((e) => e*=5);
print(listX5);//(5, 10, 15, 20, 25)
}

int addOne(int num) {
print(num + 1);
}

3.Map

这个不多说了,基本上与主流语言一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void baseUse() {
//创建映射表
var dict = {"a": "page1", "b": "page30", "c": "page70", "price": 40};
// var dict = new Map();
print(dict); //{a: page1, b: page30, c: page70, price: 40}
print(dict["price"]); //40
dict["a"] = "page2";
print(dict); //{a: page2, b: page30, c: page70, price: 40}
print(dict.containsKey("price")); //true
print(dict.containsValue("price")); //false
print(dict.isEmpty); //false
print(dict.isNotEmpty); //true
print(dict.length); //4
dict.remove("c");
print(dict);//{a: page2, b: page30, price: 40}
}

1
2
3
4
5
6
void op() {
var dict = {"a": "page1", "b": "page30", "c": "page70", "price": 40};
dict.keys.forEach(print); //a,b,c,price
dict.values.forEach(print); //a,b,c,price
dict.forEach((k, v) => (print("$k=$v"))); //这里用括号包着,好想吐槽...
}

4.dynamic(动态的)

原来是dynamic锅,让类型变成动态了

dynamic.png

int.png

1
2
3
4
5
6
7
8
9
dynamic d = 20;
d = "toly";

var list = new List<dynamic>();
list.add("1");
list.add(3);

var list2 = new List<int>();
//list2.add("toly");//ERROR:The argument type 'String' can't be assigned to the parameter type 'int'.

5.不同的东西
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//--------------------奇葩的~/----------
int a=10;
print(a/3);//3.3333333333333335
print(a~/3);//3

//--------------------奇葩的??=----------
int b = 9;
b = 5;
b ??= a; //----如果b空的则赋值
print(b); //5

//--------------------奇葩的??----------
int c = 10;
int d = 8;
var add10 = c = null ?? d + 10;//取第一个不为空的表达式
print(add10); //18

//--------------------简洁的=>----------
=> expr 等价于 {return expr;}

//--------------------好玩的{参数}----------
main() {
fun("toly");//toly,24,null
fun("toly", age: 24, sex: "男"); //toly,24,男
}

fun(String name, {int age=24, String sex}) {
print("$name,$age,$sex");
}

//--------------------好玩的[参数]----------
main() {
fun("toly"); //toly,null,null
fun2("toly", 24); //toly,24, 男
}

fun2(String name, [int age, String sex= "男"]) {
print("$name,$age,$sex");
}

//--------------------有趣的匿名方法----------
var power = (i) {
return i * i;
};
print(power(6)); //36

//--------------------这个理清楚,基本上匿名函数就OK了----------
var li = [1, 2, 3, 4, 5];
li.forEach((i) => print((i) {
return i * i;
}(i))); //1,4,9,16,25

6.类那点事
6.1:定义一个简单的类

PerSon(this.name, this.age)简化了Java中的那一坨,其他差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PerSon {
String name;
int age;

PerSon(this.name, this.age);

say(String name) {
print("are you ok $name");
}
}

main(){
var toly = new PerSon("toly", 24);
toly.say("ls");//are you ok ls
}

6.2:继承

注意语法形式

1
2
3
4
5
6
7
8
class Student extends PerSon {
String school;
Student(String name, int age, this.school) : super(name, age);
}

main() {
new Student("ls", 23, "星龙学院").say("toly");//are you ok toly
}

就先认知这么多吧,应该够玩一玩的了。


四、Canvas走起

新学一样东西,最好选择最熟悉的点切入,对我而言是绘制

1.找到画板在哪

有个CustomPainter类里有canvas,二话不说,继承之,为了避免看着乱,我新建了view包 view/star_view.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class StarView extends CustomPainter {

@override
void paint(Canvas canvas, Size size) {

}

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

2.StarView的用法

前面分析过,视图的呈现在MyHomePage中–>createState方法–>build返回的对象里
把文字的那块body改为CustomPaint就行了,FloatingActionButton就放着吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: CustomPaint(
painter: StarView(),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}

3.屏幕尺寸的获取

flutter中用的单位目测都是dp所以我用第三行那个,需要传入一个context
就在构造方法里传一下,刚好build里有个context,你用前两个除一下也行

1
2
3
window.physicalSize     //获取屏幕尺寸px----1080.0, 2196.0
window.devicePixelRatio //设备像素比----3
MediaQuery.of(context).size //获得的是dp单位:360.0, 732.0
1
2
3
4
//使用是传入context
body: CustomPaint(
painter: StarView(context),
),

4.网格走起:
4.1:StarView接收context,并初始化画笔
1
2
3
4
5
6
7
8
9
Paint mHelpPaint;
BuildContext context;

StarView(this.context) {
mHelpPaint = new Paint();
mHelpPaint.style=PaintingStyle.stroke;
mHelpPaint.color=Color(0xffBBC3C5);
mHelpPaint.isAntiAlias=true;
}

4.2:绘制网格路径

以前Android里面用的函数,修改了一些语法,给flutter用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 绘制网格路径
*
* @param step 小正方形边长
* @param winSize 屏幕尺寸
*/
Path gridPath(int step, Size winSize) {
Path path = new Path();

for (int i = 0; i < winSize.height / step + 1; i++) {
path.moveTo(0, step * i.toDouble());
path.lineTo(winSize.width, step * i.toDouble());
}

for (int i = 0; i < winSize.width / step + 1; i++) {
path.moveTo(step * i.toDouble(), 0);
path.lineTo(step * i.toDouble(), winSize.height);
}
return path;
}

4.3:绘制网格
1
2
3
4
@override
void paint(Canvas canvas, Size size) {
var winSize = MediaQuery.of(context).size;
canvas.drawPath(gridPath(20, winSize), mHelpPaint);

网格


4.4:坐标系绘制

坐标系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//绘制坐标系
drawCoo(Canvas canvas, Size coo, Size winSize) {
//初始化网格画笔
Paint paint = new Paint();
paint.strokeWidth = 2;
paint.style = PaintingStyle.stroke;

//绘制直线
canvas.drawPath(cooPath(coo, winSize), paint);
//左箭头
canvas.drawLine(new Offset(winSize.width, coo.height),
new Offset(winSize.width - 10, coo.height - 6), paint);
canvas.drawLine(new Offset(winSize.width, coo.height),
new Offset(winSize.width - 10, coo.height + 6), paint);
//下箭头
canvas.drawLine(new Offset(coo.width, winSize.height-90),
new Offset(coo.width - 6, winSize.height - 10-90), paint);
canvas.drawLine(new Offset(coo.width, winSize.height-90),
new Offset(coo.width + 6, winSize.height - 10-90), paint);
}

坐标系路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 坐标系路径
*
* @param coo 坐标点
* @param winSize 屏幕尺寸
* @return 坐标系路径
*/
Path cooPath(Size coo, Size winSize) {
Path path = new Path();
//x正半轴线
path.moveTo(coo.width, coo.height);
path.lineTo(winSize.width, coo.height);
//x负半轴线
path.moveTo(coo.width, coo.height);
path.lineTo(coo.width - winSize.width, coo.height);
//y负半轴线
path.moveTo(coo.width, coo.height);
path.lineTo(coo.width, coo.height - winSize.height);
//y负半轴线
path.moveTo(coo.width, coo.height);
path.lineTo(coo.width, winSize.height);
return path;
}

5.小结一下

感觉flutter里的Canvas很贫弱…好多api都没有,不知道是我没找到还是什么
canvas竟然没办法画文字,这不科学,mark一下。坐标系也就只能这样凑合一下了
还有Color用着挺别扭的,画线传参为什么非要Offset,连个重载都没有


6.绘制n角星

好吧,我又要拿星星来丢人现眼了
我已经n角星的java代码翻译成dart方言了

五角星分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* n角星路径
*
* @param num 几角星
* @param R 外接圆半径
* @param r 内接圆半径
* @return n角星路径
*/
Path nStarPath(int num, double R, double r) {
Path path = new Path();
double perDeg = 360 / num; //尖角的度数
double degA = perDeg / 2 / 2;
double degB = 360 / (num - 1) / 2 - degA / 2 + degA;

path.moveTo(cos(_rad(degA)) * R, (-sin(_rad(degA)) * R));
for (int i = 0; i < num; i++) {
path.lineTo(
cos(_rad(degA + perDeg * i)) * R, -sin(_rad(degA + perDeg * i)) * R);
path.lineTo(
cos(_rad(degB + perDeg * i)) * r, -sin(_rad(degB + perDeg * i)) * r);
}
path.close();
return path;
}

double _rad(double deg) {
return deg * pi / 180;
}
1
2
canvas.translate(160, 320);//移动到坐标系原点
canvas.drawPath(nStarPath(5,80,40), mPaint);

五角星.png


7.正n角星和正多边形
7.1:方法封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 画正n角星的路径:
*
* @param num 角数
* @param R 外接圆半径
* @return 画正n角星的路径
*/
Path regularStarPath(int num, double R) {
double degA, degB;
if (num % 2 == 1) {
//奇数和偶数角区别对待
degA = 360 / num / 2 / 2;
degB = 180 - degA - 360 / num / 2;
} else {
degA = 360 / num / 2;
degB = 180 - degA - 360 / num / 2;
}
double r = R * sin(_rad(degA)) / sin(_rad(degB));
return nStarPath(num, R, r);
}

/**
* 画正n边形的路径
*
* @param num 边数
* @param R 外接圆半径
* @return 画正n边形的路径
*/
Path regularPolygonPath(int num, double R) {
double r = R * cos(_rad(360 / num / 2)); //!!一点解决
return nStarPath(num, R, r);
}

7.2.批量绘制:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
canvas.translate(0, 320);

canvas.save();//绘制n角星
for (int i = 5; i < 10; i++) {
canvas.translate(64, 0);
canvas.drawPath(nStarPath(i, 30, 15), mPaint);
}
canvas.restore();

canvas.translate(0, 70);
canvas.save();//绘制正n角星
for (int i = 5; i < 10; i++) {
canvas.translate(64, 0);
canvas.drawPath(regularStarPath(i, 30), mPaint);
}
canvas.restore();

canvas.translate(0, 70);
canvas.save();//绘制正n边形
for (int i = 5; i < 10; i++) {
canvas.translate(64, 0);
canvas.drawPath(regularPolygonPath(i, 30), mPaint);
}
canvas.restore();

n角星与n边形.png


8.状态控制,点击随机色

第一个按钮的fab点击更改数字,这里换成颜色试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//-----------main.dart-------------------
Color _color = Colors.black;

void _changeColor() {
setState(() {
_color=randomRGB();
});
}

body: CustomPaint(
painter: StarView(context,_color),
),
floatingActionButton: FloatingActionButton(
onPressed: _changeColor,
tooltip: 'Increment',
child: Icon(Icons.add),
),
//-----------随机颜色-------------------
Color randomRGB(){
Random random = new Random();
int r = 30 + random.nextInt(200);
int g = 30 + random.nextInt(200);
int b = 30 + random.nextInt(200);
return Color.fromARGB(255, r, g, b);
}

//-----------star_view.dart-------------------
StarView(this.context,Color color) {
print(color);
mPaint = new Paint();
mPaint.color = color;
}

点击改变状态.gif


五、汇集一下今天的mark

经过初始项目的分析以及Dart方言的简单入门,再加上Canvas的绘制
基本上熟悉了Dart的语法与Flutter的套路(和React很像),第一天就这样吧

1
2
1.setState和React这是一模一样,mark一下,估计会刷新界面
----经过测试,是的,调用setState会重新绘制界面,和React一样
1
2
3
4
5
2.MyHomePage也是StatefulWidget家的,第一句话感觉挺诡异,先mark一下
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

----根据继承的语法以及{}的可选参数,不难理解,key是从老爸那拿的
1
2
3
4
5
3.现在焦点应该汇聚在StatefulWidget身上,很多地方都出现了,mark一下
---保持mark

4.canvas竟然没办法画文字,这不科学,mark一下
---保持mark
张风捷特烈 wechat
-------------本文结束感谢您的阅读 @张风捷特烈-------------
0%