Repository files navigation

一、Dart基础

1、一些重要的概念

  • Dart 所有的东西都是对象,即使是数字、函数、null也都是对象。所有的对象都继承自 Object 类。
  • Dart 动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下该类型是 dynamic(动态的)。
  • Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
  • Dart 没有public、private和protected关键字。如果标识符以下划线(_)开头,则该标识符对其库是私有的。
  • 没有初始化的变量都会被赋予默认值 null。
  • final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。

2、Dart 中的关键字

关键字

3、常量与变量

name的类型被推断为String:

var name = 'Bob';

如果对象不限于单一类型,或没有明确的类型,可用dynamic/Object:

Object name1 ='zf';
print('name1: ${name1.runtimeType}'); // String
name1 = ['a', 'b'];
print('name1: ${name1.runtimeType}'); // List<String>dynamic name2 ='zf';
print('name2: ${name2.runtimeType}'); // String
name2 =123;
print('name2: ${name2.runtimeType}'); // int

显式声明变量的类型:

String name = 'Bob';

所有未初始化的变量默认值都是null

int lineCount;
// 在生产环境,assert()会被忽略。// 在开发环境,assert(condition)的condition不为真时抛出一个异常。assert(lineCount ==null);

如果某个变量只打算设置一次值,请使用final或const修饰。final变量只能被初始化一次,const变量是一个编译时常量(const变量是隐式的final)。final修饰的变量在第一次声明的时候就要初始化。被final或者const修饰的变量,类型可以省略,但建议指定类型:

// 可以省略String这个类型声明final name0 ='Bob';
finalString name1 ='Bob';
const name2 ='Bob';
constString name3 ='Bob';

被final或const修饰的变量值不能再修改。final或const不能和var同时使用。常量如果是类级别的,建议使用static const:

static const String name = 'Tom';

const关键字不只是声明常数变量,还可以用来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有一个常量值:

// 注意:[]创建的是一个空的list集合// const []创建一个空的,不可变的列表var varList =const [];
final finalList =const [];
const constList =const [];
// 可以更改非final/const变量的值,即便它曾经具有const的值
varList = ['hahaha'];

在常量表达式中,final/const的操作对象必须为bool、num、string或null,const常量必须用const类型的值初始化:

constString name0 ='a';
finalString name1 ='a';
constString name2 ='a';
const aConstList =const ['1', '2'];
// 报错:Const variables must be initialized with a constant valueconst validConstString0 ='$name0 $name1';
// 报错:In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'const validConstString1 ='$name0 $name2 $aConstList';
// 正确写法const validConstString2 ='$name0 $name2';

4、数据类型

  • num: 是数字类型的父类,有两个子类 int 和 double
int a =1;
print(a);
double b =1.11;
print(b);
// String -> intint c =int.parse('1');
// String -> doubledouble d =double.parse('1.11');
// int -> StringString str1 = a.toString();
// 报错:The argument type 'int' can't be assigned to the parameter type 'String'print(str1 + a);
// 正确// print('${str1} ${a}');print(str1 + a.toString());
// double -> String,// toStringAsFixed()// 参数表示要保留几位小数,参数必填// 填写的整数值大于小数的小数点位数时,会补0// 返回结果四舍五入double num0 =3.141592653;
double num1 =3.14;
String str0 = num0.toStringAsFixed(0);
String str1 = num0.toStringAsFixed(3);
String str2 = num1.toStringAsFixed(4);
// str0 = 3, str1 = 3.142, str2 = 3.1400print('str0 = ${str0}, str1 = ${str1}, str2 = ${str2}');
// toStringAsExponential()// 科学计数,参数可为空,参数表示要保留几位小数,会四舍五入double num2 =12345.678;
String str3 = num2.toStringAsExponential();
String str4 = num2.toStringAsExponential(0);
String str5 = num2.toStringAsExponential(4);
// str3 = 1.2345678e+4, str4 = 1e+4, str5 = 1.2346e+4print('str3 = ${str3}, str4 = ${str4}, str5 = ${str5}');
// toStringAsPrecision()// 参数必填且>0, 表示有效数字的位数,也会四舍五入double num3 =123.654;
int num4 =1;
String str6 = num3.toStringAsPrecision(3);
String str7 = num3.toStringAsPrecision(1);
String str8 = num4.toStringAsPrecision(2);
// str6 = 124, str7 = 1e+2, str8 = 1.0print('str6 = ${str6}, str7 = ${str7}, str8 = ${str8}');
  • String: 可以使用单引号/双引号创建字符串;引号可以嵌套使用
String str1 ='aaaaa';
String str2 ='bbbbb';
int a =1;
String str3 ='$str1 a "b" ${str2.toUpperCase()} ${a.toString()}';
  • bool
    • Dart 是强bool类型检查,只有bool类型的值是true,才被认为是true
    • 只有两个对象具有bool类型:true和false,它们都是编译时常量
    • assert 是语言内置的断言函数,仅在检查模式下有效。条件不为真则引发异常
var fullName ='';
assert(fullName.isEmpty);
var a =0/0;
assert(a.isNaN);
var b;
assert(b ==null);
  • List集合: 元素类型可以是任意类型
// 使用List的构造函数。如果传递int参数,表示List固定长度,不可增删操作List list0 =newList();
List list1 = ['a', 10, true, null, ['bala', 'heihei']];
// 添加一个元素
list1.add('apple');
// 添加多个元素
list1.addAll(['orange', 'banana']);
// 获取List的长度/首个元素/最后一个元素/指定下标元素/某个元素的索引print('${list1.length}, ${list1.first}, ${list1.last}, ${list1[0]}, ${list1.indexOf('apple')}');
// 删除指定位置的元素,返回被删除的元素print(list1.removeAt(0));
// 删除指定元素,返回true/false。如果集合中有多个相同元素,只会删除第一个
list1.remove('apple');
// 删除最后一个元素,返回被删除的元素print(list1.removeLast());
// 删除指定范围的元素,含头不含尾,没有返回值
list1.removeRange(0, 1);
// 删除指定条件的元素,没有返回值
list1.removeWhere((item) => item.runtimeType ==String);
// 删除所有的元素,没有返回值
list1.clear();
  • Map集合: map的键和值可以是任何类型的对象
// 这种方式初始化map时,key不能相同,否则报错:Two keys in a map literal can't be equal.Map map0 = {'name':'aaa', 'age':18, 111:true, null:null, ['a']: {'xxx':'yyy'}};
// 另一种创建方式Map map1 =newMap();
map1['name'] ='aaa';
map1['age'] =18;
// 指定键值对的参数类型。如果指定了key/value类型,赋值时必须为指定的类型Map map2 =newMap<int, String>();
// 对同一key进行赋值会被覆盖
map2[1] ='abc';
map2[1] ='aaa';
// map里面的value和key都可以是null
map2[2] =null;
// 检索Map是否含有某keyassert(map2.containsKey(1));
// 删除某个键值对(参数为key值)
map2.remove(1);

5、异常(Exception)

  • throw

抛出固定类型的异常

throw new FormatException('Expected at least 1 section');

抛出任意类型的异常

throw 'Out of memory!';

  • on / catch

将可能出现异常的代码放到 try 语句中,可以通过 on 语句来指定需要捕获的异常类型;使用 catch 可以捕获到任意异常类型。rethrow可以重新抛出捕获的异常

test1() {
try {
test2();
} catch (e) {
print('test1 -- e -- ${e}');
}
}
test2() {
try {
throwFormatException('this is exception');
} onOutOfMemoryError { // 可以捕获指定类型的异常print('out of memory');
} onFormatExceptioncatch (e) { // 匹配不到FormatException,会继续匹配print('exception error: ${e}');
// 重新抛出异常,可以在test1的catch中重新捕获异常rethrow; } catch (e, r) { // 匹配所有类型的异常:e是异常对象,r时异常的堆栈信息print('exception details: ${e}');
print('stack trace: ${r}');
} finally { // 一定会执行,如果try/catch中有return/rethrow,会在return/rethrow之前执行print('this is finally');
}
}

6、函数(Function)

Dart 的函数有两种类型的参数:必需的和可选的。可选参数可以有一个默认值,在调用者没有指定可选参数时使用默认值。可选参数又分为:可选命名参数和可选位置参数。

  • 可选命名参数: 使用{}包围的参数属于可选命名参数,可设置默认值,在调用的时候,需要使用paramName:value的形式指定为哪个可选参数赋值。
void_testPar1(int a, {String name, int age =10}) {
print('a = ${a}, name = ${name}, age = ${age}');
}
_testPar1(123, name:'abc'); // a = 123, name = abc, age = 10_testPar1(123, age:18); // a = 123, name = null, age = 18_testPar1(123, name:'aaa', age:18); // a = 123, name = aaa, age = 18
  • 可选位置参数: 使用[]包围的参数属于可选位置参数。如果想指定某个位置上的参数,必须保证前面位置有参数(设置了默认参数也需要传)。
void _testPar2 (int a, [String str ='abc', int b]) {
print('a = ${a}, str = ${str}, b = ${b}');
}
_testPar2(123); // a = 123, str = abc, b = null_testPar2(123, 'beijing'); // a = 123, str = beijing, b = null_testPar2(123, 'beijing', 10); // a = 123, str = beijing, b = 10_testPar2(123, 10); // 会报错

7、类的构造函数

classPoint {
int x;
int y;
// 构造函数Point(int a, int b) {
x = a;
y = b;
}
// 上面的构造函数可以简化为:Point(this.x, this.y);
// 命名构造函数Point.fromJson(Map data) {
x = data['x'];
y = data['y'];
}
// 可以在构造函数打括号前来初始化实例的变量值,多个实例变量用逗号隔开Point.fromJson(Map data) : x = data['x'], y = data['y'];
}

8、Dart 中的异步

  • Event-Looper

    Dart 是单线程模型,也就没有所谓的主线程/子线程。 Dart 也是Event-Looper以及Event-Queue的模型,所有的事件都是通过EventLooper依次执行。 Dart 的Event Loop是:

    • 从EventQueue中获取Event
    • 处理Event
    • 直到EventQueue为空

    Event Queue

    这些Event包括了用户输入,点击,Timer,文件IO等

    Event Queue

  • 单线程模型

    Dart 中没有线程的概念,只有isolate,每个isolate都是隔离的,并不会共享内存。一个 Dart 程序是在Main isolate的main函数开始,在main函数结束后,Main isolate开始一个一个的处理Event Queue中的每一个Event。

    Event Queue

  • Event Queue以及Microtask Queue

    Dart 中的Main isolate只有一个Event Looper,但存在两个Queue:Event Queue和Microtask Queue。Microtask Queue存在的意义是:

    希望通过这个Queue来处理稍晚一些,但是在下一个消息到来之前需要处理完的事情。

    当Event Looper正在处理Microtask Queue中的Event时,Event Queue中的Event就停止了处理,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等。

    Event-Looper挑选Task的执行顺序为:

    • 优先全部执行完Microtask Queue中的Event
    • 直到Microtask Queue为空时,再执行Event Queue中的Event

    Event Queue

    Dart 中只知道Event处理的先后顺序,并不知道某个Event执行的具体时间点。

  • 异步任务调度

    当有代码可以在后续任务执行的时候,有两种方式,通过 dart:async 中的API即可

    • 使用Future类,可以将任务加入到Event Queue的队尾
    // 一般常用的Future构造函数newFuture(() {
    // do something
    });
    // 当需要将任务拆分成很多小任务一步一步执行时,可以使用Future.then来拆解任务newFuture(() {
    print('abc');
    return21;
    }).then((value) =>'!!!then01 $value!!!') //这里的value是上面任务的返回值
    .then((value) {
    // 这里的value为上一个任务执行完的返回结果print('===then02 $value==='); // ===then02 !!!then01 21!!!===return'balabala';
    }).then((value){
    // 这里的value是上个任务的返回值print('~~~then03 $value~~~~'); // ~~~then03 balabala~~~~
    }).whenComplete(() {
    print('complete');
    });
    // 当任务需要延时执行,可以使用Future.delay。不是绝对精准的。newFuture.delayed(constDuration(seconds:1), () {print('123');});
    • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾
     import 'dart:async';
    voidmain() {
    newFuture.delayed(constDuration(seconds:1), () {print('delay');});
    print('main---111');
    scheduleMicrotask(() {
    print('microtask---111');
    });
    newFuture(() {
    print('future');
    return21;
    }).then((value) {
    print('future-then-$value');
    }).whenComplete(() {
    print('complete');
    });
    scheduleMicrotask(() {
    print('microtask---222');
    });
    print('main---222');
    }

    上面代码的输出顺序为:

    main---111 main---222 microtask---111 microtask---222 future future-then-21 complete delay

    • 再看一个较复杂的例子
    scheduleMicrotask(() =>print('000'));
    newFuture(() =>print('111'))
    .then((_) {
    print('222');
    scheduleMicrotask(() =>print('333'));
    }).then((_) =>print('444'));
    scheduleMicrotask(() =>print('555'));
    newFuture(() =>print('666'))
    .then((_) {
    print('777');
    Future(() =>print('888'));
    }).then((_) =>print('999'));
    newFuture(() =>print('aaa'));
    scheduleMicrotask(() =>print('bbb'));

    上面输出顺序为:0-5-b-1-2-4-3-6-7-9-a-8

  • async和await

    // async关键字声明该函数内部有延时执行的任务asyncAndAwait() async {
    // 在Dart中,有await修饰的,其结果是一个Future对象var a =awaittest0();
    print('a-type:${a.runtimeType}');
    print('a = ${a}');
    var b =awaittest1();
    print('b-type:${b.runtimeType}');
    print('b = ${b}');
    }
    test0() {
    returnFuture(() =>'22222');
    }
    test1() {
    returnFuture(() =>'22222')
    .then((value) {
    print('then-value=${value}');
    }).whenComplete(() {
    print('complete~');
    });
    }

    上面代码的输出顺序依次为:

    a-type:String a = 11111 then-value=22222 complete~ b-type:Null b = null

二、Widget基础篇

1、Widget State

Flutter 中,一切能显示的都是 WidgetWidget 是一切的基础,作为响应式的渲染,属于 MVVM 的实现机制。我们可以通过修改数据,再用 setState 设置数据, Flutter 会自动通过绑定的数据更新 Widget 。我们需要做的就是实现 Widget 页面,并且和数据绑定起来。

Widget 分为 有状态无状态 两种。在 Flutter 中每个页面都是一帧。无状态就是保持在那一帧;有状态的 Widget 当数据更新时,其实就是绘制了新的 Widget

  • 无状态 StatelessWidget

    无状态的 Widget 实现很简单:创建一个继承自 StatelessWidgetWidget ,并通过 build 方法返回一个布局好的控件。WidgetWidget 之间通过 childchildren 进行嵌套。

import'package:flutter/material.dart';
classMyStatelessextendsStatelessWidget {
finalString text;
MyStateless(this.text);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:Text('StatelessWidget'),
),
body:Container(
color:Colors.white,
//Dart语法中,?? 表示如果text为空,就返回 ?? 后的内容。
child:Text(text ??"这就是无状态DMEO"),
),
);
}
}
  • 有状态 StatefulWidget

有状态的 Widget 需要管理 state ,通过 setState 改变数据触发 Widget 重新构建刷新。主要的生命周期有:

initState: 初始化 didChangeDependencies:initState 之后 build 之前调用 dispose: 销毁

classMyStatefulextendsStatefulWidget {
@overrideState<StatefulWidget> createState() {
return_MyStatefulState();
}
}
class_MyStatefulStateextendsState<MyStateful> {
int pressCount;
@overridevoidinitState() {
super.initState();
pressCount =0;
}
@overrideWidgetbuild(BuildContext context) {
returnContainer(
child:FlatButton(
onPressed: () {
setState(() {
pressCount +=1;
});
},
child:Center(
child:Text('有状态的Widget,可戳👇--$pressCount'),
)
),
);
}
}

不管是有状态的还是无状态的 Widget ,我们需要做的就是在 build 中堆积我们的布局,把数据添加到 Widget 中,有状态的可以通过 setState 改变数据,从而刷新UI,就这么简单。

2、Flutter中的Widget整理

先来看一张图,这张图中几乎包含了 Flutter 中所有的 Widget

我们先针对其中比较常用的讲解下其特性和使用方法。

3、布局相关的常用Widget

  • Container

    最常用的布局。只能包含一个 child ,默认铺满。支持配置 paddingmargincolor 、宽高、decoration (一般配置边框和阴影等)。不是所有的控件都有 paddingmargincolor 、宽高等属性,所以才会有 PaddingCenterWidget 的存在。

    alignment: 设置Container内child的对其方式(注意不是Container本身的对其方式)

    width/height/color: 设置Container的宽高和背景色

    padding/margin: padding设置Container边缘和其child内容的距离,margin设置Container和其它外部Widget的距离。

    decoration: Container的修饰器,主要设置背景、边框、圆角等。注意:如果设置了该属性,就不能设置Container的color,否则会抛出异常

    _containerDemo() {
    returnContainer(
    alignment:Alignment.center,
    child:Text('balabalabala'),
    height:49,
    margin:EdgeInsets.all(20),
    padding:EdgeInsets.only(top:10),
    // color: Colors.blueGrey,// 使用它就不能设置Container的color
    decoration:BoxDecoration(
    borderRadius:BorderRadius.only(
    topLeft:Radius.circular(10),
    bottomRight:Radius.circular(10)
    ),
    color:Colors.blue,
    border:Border.all(
    color:Color(0xFF000000),
    width:1
    )
    ),
    );
    }
  • Column、Row和Expanded

    竖直布局和水平布局也是很常见的场景。它们都可以通过 children 设置多个子 Widget ,可以设置主轴和副轴方向布局方式等。而 ExpendedColumnRow 中默认代表着平均充满。当然可以通过设置 flex 属性决定占屏比例。

    _columnDemo() {
    returnColumn(
    children:<Widget>[
    Expanded(child:Container(
    child:Text('aaa'),
    color:Colors.grey,
    )),
    Expanded(child:Container(
    child:Text('bbb'),
    color:Colors.blueGrey,
    )),
    Expanded(
    child:Container(
    child:Text('ccc'),
    color:Colors.red
    ),
    flex:2
    )
    ],
    // 主轴方向布局方式
    mainAxisAlignment:MainAxisAlignment.center,
    // 大小按照最大充满(这里使用Expanded会最大充满不受该属性设置影响)
    mainAxisSize :MainAxisSize.min,
    // 副轴方向布局方式
    crossAxisAlignment:CrossAxisAlignment.center
    );
    }

4、其它常用Widget

Flutter 中除了布局的 Widget ,还有交互显示和完整页面呈现的 Widget 。其中常见的有 MaterialAppScaffoldAppbarTextImageFlatButton 等。

常用Widget及特点

  • Text

    textAlign: 设置文本对其方式:center、left、right、start、end

    maxLines: 设置最多显示的行数

    overflow: 文本溢出时怎么处理。clip:直接切断;ellipsis:...;fade:渐变(上下渐变,不是左右)

    style: 通过TextStyle设置字体大小、颜色、weight等

    _textDemo() {
    returnContainer(
    width:100,
    child:Text(
    'balabalabalabalabalabalabalabalabalabalabalabalabalabalabala',
    textAlign:TextAlign.end,
    style:TextStyle(
    fontSize:20,
    color:Colors.white,
    fontWeight:FontWeight.w500
    ),
    maxLines:1,
    overflow:TextOverflow.fade,
    )
    );
    }
  • image

    加载图片有四种方式分别为:

    Image.asset: 加载项目资源目录中的图片,相对路径

    Image.network: 加载网络图片

    Image.file: 加载本地图片,绝对路径

    Image.memory: 加载Uint8List资源图片(没使用过😢)

    通过fit属性设置图片的填充方式:

    BoxFit.fill: 图片拉伸以充满父容器

    BoxFit.contain: 比例不变拉伸,可能会有空隙(水平和竖直方向最多有一个方向有空隙)

    BoxFit.cover: 可能被裁减,比例不变拉伸,充满父容器

    BoxFit.fitWidth/fitHeight: 宽度/高度充满

    BoxFit.scaleDown: 效果和contain类似,但图片不会被拉伸超过源图片大小

    _imageDemo() {
    returnContainer(
    alignment:Alignment.center,
    width:300,
    height:400,
    color:Colors.blue,
    child:Image.network(
    'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2527385882,2413489660&fm=26&gp=0.jpg',
    fit:BoxFit.contain,
    )
    );
    }
  • TextField

    TextFieldCupertinoTextField 是两种不同风格的 textField ,两者样式有很大差异,用法略有不同。先来看下 TextField 的基本使用:

    _textFieldDemo0() {
    returnTextField(
    maxLength:30,
    maxLines:1,
    // 是否自动聚焦
    autofocus:false,
    // 是否为密码输入框
    obscureText:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    // 装饰效果
    decoration:InputDecoration(
    // decoration是否使用fillColor填充,默认为false
    filled:false,
    fillColor:Colors.red,
    hintText:'placeholder',
    helperText:'helperText',
    labelText:'labelText'
    ),
    enabled:true,
    // 在onSubmitted之前调用。实现该方法后,点键盘return输入框不会失去焦点
    onEditingComplete: () {
    print('TextField:complete');
    }
    );
    }

    下面 CupertinoTextField (iOS风格的TextField)的基本使用:

    _textFieldDemo1() {
    returnCupertinoTextField(
    maxLength:200,
    maxLines:1,
    autofocus:false,
    // 密码
    obscureText:false,
    autocorrect:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    placeholder:'placeholder',
    clearButtonMode:OverlayVisibilityMode.editing,
    enabled:true,
    // 设置光标宽度,默认为2
    cursorWidth:2,
    // 设置光标颜色,默认为blue
    cursorColor:Colors.red,
    // 在onSubmitted之前调用。实现了该方法后,按return不会让textField失去焦点
    onEditingComplete: () {
    print('CupertinoTextField:complete');
    }
    );
    }
  • RaisedButton & FlatButton

    FlatButton用法和RaisedButton基本相同,但样式上FlatButton属于扁平化,RaisedButton属于拟物化,有立体感(凸起),可以设置Button底部阴影效果。

    // RaisedButton 凸起效果_raisedButtonDemo() {
    returnRaisedButton(
    onPressed: () =>_showMyDialog(),
    // highlight状态发生变化就调用(高亮和取消高亮都调用)
    onHighlightChanged: (value) {
    print('highlight changed: ${value}');
    },
    // 文本文字颜色
    textColor:Colors.white,
    // 背景色
    color:Colors.blue,
    child:Text('戳'),
    padding:EdgeInsets.all(10),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    color:Colors.black,
    width:1
    )
    ),
    elevation:10,
    );
    }
  • OutlineButton

    线框按钮,它的color属性不会生效(即不能通过color设置OutlineButton的背景色),带有默认边框。其余用法和效果同FlatButton,都是扁平化的效果。

    // OutlineButton 扁平化,默认带边框_outlineButtonDemo() {
    returnOutlineButton(
    onPressed: () =>_showMyDialog(),
    textColor:Colors.blue,
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // borderSide可以设置边框的颜色、宽度和边框样式(none和solid两种)
    borderSide:BorderSide(color:Colors.blue),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    // 这里的BorderSide只可以设置边框样式,并且优先级低
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }
  • RawMaterialButton

    其效果类似FlatButton,用法与上面的三种button稍有差异。上面介绍的几种button,它们的很多属性(如shape)可以设置为null,但RawMaterialButton的属性不允许有null,否则抛出异常。

    // MaterialButton 扁平化,效果类似FlatButton_rawMaterialButtonDemo(bool withBorder) {
    returnRawMaterialButton(
    onPressed: () =>_showMyDialog(),
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // 填充色/背景色
    fillColor:Colors.blue,
    textStyle:TextStyle(color:Colors.white),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }

    ⚠️ 以上这几种button都可以设置其padding值,但它们都有一个默认的padding。当这些button的上一层级Widget的尺寸足够容纳button,这时设置的padding值比默认值大才会生效。当button上一层级的Widget不足以展示button的默认padding时,设置较小的padding可以生效。我们可以通过在button外面包裹一层Container,并设置其alignment为center,再根据需求设置Container的宽高,就可以布局出自己想要的button样式。

  • 通过GestureDetector实现自定义样式的button

    由于上面介绍的几种button,都有一些无法改变的固定样式,这往往不能满足开发中的特定需求。除了上面说的利用Container包裹button以达到想要的样式之外,还可以通过GestureDetector来实现自定义样式的button(其实就是在自己布局的Widget基础上添加了点击手势功能)。

    _myButton() {
    returnGestureDetector(
    onTap: () =>_showMyDialog(),
    child:Container(
    width:70,
    height:28,
    margin:EdgeInsets.only(top:20),
    decoration:BoxDecoration(
    borderRadius:BorderRadius.circular(11.0),
    border:Border.all(width:0.5, color:Color(0xFF6BCBD7))
    ),
    child:Center(
    child:Text('点击刷新',
    style:TextStyle(
    fontSize:13,
    color:Color(0xFF6BCBD7)
    ),
    ),
    ),
    ),
    );
    }
  • CupertinoButton

    CupertinoButton是iOS风格的button

    _iosButtonDemo() {
    returnContainer(
    margin:EdgeInsets.only(top:15),
    child:CupertinoButton(
    child:Text(
    'abc',
    style:TextStyle(fontSize:17, color:Colors.white),
    ),
    color:Colors.blue,
    minSize:20,
    padding:EdgeInsets.fromLTRB(15, 10, 15, 10),
    borderRadius:BorderRadius.circular(10),
    onPressed: () =>_showMyDialog()
    )
    );
    }
    // 以上所有例子中的按钮点击事件_showMyDialog() {
    showDialog(
    context: context,
    builder: (context) {
    returnAlertDialog(
    title:Text('我是title'),
    content:Text('我是content'),
    actions:<Widget>[
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('确定')
    ),
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('取消')
    )
    ],
    );
    }
    );
    }

About

Dart简单语法介绍和Flutter中常用Widget介绍

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

一、Dart基础

1、一些重要的概念

  • Dart 所有的东西都是对象,即使是数字、函数、null也都是对象。所有的对象都继承自 Object 类。
  • Dart 动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下该类型是 dynamic(动态的)。
  • Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
  • Dart 没有public、private和protected关键字。如果标识符以下划线(_)开头,则该标识符对其库是私有的。
  • 没有初始化的变量都会被赋予默认值 null。
  • final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。

2、Dart 中的关键字

关键字

3、常量与变量

name的类型被推断为String:

var name = 'Bob';

如果对象不限于单一类型,或没有明确的类型,可用dynamic/Object:

Object name1 ='zf';
print('name1: ${name1.runtimeType}'); // String
name1 = ['a', 'b'];
print('name1: ${name1.runtimeType}'); // List<String>dynamic name2 ='zf';
print('name2: ${name2.runtimeType}'); // String
name2 =123;
print('name2: ${name2.runtimeType}'); // int

显式声明变量的类型:

String name = 'Bob';

所有未初始化的变量默认值都是null

int lineCount;
// 在生产环境,assert()会被忽略。// 在开发环境,assert(condition)的condition不为真时抛出一个异常。assert(lineCount ==null);

如果某个变量只打算设置一次值,请使用final或const修饰。final变量只能被初始化一次,const变量是一个编译时常量(const变量是隐式的final)。final修饰的变量在第一次声明的时候就要初始化。被final或者const修饰的变量,类型可以省略,但建议指定类型:

// 可以省略String这个类型声明final name0 ='Bob';
finalString name1 ='Bob';
const name2 ='Bob';
constString name3 ='Bob';

被final或const修饰的变量值不能再修改。final或const不能和var同时使用。常量如果是类级别的,建议使用static const:

static const String name = 'Tom';

const关键字不只是声明常数变量,还可以用来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有一个常量值:

// 注意:[]创建的是一个空的list集合// const []创建一个空的,不可变的列表var varList =const [];
final finalList =const [];
const constList =const [];
// 可以更改非final/const变量的值,即便它曾经具有const的值
varList = ['hahaha'];

在常量表达式中,final/const的操作对象必须为bool、num、string或null,const常量必须用const类型的值初始化:

constString name0 ='a';
finalString name1 ='a';
constString name2 ='a';
const aConstList =const ['1', '2'];
// 报错:Const variables must be initialized with a constant valueconst validConstString0 ='$name0 $name1';
// 报错:In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'const validConstString1 ='$name0 $name2 $aConstList';
// 正确写法const validConstString2 ='$name0 $name2';

4、数据类型

  • num: 是数字类型的父类,有两个子类 int 和 double
int a =1;
print(a);
double b =1.11;
print(b);
// String -> intint c =int.parse('1');
// String -> doubledouble d =double.parse('1.11');
// int -> StringString str1 = a.toString();
// 报错:The argument type 'int' can't be assigned to the parameter type 'String'print(str1 + a);
// 正确// print('${str1} ${a}');print(str1 + a.toString());
// double -> String,// toStringAsFixed()// 参数表示要保留几位小数,参数必填// 填写的整数值大于小数的小数点位数时,会补0// 返回结果四舍五入double num0 =3.141592653;
double num1 =3.14;
String str0 = num0.toStringAsFixed(0);
String str1 = num0.toStringAsFixed(3);
String str2 = num1.toStringAsFixed(4);
// str0 = 3, str1 = 3.142, str2 = 3.1400print('str0 = ${str0}, str1 = ${str1}, str2 = ${str2}');
// toStringAsExponential()// 科学计数,参数可为空,参数表示要保留几位小数,会四舍五入double num2 =12345.678;
String str3 = num2.toStringAsExponential();
String str4 = num2.toStringAsExponential(0);
String str5 = num2.toStringAsExponential(4);
// str3 = 1.2345678e+4, str4 = 1e+4, str5 = 1.2346e+4print('str3 = ${str3}, str4 = ${str4}, str5 = ${str5}');
// toStringAsPrecision()// 参数必填且>0, 表示有效数字的位数,也会四舍五入double num3 =123.654;
int num4 =1;
String str6 = num3.toStringAsPrecision(3);
String str7 = num3.toStringAsPrecision(1);
String str8 = num4.toStringAsPrecision(2);
// str6 = 124, str7 = 1e+2, str8 = 1.0print('str6 = ${str6}, str7 = ${str7}, str8 = ${str8}');
  • String: 可以使用单引号/双引号创建字符串;引号可以嵌套使用
String str1 ='aaaaa';
String str2 ='bbbbb';
int a =1;
String str3 ='$str1 a "b" ${str2.toUpperCase()} ${a.toString()}';
  • bool
    • Dart 是强bool类型检查,只有bool类型的值是true,才被认为是true
    • 只有两个对象具有bool类型:true和false,它们都是编译时常量
    • assert 是语言内置的断言函数,仅在检查模式下有效。条件不为真则引发异常
var fullName ='';
assert(fullName.isEmpty);
var a =0/0;
assert(a.isNaN);
var b;
assert(b ==null);
  • List集合: 元素类型可以是任意类型
// 使用List的构造函数。如果传递int参数,表示List固定长度,不可增删操作List list0 =newList();
List list1 = ['a', 10, true, null, ['bala', 'heihei']];
// 添加一个元素
list1.add('apple');
// 添加多个元素
list1.addAll(['orange', 'banana']);
// 获取List的长度/首个元素/最后一个元素/指定下标元素/某个元素的索引print('${list1.length}, ${list1.first}, ${list1.last}, ${list1[0]}, ${list1.indexOf('apple')}');
// 删除指定位置的元素,返回被删除的元素print(list1.removeAt(0));
// 删除指定元素,返回true/false。如果集合中有多个相同元素,只会删除第一个
list1.remove('apple');
// 删除最后一个元素,返回被删除的元素print(list1.removeLast());
// 删除指定范围的元素,含头不含尾,没有返回值
list1.removeRange(0, 1);
// 删除指定条件的元素,没有返回值
list1.removeWhere((item) => item.runtimeType ==String);
// 删除所有的元素,没有返回值
list1.clear();
  • Map集合: map的键和值可以是任何类型的对象
// 这种方式初始化map时,key不能相同,否则报错:Two keys in a map literal can't be equal.Map map0 = {'name':'aaa', 'age':18, 111:true, null:null, ['a']: {'xxx':'yyy'}};
// 另一种创建方式Map map1 =newMap();
map1['name'] ='aaa';
map1['age'] =18;
// 指定键值对的参数类型。如果指定了key/value类型,赋值时必须为指定的类型Map map2 =newMap<int, String>();
// 对同一key进行赋值会被覆盖
map2[1] ='abc';
map2[1] ='aaa';
// map里面的value和key都可以是null
map2[2] =null;
// 检索Map是否含有某keyassert(map2.containsKey(1));
// 删除某个键值对(参数为key值)
map2.remove(1);

5、异常(Exception)

  • throw

抛出固定类型的异常

throw new FormatException('Expected at least 1 section');

抛出任意类型的异常

throw 'Out of memory!';

  • on / catch

将可能出现异常的代码放到 try 语句中,可以通过 on 语句来指定需要捕获的异常类型;使用 catch 可以捕获到任意异常类型。rethrow可以重新抛出捕获的异常

test1() {
try {
test2();
} catch (e) {
print('test1 -- e -- ${e}');
}
}
test2() {
try {
throwFormatException('this is exception');
} onOutOfMemoryError { // 可以捕获指定类型的异常print('out of memory');
} onFormatExceptioncatch (e) { // 匹配不到FormatException,会继续匹配print('exception error: ${e}');
// 重新抛出异常,可以在test1的catch中重新捕获异常rethrow; } catch (e, r) { // 匹配所有类型的异常:e是异常对象,r时异常的堆栈信息print('exception details: ${e}');
print('stack trace: ${r}');
} finally { // 一定会执行,如果try/catch中有return/rethrow,会在return/rethrow之前执行print('this is finally');
}
}

6、函数(Function)

Dart 的函数有两种类型的参数:必需的和可选的。可选参数可以有一个默认值,在调用者没有指定可选参数时使用默认值。可选参数又分为:可选命名参数和可选位置参数。

  • 可选命名参数: 使用{}包围的参数属于可选命名参数,可设置默认值,在调用的时候,需要使用paramName:value的形式指定为哪个可选参数赋值。
void_testPar1(int a, {String name, int age =10}) {
print('a = ${a}, name = ${name}, age = ${age}');
}
_testPar1(123, name:'abc'); // a = 123, name = abc, age = 10_testPar1(123, age:18); // a = 123, name = null, age = 18_testPar1(123, name:'aaa', age:18); // a = 123, name = aaa, age = 18
  • 可选位置参数: 使用[]包围的参数属于可选位置参数。如果想指定某个位置上的参数,必须保证前面位置有参数(设置了默认参数也需要传)。
void _testPar2 (int a, [String str ='abc', int b]) {
print('a = ${a}, str = ${str}, b = ${b}');
}
_testPar2(123); // a = 123, str = abc, b = null_testPar2(123, 'beijing'); // a = 123, str = beijing, b = null_testPar2(123, 'beijing', 10); // a = 123, str = beijing, b = 10_testPar2(123, 10); // 会报错

7、类的构造函数

classPoint {
int x;
int y;
// 构造函数Point(int a, int b) {
x = a;
y = b;
}
// 上面的构造函数可以简化为:Point(this.x, this.y);
// 命名构造函数Point.fromJson(Map data) {
x = data['x'];
y = data['y'];
}
// 可以在构造函数打括号前来初始化实例的变量值,多个实例变量用逗号隔开Point.fromJson(Map data) : x = data['x'], y = data['y'];
}

8、Dart 中的异步

  • Event-Looper

    Dart 是单线程模型,也就没有所谓的主线程/子线程。 Dart 也是Event-Looper以及Event-Queue的模型,所有的事件都是通过EventLooper依次执行。 Dart 的Event Loop是:

    • 从EventQueue中获取Event
    • 处理Event
    • 直到EventQueue为空

    Event Queue

    这些Event包括了用户输入,点击,Timer,文件IO等

    Event Queue

  • 单线程模型

    Dart 中没有线程的概念,只有isolate,每个isolate都是隔离的,并不会共享内存。一个 Dart 程序是在Main isolate的main函数开始,在main函数结束后,Main isolate开始一个一个的处理Event Queue中的每一个Event。

    Event Queue

  • Event Queue以及Microtask Queue

    Dart 中的Main isolate只有一个Event Looper,但存在两个Queue:Event Queue和Microtask Queue。Microtask Queue存在的意义是:

    希望通过这个Queue来处理稍晚一些,但是在下一个消息到来之前需要处理完的事情。

    当Event Looper正在处理Microtask Queue中的Event时,Event Queue中的Event就停止了处理,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等。

    Event-Looper挑选Task的执行顺序为:

    • 优先全部执行完Microtask Queue中的Event
    • 直到Microtask Queue为空时,再执行Event Queue中的Event

    Event Queue

    Dart 中只知道Event处理的先后顺序,并不知道某个Event执行的具体时间点。

  • 异步任务调度

    当有代码可以在后续任务执行的时候,有两种方式,通过 dart:async 中的API即可

    • 使用Future类,可以将任务加入到Event Queue的队尾
    // 一般常用的Future构造函数newFuture(() {
    // do something
    });
    // 当需要将任务拆分成很多小任务一步一步执行时,可以使用Future.then来拆解任务newFuture(() {
    print('abc');
    return21;
    }).then((value) =>'!!!then01 $value!!!') //这里的value是上面任务的返回值
    .then((value) {
    // 这里的value为上一个任务执行完的返回结果print('===then02 $value==='); // ===then02 !!!then01 21!!!===return'balabala';
    }).then((value){
    // 这里的value是上个任务的返回值print('~~~then03 $value~~~~'); // ~~~then03 balabala~~~~
    }).whenComplete(() {
    print('complete');
    });
    // 当任务需要延时执行,可以使用Future.delay。不是绝对精准的。newFuture.delayed(constDuration(seconds:1), () {print('123');});
    • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾
     import 'dart:async';
    voidmain() {
    newFuture.delayed(constDuration(seconds:1), () {print('delay');});
    print('main---111');
    scheduleMicrotask(() {
    print('microtask---111');
    });
    newFuture(() {
    print('future');
    return21;
    }).then((value) {
    print('future-then-$value');
    }).whenComplete(() {
    print('complete');
    });
    scheduleMicrotask(() {
    print('microtask---222');
    });
    print('main---222');
    }

    上面代码的输出顺序为:

    main---111 main---222 microtask---111 microtask---222 future future-then-21 complete delay

    • 再看一个较复杂的例子
    scheduleMicrotask(() =>print('000'));
    newFuture(() =>print('111'))
    .then((_) {
    print('222');
    scheduleMicrotask(() =>print('333'));
    }).then((_) =>print('444'));
    scheduleMicrotask(() =>print('555'));
    newFuture(() =>print('666'))
    .then((_) {
    print('777');
    Future(() =>print('888'));
    }).then((_) =>print('999'));
    newFuture(() =>print('aaa'));
    scheduleMicrotask(() =>print('bbb'));

    上面输出顺序为:0-5-b-1-2-4-3-6-7-9-a-8

  • async和await

    // async关键字声明该函数内部有延时执行的任务asyncAndAwait() async {
    // 在Dart中,有await修饰的,其结果是一个Future对象var a =awaittest0();
    print('a-type:${a.runtimeType}');
    print('a = ${a}');
    var b =awaittest1();
    print('b-type:${b.runtimeType}');
    print('b = ${b}');
    }
    test0() {
    returnFuture(() =>'22222');
    }
    test1() {
    returnFuture(() =>'22222')
    .then((value) {
    print('then-value=${value}');
    }).whenComplete(() {
    print('complete~');
    });
    }

    上面代码的输出顺序依次为:

    a-type:String a = 11111 then-value=22222 complete~ b-type:Null b = null

二、Widget基础篇

1、Widget State

Flutter 中,一切能显示的都是 WidgetWidget 是一切的基础,作为响应式的渲染,属于 MVVM 的实现机制。我们可以通过修改数据,再用 setState 设置数据, Flutter 会自动通过绑定的数据更新 Widget 。我们需要做的就是实现 Widget 页面,并且和数据绑定起来。

Widget 分为 有状态无状态 两种。在 Flutter 中每个页面都是一帧。无状态就是保持在那一帧;有状态的 Widget 当数据更新时,其实就是绘制了新的 Widget

  • 无状态 StatelessWidget

    无状态的 Widget 实现很简单:创建一个继承自 StatelessWidgetWidget ,并通过 build 方法返回一个布局好的控件。WidgetWidget 之间通过 childchildren 进行嵌套。

import'package:flutter/material.dart';
classMyStatelessextendsStatelessWidget {
finalString text;
MyStateless(this.text);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:Text('StatelessWidget'),
),
body:Container(
color:Colors.white,
//Dart语法中,?? 表示如果text为空,就返回 ?? 后的内容。
child:Text(text ??"这就是无状态DMEO"),
),
);
}
}
  • 有状态 StatefulWidget

有状态的 Widget 需要管理 state ,通过 setState 改变数据触发 Widget 重新构建刷新。主要的生命周期有:

initState: 初始化 didChangeDependencies:initState 之后 build 之前调用 dispose: 销毁

classMyStatefulextendsStatefulWidget {
@overrideState<StatefulWidget> createState() {
return_MyStatefulState();
}
}
class_MyStatefulStateextendsState<MyStateful> {
int pressCount;
@overridevoidinitState() {
super.initState();
pressCount =0;
}
@overrideWidgetbuild(BuildContext context) {
returnContainer(
child:FlatButton(
onPressed: () {
setState(() {
pressCount +=1;
});
},
child:Center(
child:Text('有状态的Widget,可戳👇--$pressCount'),
)
),
);
}
}

不管是有状态的还是无状态的 Widget ,我们需要做的就是在 build 中堆积我们的布局,把数据添加到 Widget 中,有状态的可以通过 setState 改变数据,从而刷新UI,就这么简单。

2、Flutter中的Widget整理

先来看一张图,这张图中几乎包含了 Flutter 中所有的 Widget

我们先针对其中比较常用的讲解下其特性和使用方法。

3、布局相关的常用Widget

  • Container

    最常用的布局。只能包含一个 child ,默认铺满。支持配置 paddingmargincolor 、宽高、decoration (一般配置边框和阴影等)。不是所有的控件都有 paddingmargincolor 、宽高等属性,所以才会有 PaddingCenterWidget 的存在。

    alignment: 设置Container内child的对其方式(注意不是Container本身的对其方式)

    width/height/color: 设置Container的宽高和背景色

    padding/margin: padding设置Container边缘和其child内容的距离,margin设置Container和其它外部Widget的距离。

    decoration: Container的修饰器,主要设置背景、边框、圆角等。注意:如果设置了该属性,就不能设置Container的color,否则会抛出异常

    _containerDemo() {
    returnContainer(
    alignment:Alignment.center,
    child:Text('balabalabala'),
    height:49,
    margin:EdgeInsets.all(20),
    padding:EdgeInsets.only(top:10),
    // color: Colors.blueGrey,// 使用它就不能设置Container的color
    decoration:BoxDecoration(
    borderRadius:BorderRadius.only(
    topLeft:Radius.circular(10),
    bottomRight:Radius.circular(10)
    ),
    color:Colors.blue,
    border:Border.all(
    color:Color(0xFF000000),
    width:1
    )
    ),
    );
    }
  • Column、Row和Expanded

    竖直布局和水平布局也是很常见的场景。它们都可以通过 children 设置多个子 Widget ,可以设置主轴和副轴方向布局方式等。而 ExpendedColumnRow 中默认代表着平均充满。当然可以通过设置 flex 属性决定占屏比例。

    _columnDemo() {
    returnColumn(
    children:<Widget>[
    Expanded(child:Container(
    child:Text('aaa'),
    color:Colors.grey,
    )),
    Expanded(child:Container(
    child:Text('bbb'),
    color:Colors.blueGrey,
    )),
    Expanded(
    child:Container(
    child:Text('ccc'),
    color:Colors.red
    ),
    flex:2
    )
    ],
    // 主轴方向布局方式
    mainAxisAlignment:MainAxisAlignment.center,
    // 大小按照最大充满(这里使用Expanded会最大充满不受该属性设置影响)
    mainAxisSize :MainAxisSize.min,
    // 副轴方向布局方式
    crossAxisAlignment:CrossAxisAlignment.center
    );
    }

4、其它常用Widget

Flutter 中除了布局的 Widget ,还有交互显示和完整页面呈现的 Widget 。其中常见的有 MaterialAppScaffoldAppbarTextImageFlatButton 等。

常用Widget及特点

  • Text

    textAlign: 设置文本对其方式:center、left、right、start、end

    maxLines: 设置最多显示的行数

    overflow: 文本溢出时怎么处理。clip:直接切断;ellipsis:...;fade:渐变(上下渐变,不是左右)

    style: 通过TextStyle设置字体大小、颜色、weight等

    _textDemo() {
    returnContainer(
    width:100,
    child:Text(
    'balabalabalabalabalabalabalabalabalabalabalabalabalabalabala',
    textAlign:TextAlign.end,
    style:TextStyle(
    fontSize:20,
    color:Colors.white,
    fontWeight:FontWeight.w500
    ),
    maxLines:1,
    overflow:TextOverflow.fade,
    )
    );
    }
  • image

    加载图片有四种方式分别为:

    Image.asset: 加载项目资源目录中的图片,相对路径

    Image.network: 加载网络图片

    Image.file: 加载本地图片,绝对路径

    Image.memory: 加载Uint8List资源图片(没使用过😢)

    通过fit属性设置图片的填充方式:

    BoxFit.fill: 图片拉伸以充满父容器

    BoxFit.contain: 比例不变拉伸,可能会有空隙(水平和竖直方向最多有一个方向有空隙)

    BoxFit.cover: 可能被裁减,比例不变拉伸,充满父容器

    BoxFit.fitWidth/fitHeight: 宽度/高度充满

    BoxFit.scaleDown: 效果和contain类似,但图片不会被拉伸超过源图片大小

    _imageDemo() {
    returnContainer(
    alignment:Alignment.center,
    width:300,
    height:400,
    color:Colors.blue,
    child:Image.network(
    'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2527385882,2413489660&fm=26&gp=0.jpg',
    fit:BoxFit.contain,
    )
    );
    }
  • TextField

    TextFieldCupertinoTextField 是两种不同风格的 textField ,两者样式有很大差异,用法略有不同。先来看下 TextField 的基本使用:

    _textFieldDemo0() {
    returnTextField(
    maxLength:30,
    maxLines:1,
    // 是否自动聚焦
    autofocus:false,
    // 是否为密码输入框
    obscureText:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    // 装饰效果
    decoration:InputDecoration(
    // decoration是否使用fillColor填充,默认为false
    filled:false,
    fillColor:Colors.red,
    hintText:'placeholder',
    helperText:'helperText',
    labelText:'labelText'
    ),
    enabled:true,
    // 在onSubmitted之前调用。实现该方法后,点键盘return输入框不会失去焦点
    onEditingComplete: () {
    print('TextField:complete');
    }
    );
    }

    下面 CupertinoTextField (iOS风格的TextField)的基本使用:

    _textFieldDemo1() {
    returnCupertinoTextField(
    maxLength:200,
    maxLines:1,
    autofocus:false,
    // 密码
    obscureText:false,
    autocorrect:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    placeholder:'placeholder',
    clearButtonMode:OverlayVisibilityMode.editing,
    enabled:true,
    // 设置光标宽度,默认为2
    cursorWidth:2,
    // 设置光标颜色,默认为blue
    cursorColor:Colors.red,
    // 在onSubmitted之前调用。实现了该方法后,按return不会让textField失去焦点
    onEditingComplete: () {
    print('CupertinoTextField:complete');
    }
    );
    }
  • RaisedButton & FlatButton

    FlatButton用法和RaisedButton基本相同,但样式上FlatButton属于扁平化,RaisedButton属于拟物化,有立体感(凸起),可以设置Button底部阴影效果。

    // RaisedButton 凸起效果_raisedButtonDemo() {
    returnRaisedButton(
    onPressed: () =>_showMyDialog(),
    // highlight状态发生变化就调用(高亮和取消高亮都调用)
    onHighlightChanged: (value) {
    print('highlight changed: ${value}');
    },
    // 文本文字颜色
    textColor:Colors.white,
    // 背景色
    color:Colors.blue,
    child:Text('戳'),
    padding:EdgeInsets.all(10),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    color:Colors.black,
    width:1
    )
    ),
    elevation:10,
    );
    }
  • OutlineButton

    线框按钮,它的color属性不会生效(即不能通过color设置OutlineButton的背景色),带有默认边框。其余用法和效果同FlatButton,都是扁平化的效果。

    // OutlineButton 扁平化,默认带边框_outlineButtonDemo() {
    returnOutlineButton(
    onPressed: () =>_showMyDialog(),
    textColor:Colors.blue,
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // borderSide可以设置边框的颜色、宽度和边框样式(none和solid两种)
    borderSide:BorderSide(color:Colors.blue),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    // 这里的BorderSide只可以设置边框样式,并且优先级低
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }
  • RawMaterialButton

    其效果类似FlatButton,用法与上面的三种button稍有差异。上面介绍的几种button,它们的很多属性(如shape)可以设置为null,但RawMaterialButton的属性不允许有null,否则抛出异常。

    // MaterialButton 扁平化,效果类似FlatButton_rawMaterialButtonDemo(bool withBorder) {
    returnRawMaterialButton(
    onPressed: () =>_showMyDialog(),
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // 填充色/背景色
    fillColor:Colors.blue,
    textStyle:TextStyle(color:Colors.white),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }

    ⚠️ 以上这几种button都可以设置其padding值,但它们都有一个默认的padding。当这些button的上一层级Widget的尺寸足够容纳button,这时设置的padding值比默认值大才会生效。当button上一层级的Widget不足以展示button的默认padding时,设置较小的padding可以生效。我们可以通过在button外面包裹一层Container,并设置其alignment为center,再根据需求设置Container的宽高,就可以布局出自己想要的button样式。

  • 通过GestureDetector实现自定义样式的button

    由于上面介绍的几种button,都有一些无法改变的固定样式,这往往不能满足开发中的特定需求。除了上面说的利用Container包裹button以达到想要的样式之外,还可以通过GestureDetector来实现自定义样式的button(其实就是在自己布局的Widget基础上添加了点击手势功能)。

    _myButton() {
    returnGestureDetector(
    onTap: () =>_showMyDialog(),
    child:Container(
    width:70,
    height:28,
    margin:EdgeInsets.only(top:20),
    decoration:BoxDecoration(
    borderRadius:BorderRadius.circular(11.0),
    border:Border.all(width:0.5, color:Color(0xFF6BCBD7))
    ),
    child:Center(
    child:Text('点击刷新',
    style:TextStyle(
    fontSize:13,
    color:Color(0xFF6BCBD7)
    ),
    ),
    ),
    ),
    );
    }
  • CupertinoButton

    CupertinoButton是iOS风格的button

    _iosButtonDemo() {
    returnContainer(
    margin:EdgeInsets.only(top:15),
    child:CupertinoButton(
    child:Text(
    'abc',
    style:TextStyle(fontSize:17, color:Colors.white),
    ),
    color:Colors.blue,
    minSize:20,
    padding:EdgeInsets.fromLTRB(15, 10, 15, 10),
    borderRadius:BorderRadius.circular(10),
    onPressed: () =>_showMyDialog()
    )
    );
    }
    // 以上所有例子中的按钮点击事件_showMyDialog() {
    showDialog(
    context: context,
    builder: (context) {
    returnAlertDialog(
    title:Text('我是title'),
    content:Text('我是content'),
    actions:<Widget>[
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('确定')
    ),
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('取消')
    )
    ],
    );
    }
    );
    }

About

Dart简单语法介绍和Flutter中常用Widget介绍

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

一、Dart基础

1、一些重要的概念

  • Dart 所有的东西都是对象,即使是数字、函数、null也都是对象。所有的对象都继承自 Object 类。
  • Dart 动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下该类型是 dynamic(动态的)。
  • Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
  • Dart 没有public、private和protected关键字。如果标识符以下划线(_)开头,则该标识符对其库是私有的。
  • 没有初始化的变量都会被赋予默认值 null。
  • final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。

2、Dart 中的关键字

关键字

3、常量与变量

name的类型被推断为String:

var name = 'Bob';

如果对象不限于单一类型,或没有明确的类型,可用dynamic/Object:

Object name1 ='zf';
print('name1: ${name1.runtimeType}'); // String
name1 = ['a', 'b'];
print('name1: ${name1.runtimeType}'); // List<String>dynamic name2 ='zf';
print('name2: ${name2.runtimeType}'); // String
name2 =123;
print('name2: ${name2.runtimeType}'); // int

显式声明变量的类型:

String name = 'Bob';

所有未初始化的变量默认值都是null

int lineCount;
// 在生产环境,assert()会被忽略。// 在开发环境,assert(condition)的condition不为真时抛出一个异常。assert(lineCount ==null);

如果某个变量只打算设置一次值,请使用final或const修饰。final变量只能被初始化一次,const变量是一个编译时常量(const变量是隐式的final)。final修饰的变量在第一次声明的时候就要初始化。被final或者const修饰的变量,类型可以省略,但建议指定类型:

// 可以省略String这个类型声明final name0 ='Bob';
finalString name1 ='Bob';
const name2 ='Bob';
constString name3 ='Bob';

被final或const修饰的变量值不能再修改。final或const不能和var同时使用。常量如果是类级别的,建议使用static const:

static const String name = 'Tom';

const关键字不只是声明常数变量,还可以用来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有一个常量值:

// 注意:[]创建的是一个空的list集合// const []创建一个空的,不可变的列表var varList =const [];
final finalList =const [];
const constList =const [];
// 可以更改非final/const变量的值,即便它曾经具有const的值
varList = ['hahaha'];

在常量表达式中,final/const的操作对象必须为bool、num、string或null,const常量必须用const类型的值初始化:

constString name0 ='a';
finalString name1 ='a';
constString name2 ='a';
const aConstList =const ['1', '2'];
// 报错:Const variables must be initialized with a constant valueconst validConstString0 ='$name0 $name1';
// 报错:In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'const validConstString1 ='$name0 $name2 $aConstList';
// 正确写法const validConstString2 ='$name0 $name2';

4、数据类型

  • num: 是数字类型的父类,有两个子类 int 和 double
int a =1;
print(a);
double b =1.11;
print(b);
// String -> intint c =int.parse('1');
// String -> doubledouble d =double.parse('1.11');
// int -> StringString str1 = a.toString();
// 报错:The argument type 'int' can't be assigned to the parameter type 'String'print(str1 + a);
// 正确// print('${str1} ${a}');print(str1 + a.toString());
// double -> String,// toStringAsFixed()// 参数表示要保留几位小数,参数必填// 填写的整数值大于小数的小数点位数时,会补0// 返回结果四舍五入double num0 =3.141592653;
double num1 =3.14;
String str0 = num0.toStringAsFixed(0);
String str1 = num0.toStringAsFixed(3);
String str2 = num1.toStringAsFixed(4);
// str0 = 3, str1 = 3.142, str2 = 3.1400print('str0 = ${str0}, str1 = ${str1}, str2 = ${str2}');
// toStringAsExponential()// 科学计数,参数可为空,参数表示要保留几位小数,会四舍五入double num2 =12345.678;
String str3 = num2.toStringAsExponential();
String str4 = num2.toStringAsExponential(0);
String str5 = num2.toStringAsExponential(4);
// str3 = 1.2345678e+4, str4 = 1e+4, str5 = 1.2346e+4print('str3 = ${str3}, str4 = ${str4}, str5 = ${str5}');
// toStringAsPrecision()// 参数必填且>0, 表示有效数字的位数,也会四舍五入double num3 =123.654;
int num4 =1;
String str6 = num3.toStringAsPrecision(3);
String str7 = num3.toStringAsPrecision(1);
String str8 = num4.toStringAsPrecision(2);
// str6 = 124, str7 = 1e+2, str8 = 1.0print('str6 = ${str6}, str7 = ${str7}, str8 = ${str8}');
  • String: 可以使用单引号/双引号创建字符串;引号可以嵌套使用
String str1 ='aaaaa';
String str2 ='bbbbb';
int a =1;
String str3 ='$str1 a "b" ${str2.toUpperCase()} ${a.toString()}';
  • bool
    • Dart 是强bool类型检查,只有bool类型的值是true,才被认为是true
    • 只有两个对象具有bool类型:true和false,它们都是编译时常量
    • assert 是语言内置的断言函数,仅在检查模式下有效。条件不为真则引发异常
var fullName ='';
assert(fullName.isEmpty);
var a =0/0;
assert(a.isNaN);
var b;
assert(b ==null);
  • List集合: 元素类型可以是任意类型
// 使用List的构造函数。如果传递int参数,表示List固定长度,不可增删操作List list0 =newList();
List list1 = ['a', 10, true, null, ['bala', 'heihei']];
// 添加一个元素
list1.add('apple');
// 添加多个元素
list1.addAll(['orange', 'banana']);
// 获取List的长度/首个元素/最后一个元素/指定下标元素/某个元素的索引print('${list1.length}, ${list1.first}, ${list1.last}, ${list1[0]}, ${list1.indexOf('apple')}');
// 删除指定位置的元素,返回被删除的元素print(list1.removeAt(0));
// 删除指定元素,返回true/false。如果集合中有多个相同元素,只会删除第一个
list1.remove('apple');
// 删除最后一个元素,返回被删除的元素print(list1.removeLast());
// 删除指定范围的元素,含头不含尾,没有返回值
list1.removeRange(0, 1);
// 删除指定条件的元素,没有返回值
list1.removeWhere((item) => item.runtimeType ==String);
// 删除所有的元素,没有返回值
list1.clear();
  • Map集合: map的键和值可以是任何类型的对象
// 这种方式初始化map时,key不能相同,否则报错:Two keys in a map literal can't be equal.Map map0 = {'name':'aaa', 'age':18, 111:true, null:null, ['a']: {'xxx':'yyy'}};
// 另一种创建方式Map map1 =newMap();
map1['name'] ='aaa';
map1['age'] =18;
// 指定键值对的参数类型。如果指定了key/value类型,赋值时必须为指定的类型Map map2 =newMap<int, String>();
// 对同一key进行赋值会被覆盖
map2[1] ='abc';
map2[1] ='aaa';
// map里面的value和key都可以是null
map2[2] =null;
// 检索Map是否含有某keyassert(map2.containsKey(1));
// 删除某个键值对(参数为key值)
map2.remove(1);

5、异常(Exception)

  • throw

抛出固定类型的异常

throw new FormatException('Expected at least 1 section');

抛出任意类型的异常

throw 'Out of memory!';

  • on / catch

将可能出现异常的代码放到 try 语句中,可以通过 on 语句来指定需要捕获的异常类型;使用 catch 可以捕获到任意异常类型。rethrow可以重新抛出捕获的异常

test1() {
try {
test2();
} catch (e) {
print('test1 -- e -- ${e}');
}
}
test2() {
try {
throwFormatException('this is exception');
} onOutOfMemoryError { // 可以捕获指定类型的异常print('out of memory');
} onFormatExceptioncatch (e) { // 匹配不到FormatException,会继续匹配print('exception error: ${e}');
// 重新抛出异常,可以在test1的catch中重新捕获异常rethrow; } catch (e, r) { // 匹配所有类型的异常:e是异常对象,r时异常的堆栈信息print('exception details: ${e}');
print('stack trace: ${r}');
} finally { // 一定会执行,如果try/catch中有return/rethrow,会在return/rethrow之前执行print('this is finally');
}
}

6、函数(Function)

Dart 的函数有两种类型的参数:必需的和可选的。可选参数可以有一个默认值,在调用者没有指定可选参数时使用默认值。可选参数又分为:可选命名参数和可选位置参数。

  • 可选命名参数: 使用{}包围的参数属于可选命名参数,可设置默认值,在调用的时候,需要使用paramName:value的形式指定为哪个可选参数赋值。
void_testPar1(int a, {String name, int age =10}) {
print('a = ${a}, name = ${name}, age = ${age}');
}
_testPar1(123, name:'abc'); // a = 123, name = abc, age = 10_testPar1(123, age:18); // a = 123, name = null, age = 18_testPar1(123, name:'aaa', age:18); // a = 123, name = aaa, age = 18
  • 可选位置参数: 使用[]包围的参数属于可选位置参数。如果想指定某个位置上的参数,必须保证前面位置有参数(设置了默认参数也需要传)。
void _testPar2 (int a, [String str ='abc', int b]) {
print('a = ${a}, str = ${str}, b = ${b}');
}
_testPar2(123); // a = 123, str = abc, b = null_testPar2(123, 'beijing'); // a = 123, str = beijing, b = null_testPar2(123, 'beijing', 10); // a = 123, str = beijing, b = 10_testPar2(123, 10); // 会报错

7、类的构造函数

classPoint {
int x;
int y;
// 构造函数Point(int a, int b) {
x = a;
y = b;
}
// 上面的构造函数可以简化为:Point(this.x, this.y);
// 命名构造函数Point.fromJson(Map data) {
x = data['x'];
y = data['y'];
}
// 可以在构造函数打括号前来初始化实例的变量值,多个实例变量用逗号隔开Point.fromJson(Map data) : x = data['x'], y = data['y'];
}

8、Dart 中的异步

  • Event-Looper

    Dart 是单线程模型,也就没有所谓的主线程/子线程。 Dart 也是Event-Looper以及Event-Queue的模型,所有的事件都是通过EventLooper依次执行。 Dart 的Event Loop是:

    • 从EventQueue中获取Event
    • 处理Event
    • 直到EventQueue为空

    Event Queue

    这些Event包括了用户输入,点击,Timer,文件IO等

    Event Queue

  • 单线程模型

    Dart 中没有线程的概念,只有isolate,每个isolate都是隔离的,并不会共享内存。一个 Dart 程序是在Main isolate的main函数开始,在main函数结束后,Main isolate开始一个一个的处理Event Queue中的每一个Event。

    Event Queue

  • Event Queue以及Microtask Queue

    Dart 中的Main isolate只有一个Event Looper,但存在两个Queue:Event Queue和Microtask Queue。Microtask Queue存在的意义是:

    希望通过这个Queue来处理稍晚一些,但是在下一个消息到来之前需要处理完的事情。

    当Event Looper正在处理Microtask Queue中的Event时,Event Queue中的Event就停止了处理,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等。

    Event-Looper挑选Task的执行顺序为:

    • 优先全部执行完Microtask Queue中的Event
    • 直到Microtask Queue为空时,再执行Event Queue中的Event

    Event Queue

    Dart 中只知道Event处理的先后顺序,并不知道某个Event执行的具体时间点。

  • 异步任务调度

    当有代码可以在后续任务执行的时候,有两种方式,通过 dart:async 中的API即可

    • 使用Future类,可以将任务加入到Event Queue的队尾
    // 一般常用的Future构造函数newFuture(() {
    // do something
    });
    // 当需要将任务拆分成很多小任务一步一步执行时,可以使用Future.then来拆解任务newFuture(() {
    print('abc');
    return21;
    }).then((value) =>'!!!then01 $value!!!') //这里的value是上面任务的返回值
    .then((value) {
    // 这里的value为上一个任务执行完的返回结果print('===then02 $value==='); // ===then02 !!!then01 21!!!===return'balabala';
    }).then((value){
    // 这里的value是上个任务的返回值print('~~~then03 $value~~~~'); // ~~~then03 balabala~~~~
    }).whenComplete(() {
    print('complete');
    });
    // 当任务需要延时执行,可以使用Future.delay。不是绝对精准的。newFuture.delayed(constDuration(seconds:1), () {print('123');});
    • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾
     import 'dart:async';
    voidmain() {
    newFuture.delayed(constDuration(seconds:1), () {print('delay');});
    print('main---111');
    scheduleMicrotask(() {
    print('microtask---111');
    });
    newFuture(() {
    print('future');
    return21;
    }).then((value) {
    print('future-then-$value');
    }).whenComplete(() {
    print('complete');
    });
    scheduleMicrotask(() {
    print('microtask---222');
    });
    print('main---222');
    }

    上面代码的输出顺序为:

    main---111 main---222 microtask---111 microtask---222 future future-then-21 complete delay

    • 再看一个较复杂的例子
    scheduleMicrotask(() =>print('000'));
    newFuture(() =>print('111'))
    .then((_) {
    print('222');
    scheduleMicrotask(() =>print('333'));
    }).then((_) =>print('444'));
    scheduleMicrotask(() =>print('555'));
    newFuture(() =>print('666'))
    .then((_) {
    print('777');
    Future(() =>print('888'));
    }).then((_) =>print('999'));
    newFuture(() =>print('aaa'));
    scheduleMicrotask(() =>print('bbb'));

    上面输出顺序为:0-5-b-1-2-4-3-6-7-9-a-8

  • async和await

    // async关键字声明该函数内部有延时执行的任务asyncAndAwait() async {
    // 在Dart中,有await修饰的,其结果是一个Future对象var a =awaittest0();
    print('a-type:${a.runtimeType}');
    print('a = ${a}');
    var b =awaittest1();
    print('b-type:${b.runtimeType}');
    print('b = ${b}');
    }
    test0() {
    returnFuture(() =>'22222');
    }
    test1() {
    returnFuture(() =>'22222')
    .then((value) {
    print('then-value=${value}');
    }).whenComplete(() {
    print('complete~');
    });
    }

    上面代码的输出顺序依次为:

    a-type:String a = 11111 then-value=22222 complete~ b-type:Null b = null

二、Widget基础篇

1、Widget State

Flutter 中,一切能显示的都是 WidgetWidget 是一切的基础,作为响应式的渲染,属于 MVVM 的实现机制。我们可以通过修改数据,再用 setState 设置数据, Flutter 会自动通过绑定的数据更新 Widget 。我们需要做的就是实现 Widget 页面,并且和数据绑定起来。

Widget 分为 有状态无状态 两种。在 Flutter 中每个页面都是一帧。无状态就是保持在那一帧;有状态的 Widget 当数据更新时,其实就是绘制了新的 Widget

  • 无状态 StatelessWidget

    无状态的 Widget 实现很简单:创建一个继承自 StatelessWidgetWidget ,并通过 build 方法返回一个布局好的控件。WidgetWidget 之间通过 childchildren 进行嵌套。

import'package:flutter/material.dart';
classMyStatelessextendsStatelessWidget {
finalString text;
MyStateless(this.text);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:Text('StatelessWidget'),
),
body:Container(
color:Colors.white,
//Dart语法中,?? 表示如果text为空,就返回 ?? 后的内容。
child:Text(text ??"这就是无状态DMEO"),
),
);
}
}
  • 有状态 StatefulWidget

有状态的 Widget 需要管理 state ,通过 setState 改变数据触发 Widget 重新构建刷新。主要的生命周期有:

initState: 初始化 didChangeDependencies:initState 之后 build 之前调用 dispose: 销毁

classMyStatefulextendsStatefulWidget {
@overrideState<StatefulWidget> createState() {
return_MyStatefulState();
}
}
class_MyStatefulStateextendsState<MyStateful> {
int pressCount;
@overridevoidinitState() {
super.initState();
pressCount =0;
}
@overrideWidgetbuild(BuildContext context) {
returnContainer(
child:FlatButton(
onPressed: () {
setState(() {
pressCount +=1;
});
},
child:Center(
child:Text('有状态的Widget,可戳👇--$pressCount'),
)
),
);
}
}

不管是有状态的还是无状态的 Widget ,我们需要做的就是在 build 中堆积我们的布局,把数据添加到 Widget 中,有状态的可以通过 setState 改变数据,从而刷新UI,就这么简单。

2、Flutter中的Widget整理

先来看一张图,这张图中几乎包含了 Flutter 中所有的 Widget

我们先针对其中比较常用的讲解下其特性和使用方法。

3、布局相关的常用Widget

  • Container

    最常用的布局。只能包含一个 child ,默认铺满。支持配置 paddingmargincolor 、宽高、decoration (一般配置边框和阴影等)。不是所有的控件都有 paddingmargincolor 、宽高等属性,所以才会有 PaddingCenterWidget 的存在。

    alignment: 设置Container内child的对其方式(注意不是Container本身的对其方式)

    width/height/color: 设置Container的宽高和背景色

    padding/margin: padding设置Container边缘和其child内容的距离,margin设置Container和其它外部Widget的距离。

    decoration: Container的修饰器,主要设置背景、边框、圆角等。注意:如果设置了该属性,就不能设置Container的color,否则会抛出异常

    _containerDemo() {
    returnContainer(
    alignment:Alignment.center,
    child:Text('balabalabala'),
    height:49,
    margin:EdgeInsets.all(20),
    padding:EdgeInsets.only(top:10),
    // color: Colors.blueGrey,// 使用它就不能设置Container的color
    decoration:BoxDecoration(
    borderRadius:BorderRadius.only(
    topLeft:Radius.circular(10),
    bottomRight:Radius.circular(10)
    ),
    color:Colors.blue,
    border:Border.all(
    color:Color(0xFF000000),
    width:1
    )
    ),
    );
    }
  • Column、Row和Expanded

    竖直布局和水平布局也是很常见的场景。它们都可以通过 children 设置多个子 Widget ,可以设置主轴和副轴方向布局方式等。而 ExpendedColumnRow 中默认代表着平均充满。当然可以通过设置 flex 属性决定占屏比例。

    _columnDemo() {
    returnColumn(
    children:<Widget>[
    Expanded(child:Container(
    child:Text('aaa'),
    color:Colors.grey,
    )),
    Expanded(child:Container(
    child:Text('bbb'),
    color:Colors.blueGrey,
    )),
    Expanded(
    child:Container(
    child:Text('ccc'),
    color:Colors.red
    ),
    flex:2
    )
    ],
    // 主轴方向布局方式
    mainAxisAlignment:MainAxisAlignment.center,
    // 大小按照最大充满(这里使用Expanded会最大充满不受该属性设置影响)
    mainAxisSize :MainAxisSize.min,
    // 副轴方向布局方式
    crossAxisAlignment:CrossAxisAlignment.center
    );
    }

4、其它常用Widget

Flutter 中除了布局的 Widget ,还有交互显示和完整页面呈现的 Widget 。其中常见的有 MaterialAppScaffoldAppbarTextImageFlatButton 等。

常用Widget及特点

  • Text

    textAlign: 设置文本对其方式:center、left、right、start、end

    maxLines: 设置最多显示的行数

    overflow: 文本溢出时怎么处理。clip:直接切断;ellipsis:...;fade:渐变(上下渐变,不是左右)

    style: 通过TextStyle设置字体大小、颜色、weight等

    _textDemo() {
    returnContainer(
    width:100,
    child:Text(
    'balabalabalabalabalabalabalabalabalabalabalabalabalabalabala',
    textAlign:TextAlign.end,
    style:TextStyle(
    fontSize:20,
    color:Colors.white,
    fontWeight:FontWeight.w500
    ),
    maxLines:1,
    overflow:TextOverflow.fade,
    )
    );
    }
  • image

    加载图片有四种方式分别为:

    Image.asset: 加载项目资源目录中的图片,相对路径

    Image.network: 加载网络图片

    Image.file: 加载本地图片,绝对路径

    Image.memory: 加载Uint8List资源图片(没使用过😢)

    通过fit属性设置图片的填充方式:

    BoxFit.fill: 图片拉伸以充满父容器

    BoxFit.contain: 比例不变拉伸,可能会有空隙(水平和竖直方向最多有一个方向有空隙)

    BoxFit.cover: 可能被裁减,比例不变拉伸,充满父容器

    BoxFit.fitWidth/fitHeight: 宽度/高度充满

    BoxFit.scaleDown: 效果和contain类似,但图片不会被拉伸超过源图片大小

    _imageDemo() {
    returnContainer(
    alignment:Alignment.center,
    width:300,
    height:400,
    color:Colors.blue,
    child:Image.network(
    'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2527385882,2413489660&fm=26&gp=0.jpg',
    fit:BoxFit.contain,
    )
    );
    }
  • TextField

    TextFieldCupertinoTextField 是两种不同风格的 textField ,两者样式有很大差异,用法略有不同。先来看下 TextField 的基本使用:

    _textFieldDemo0() {
    returnTextField(
    maxLength:30,
    maxLines:1,
    // 是否自动聚焦
    autofocus:false,
    // 是否为密码输入框
    obscureText:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    // 装饰效果
    decoration:InputDecoration(
    // decoration是否使用fillColor填充,默认为false
    filled:false,
    fillColor:Colors.red,
    hintText:'placeholder',
    helperText:'helperText',
    labelText:'labelText'
    ),
    enabled:true,
    // 在onSubmitted之前调用。实现该方法后,点键盘return输入框不会失去焦点
    onEditingComplete: () {
    print('TextField:complete');
    }
    );
    }

    下面 CupertinoTextField (iOS风格的TextField)的基本使用:

    _textFieldDemo1() {
    returnCupertinoTextField(
    maxLength:200,
    maxLines:1,
    autofocus:false,
    // 密码
    obscureText:false,
    autocorrect:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    placeholder:'placeholder',
    clearButtonMode:OverlayVisibilityMode.editing,
    enabled:true,
    // 设置光标宽度,默认为2
    cursorWidth:2,
    // 设置光标颜色,默认为blue
    cursorColor:Colors.red,
    // 在onSubmitted之前调用。实现了该方法后,按return不会让textField失去焦点
    onEditingComplete: () {
    print('CupertinoTextField:complete');
    }
    );
    }
  • RaisedButton & FlatButton

    FlatButton用法和RaisedButton基本相同,但样式上FlatButton属于扁平化,RaisedButton属于拟物化,有立体感(凸起),可以设置Button底部阴影效果。

    // RaisedButton 凸起效果_raisedButtonDemo() {
    returnRaisedButton(
    onPressed: () =>_showMyDialog(),
    // highlight状态发生变化就调用(高亮和取消高亮都调用)
    onHighlightChanged: (value) {
    print('highlight changed: ${value}');
    },
    // 文本文字颜色
    textColor:Colors.white,
    // 背景色
    color:Colors.blue,
    child:Text('戳'),
    padding:EdgeInsets.all(10),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    color:Colors.black,
    width:1
    )
    ),
    elevation:10,
    );
    }
  • OutlineButton

    线框按钮,它的color属性不会生效(即不能通过color设置OutlineButton的背景色),带有默认边框。其余用法和效果同FlatButton,都是扁平化的效果。

    // OutlineButton 扁平化,默认带边框_outlineButtonDemo() {
    returnOutlineButton(
    onPressed: () =>_showMyDialog(),
    textColor:Colors.blue,
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // borderSide可以设置边框的颜色、宽度和边框样式(none和solid两种)
    borderSide:BorderSide(color:Colors.blue),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    // 这里的BorderSide只可以设置边框样式,并且优先级低
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }
  • RawMaterialButton

    其效果类似FlatButton,用法与上面的三种button稍有差异。上面介绍的几种button,它们的很多属性(如shape)可以设置为null,但RawMaterialButton的属性不允许有null,否则抛出异常。

    // MaterialButton 扁平化,效果类似FlatButton_rawMaterialButtonDemo(bool withBorder) {
    returnRawMaterialButton(
    onPressed: () =>_showMyDialog(),
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // 填充色/背景色
    fillColor:Colors.blue,
    textStyle:TextStyle(color:Colors.white),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }

    ⚠️ 以上这几种button都可以设置其padding值,但它们都有一个默认的padding。当这些button的上一层级Widget的尺寸足够容纳button,这时设置的padding值比默认值大才会生效。当button上一层级的Widget不足以展示button的默认padding时,设置较小的padding可以生效。我们可以通过在button外面包裹一层Container,并设置其alignment为center,再根据需求设置Container的宽高,就可以布局出自己想要的button样式。

  • 通过GestureDetector实现自定义样式的button

    由于上面介绍的几种button,都有一些无法改变的固定样式,这往往不能满足开发中的特定需求。除了上面说的利用Container包裹button以达到想要的样式之外,还可以通过GestureDetector来实现自定义样式的button(其实就是在自己布局的Widget基础上添加了点击手势功能)。

    _myButton() {
    returnGestureDetector(
    onTap: () =>_showMyDialog(),
    child:Container(
    width:70,
    height:28,
    margin:EdgeInsets.only(top:20),
    decoration:BoxDecoration(
    borderRadius:BorderRadius.circular(11.0),
    border:Border.all(width:0.5, color:Color(0xFF6BCBD7))
    ),
    child:Center(
    child:Text('点击刷新',
    style:TextStyle(
    fontSize:13,
    color:Color(0xFF6BCBD7)
    ),
    ),
    ),
    ),
    );
    }
  • CupertinoButton

    CupertinoButton是iOS风格的button

    _iosButtonDemo() {
    returnContainer(
    margin:EdgeInsets.only(top:15),
    child:CupertinoButton(
    child:Text(
    'abc',
    style:TextStyle(fontSize:17, color:Colors.white),
    ),
    color:Colors.blue,
    minSize:20,
    padding:EdgeInsets.fromLTRB(15, 10, 15, 10),
    borderRadius:BorderRadius.circular(10),
    onPressed: () =>_showMyDialog()
    )
    );
    }
    // 以上所有例子中的按钮点击事件_showMyDialog() {
    showDialog(
    context: context,
    builder: (context) {
    returnAlertDialog(
    title:Text('我是title'),
    content:Text('我是content'),
    actions:<Widget>[
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('确定')
    ),
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('取消')
    )
    ],
    );
    }
    );
    }

About

Dart简单语法介绍和Flutter中常用Widget介绍

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

一、Dart基础

1、一些重要的概念

  • Dart 所有的东西都是对象,即使是数字、函数、null也都是对象。所有的对象都继承自 Object 类。
  • Dart 动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下该类型是 dynamic(动态的)。
  • Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
  • Dart 没有public、private和protected关键字。如果标识符以下划线(_)开头,则该标识符对其库是私有的。
  • 没有初始化的变量都会被赋予默认值 null。
  • final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。

2、Dart 中的关键字

关键字

3、常量与变量

name的类型被推断为String:

var name = 'Bob';

如果对象不限于单一类型,或没有明确的类型,可用dynamic/Object:

Object name1 ='zf';
print('name1: ${name1.runtimeType}'); // String
name1 = ['a', 'b'];
print('name1: ${name1.runtimeType}'); // List<String>dynamic name2 ='zf';
print('name2: ${name2.runtimeType}'); // String
name2 =123;
print('name2: ${name2.runtimeType}'); // int

显式声明变量的类型:

String name = 'Bob';

所有未初始化的变量默认值都是null

int lineCount;
// 在生产环境,assert()会被忽略。// 在开发环境,assert(condition)的condition不为真时抛出一个异常。assert(lineCount ==null);

如果某个变量只打算设置一次值,请使用final或const修饰。final变量只能被初始化一次,const变量是一个编译时常量(const变量是隐式的final)。final修饰的变量在第一次声明的时候就要初始化。被final或者const修饰的变量,类型可以省略,但建议指定类型:

// 可以省略String这个类型声明final name0 ='Bob';
finalString name1 ='Bob';
const name2 ='Bob';
constString name3 ='Bob';

被final或const修饰的变量值不能再修改。final或const不能和var同时使用。常量如果是类级别的,建议使用static const:

static const String name = 'Tom';

const关键字不只是声明常数变量,还可以用来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有一个常量值:

// 注意:[]创建的是一个空的list集合// const []创建一个空的,不可变的列表var varList =const [];
final finalList =const [];
const constList =const [];
// 可以更改非final/const变量的值,即便它曾经具有const的值
varList = ['hahaha'];

在常量表达式中,final/const的操作对象必须为bool、num、string或null,const常量必须用const类型的值初始化:

constString name0 ='a';
finalString name1 ='a';
constString name2 ='a';
const aConstList =const ['1', '2'];
// 报错:Const variables must be initialized with a constant valueconst validConstString0 ='$name0 $name1';
// 报错:In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'const validConstString1 ='$name0 $name2 $aConstList';
// 正确写法const validConstString2 ='$name0 $name2';

4、数据类型

  • num: 是数字类型的父类,有两个子类 int 和 double
int a =1;
print(a);
double b =1.11;
print(b);
// String -> intint c =int.parse('1');
// String -> doubledouble d =double.parse('1.11');
// int -> StringString str1 = a.toString();
// 报错:The argument type 'int' can't be assigned to the parameter type 'String'print(str1 + a);
// 正确// print('${str1} ${a}');print(str1 + a.toString());
// double -> String,// toStringAsFixed()// 参数表示要保留几位小数,参数必填// 填写的整数值大于小数的小数点位数时,会补0// 返回结果四舍五入double num0 =3.141592653;
double num1 =3.14;
String str0 = num0.toStringAsFixed(0);
String str1 = num0.toStringAsFixed(3);
String str2 = num1.toStringAsFixed(4);
// str0 = 3, str1 = 3.142, str2 = 3.1400print('str0 = ${str0}, str1 = ${str1}, str2 = ${str2}');
// toStringAsExponential()// 科学计数,参数可为空,参数表示要保留几位小数,会四舍五入double num2 =12345.678;
String str3 = num2.toStringAsExponential();
String str4 = num2.toStringAsExponential(0);
String str5 = num2.toStringAsExponential(4);
// str3 = 1.2345678e+4, str4 = 1e+4, str5 = 1.2346e+4print('str3 = ${str3}, str4 = ${str4}, str5 = ${str5}');
// toStringAsPrecision()// 参数必填且>0, 表示有效数字的位数,也会四舍五入double num3 =123.654;
int num4 =1;
String str6 = num3.toStringAsPrecision(3);
String str7 = num3.toStringAsPrecision(1);
String str8 = num4.toStringAsPrecision(2);
// str6 = 124, str7 = 1e+2, str8 = 1.0print('str6 = ${str6}, str7 = ${str7}, str8 = ${str8}');
  • String: 可以使用单引号/双引号创建字符串;引号可以嵌套使用
String str1 ='aaaaa';
String str2 ='bbbbb';
int a =1;
String str3 ='$str1 a "b" ${str2.toUpperCase()} ${a.toString()}';
  • bool
    • Dart 是强bool类型检查,只有bool类型的值是true,才被认为是true
    • 只有两个对象具有bool类型:true和false,它们都是编译时常量
    • assert 是语言内置的断言函数,仅在检查模式下有效。条件不为真则引发异常
var fullName ='';
assert(fullName.isEmpty);
var a =0/0;
assert(a.isNaN);
var b;
assert(b ==null);
  • List集合: 元素类型可以是任意类型
// 使用List的构造函数。如果传递int参数,表示List固定长度,不可增删操作List list0 =newList();
List list1 = ['a', 10, true, null, ['bala', 'heihei']];
// 添加一个元素
list1.add('apple');
// 添加多个元素
list1.addAll(['orange', 'banana']);
// 获取List的长度/首个元素/最后一个元素/指定下标元素/某个元素的索引print('${list1.length}, ${list1.first}, ${list1.last}, ${list1[0]}, ${list1.indexOf('apple')}');
// 删除指定位置的元素,返回被删除的元素print(list1.removeAt(0));
// 删除指定元素,返回true/false。如果集合中有多个相同元素,只会删除第一个
list1.remove('apple');
// 删除最后一个元素,返回被删除的元素print(list1.removeLast());
// 删除指定范围的元素,含头不含尾,没有返回值
list1.removeRange(0, 1);
// 删除指定条件的元素,没有返回值
list1.removeWhere((item) => item.runtimeType ==String);
// 删除所有的元素,没有返回值
list1.clear();
  • Map集合: map的键和值可以是任何类型的对象
// 这种方式初始化map时,key不能相同,否则报错:Two keys in a map literal can't be equal.Map map0 = {'name':'aaa', 'age':18, 111:true, null:null, ['a']: {'xxx':'yyy'}};
// 另一种创建方式Map map1 =newMap();
map1['name'] ='aaa';
map1['age'] =18;
// 指定键值对的参数类型。如果指定了key/value类型,赋值时必须为指定的类型Map map2 =newMap<int, String>();
// 对同一key进行赋值会被覆盖
map2[1] ='abc';
map2[1] ='aaa';
// map里面的value和key都可以是null
map2[2] =null;
// 检索Map是否含有某keyassert(map2.containsKey(1));
// 删除某个键值对(参数为key值)
map2.remove(1);

5、异常(Exception)

  • throw

抛出固定类型的异常

throw new FormatException('Expected at least 1 section');

抛出任意类型的异常

throw 'Out of memory!';

  • on / catch

将可能出现异常的代码放到 try 语句中,可以通过 on 语句来指定需要捕获的异常类型;使用 catch 可以捕获到任意异常类型。rethrow可以重新抛出捕获的异常

test1() {
try {
test2();
} catch (e) {
print('test1 -- e -- ${e}');
}
}
test2() {
try {
throwFormatException('this is exception');
} onOutOfMemoryError { // 可以捕获指定类型的异常print('out of memory');
} onFormatExceptioncatch (e) { // 匹配不到FormatException,会继续匹配print('exception error: ${e}');
// 重新抛出异常,可以在test1的catch中重新捕获异常rethrow; } catch (e, r) { // 匹配所有类型的异常:e是异常对象,r时异常的堆栈信息print('exception details: ${e}');
print('stack trace: ${r}');
} finally { // 一定会执行,如果try/catch中有return/rethrow,会在return/rethrow之前执行print('this is finally');
}
}

6、函数(Function)

Dart 的函数有两种类型的参数:必需的和可选的。可选参数可以有一个默认值,在调用者没有指定可选参数时使用默认值。可选参数又分为:可选命名参数和可选位置参数。

  • 可选命名参数: 使用{}包围的参数属于可选命名参数,可设置默认值,在调用的时候,需要使用paramName:value的形式指定为哪个可选参数赋值。
void_testPar1(int a, {String name, int age =10}) {
print('a = ${a}, name = ${name}, age = ${age}');
}
_testPar1(123, name:'abc'); // a = 123, name = abc, age = 10_testPar1(123, age:18); // a = 123, name = null, age = 18_testPar1(123, name:'aaa', age:18); // a = 123, name = aaa, age = 18
  • 可选位置参数: 使用[]包围的参数属于可选位置参数。如果想指定某个位置上的参数,必须保证前面位置有参数(设置了默认参数也需要传)。
void _testPar2 (int a, [String str ='abc', int b]) {
print('a = ${a}, str = ${str}, b = ${b}');
}
_testPar2(123); // a = 123, str = abc, b = null_testPar2(123, 'beijing'); // a = 123, str = beijing, b = null_testPar2(123, 'beijing', 10); // a = 123, str = beijing, b = 10_testPar2(123, 10); // 会报错

7、类的构造函数

classPoint {
int x;
int y;
// 构造函数Point(int a, int b) {
x = a;
y = b;
}
// 上面的构造函数可以简化为:Point(this.x, this.y);
// 命名构造函数Point.fromJson(Map data) {
x = data['x'];
y = data['y'];
}
// 可以在构造函数打括号前来初始化实例的变量值,多个实例变量用逗号隔开Point.fromJson(Map data) : x = data['x'], y = data['y'];
}

8、Dart 中的异步

  • Event-Looper

    Dart 是单线程模型,也就没有所谓的主线程/子线程。 Dart 也是Event-Looper以及Event-Queue的模型,所有的事件都是通过EventLooper依次执行。 Dart 的Event Loop是:

    • 从EventQueue中获取Event
    • 处理Event
    • 直到EventQueue为空

    Event Queue

    这些Event包括了用户输入,点击,Timer,文件IO等

    Event Queue

  • 单线程模型

    Dart 中没有线程的概念,只有isolate,每个isolate都是隔离的,并不会共享内存。一个 Dart 程序是在Main isolate的main函数开始,在main函数结束后,Main isolate开始一个一个的处理Event Queue中的每一个Event。

    Event Queue

  • Event Queue以及Microtask Queue

    Dart 中的Main isolate只有一个Event Looper,但存在两个Queue:Event Queue和Microtask Queue。Microtask Queue存在的意义是:

    希望通过这个Queue来处理稍晚一些,但是在下一个消息到来之前需要处理完的事情。

    当Event Looper正在处理Microtask Queue中的Event时,Event Queue中的Event就停止了处理,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等。

    Event-Looper挑选Task的执行顺序为:

    • 优先全部执行完Microtask Queue中的Event
    • 直到Microtask Queue为空时,再执行Event Queue中的Event

    Event Queue

    Dart 中只知道Event处理的先后顺序,并不知道某个Event执行的具体时间点。

  • 异步任务调度

    当有代码可以在后续任务执行的时候,有两种方式,通过 dart:async 中的API即可

    • 使用Future类,可以将任务加入到Event Queue的队尾
    // 一般常用的Future构造函数newFuture(() {
    // do something
    });
    // 当需要将任务拆分成很多小任务一步一步执行时,可以使用Future.then来拆解任务newFuture(() {
    print('abc');
    return21;
    }).then((value) =>'!!!then01 $value!!!') //这里的value是上面任务的返回值
    .then((value) {
    // 这里的value为上一个任务执行完的返回结果print('===then02 $value==='); // ===then02 !!!then01 21!!!===return'balabala';
    }).then((value){
    // 这里的value是上个任务的返回值print('~~~then03 $value~~~~'); // ~~~then03 balabala~~~~
    }).whenComplete(() {
    print('complete');
    });
    // 当任务需要延时执行,可以使用Future.delay。不是绝对精准的。newFuture.delayed(constDuration(seconds:1), () {print('123');});
    • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾
     import 'dart:async';
    voidmain() {
    newFuture.delayed(constDuration(seconds:1), () {print('delay');});
    print('main---111');
    scheduleMicrotask(() {
    print('microtask---111');
    });
    newFuture(() {
    print('future');
    return21;
    }).then((value) {
    print('future-then-$value');
    }).whenComplete(() {
    print('complete');
    });
    scheduleMicrotask(() {
    print('microtask---222');
    });
    print('main---222');
    }

    上面代码的输出顺序为:

    main---111 main---222 microtask---111 microtask---222 future future-then-21 complete delay

    • 再看一个较复杂的例子
    scheduleMicrotask(() =>print('000'));
    newFuture(() =>print('111'))
    .then((_) {
    print('222');
    scheduleMicrotask(() =>print('333'));
    }).then((_) =>print('444'));
    scheduleMicrotask(() =>print('555'));
    newFuture(() =>print('666'))
    .then((_) {
    print('777');
    Future(() =>print('888'));
    }).then((_) =>print('999'));
    newFuture(() =>print('aaa'));
    scheduleMicrotask(() =>print('bbb'));

    上面输出顺序为:0-5-b-1-2-4-3-6-7-9-a-8

  • async和await

    // async关键字声明该函数内部有延时执行的任务asyncAndAwait() async {
    // 在Dart中,有await修饰的,其结果是一个Future对象var a =awaittest0();
    print('a-type:${a.runtimeType}');
    print('a = ${a}');
    var b =awaittest1();
    print('b-type:${b.runtimeType}');
    print('b = ${b}');
    }
    test0() {
    returnFuture(() =>'22222');
    }
    test1() {
    returnFuture(() =>'22222')
    .then((value) {
    print('then-value=${value}');
    }).whenComplete(() {
    print('complete~');
    });
    }

    上面代码的输出顺序依次为:

    a-type:String a = 11111 then-value=22222 complete~ b-type:Null b = null

二、Widget基础篇

1、Widget State

Flutter 中,一切能显示的都是 WidgetWidget 是一切的基础,作为响应式的渲染,属于 MVVM 的实现机制。我们可以通过修改数据,再用 setState 设置数据, Flutter 会自动通过绑定的数据更新 Widget 。我们需要做的就是实现 Widget 页面,并且和数据绑定起来。

Widget 分为 有状态无状态 两种。在 Flutter 中每个页面都是一帧。无状态就是保持在那一帧;有状态的 Widget 当数据更新时,其实就是绘制了新的 Widget

  • 无状态 StatelessWidget

    无状态的 Widget 实现很简单:创建一个继承自 StatelessWidgetWidget ,并通过 build 方法返回一个布局好的控件。WidgetWidget 之间通过 childchildren 进行嵌套。

import'package:flutter/material.dart';
classMyStatelessextendsStatelessWidget {
finalString text;
MyStateless(this.text);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:Text('StatelessWidget'),
),
body:Container(
color:Colors.white,
//Dart语法中,?? 表示如果text为空,就返回 ?? 后的内容。
child:Text(text ??"这就是无状态DMEO"),
),
);
}
}
  • 有状态 StatefulWidget

有状态的 Widget 需要管理 state ,通过 setState 改变数据触发 Widget 重新构建刷新。主要的生命周期有:

initState: 初始化 didChangeDependencies:initState 之后 build 之前调用 dispose: 销毁

classMyStatefulextendsStatefulWidget {
@overrideState<StatefulWidget> createState() {
return_MyStatefulState();
}
}
class_MyStatefulStateextendsState<MyStateful> {
int pressCount;
@overridevoidinitState() {
super.initState();
pressCount =0;
}
@overrideWidgetbuild(BuildContext context) {
returnContainer(
child:FlatButton(
onPressed: () {
setState(() {
pressCount +=1;
});
},
child:Center(
child:Text('有状态的Widget,可戳👇--$pressCount'),
)
),
);
}
}

不管是有状态的还是无状态的 Widget ,我们需要做的就是在 build 中堆积我们的布局,把数据添加到 Widget 中,有状态的可以通过 setState 改变数据,从而刷新UI,就这么简单。

2、Flutter中的Widget整理

先来看一张图,这张图中几乎包含了 Flutter 中所有的 Widget

我们先针对其中比较常用的讲解下其特性和使用方法。

3、布局相关的常用Widget

  • Container

    最常用的布局。只能包含一个 child ,默认铺满。支持配置 paddingmargincolor 、宽高、decoration (一般配置边框和阴影等)。不是所有的控件都有 paddingmargincolor 、宽高等属性,所以才会有 PaddingCenterWidget 的存在。

    alignment: 设置Container内child的对其方式(注意不是Container本身的对其方式)

    width/height/color: 设置Container的宽高和背景色

    padding/margin: padding设置Container边缘和其child内容的距离,margin设置Container和其它外部Widget的距离。

    decoration: Container的修饰器,主要设置背景、边框、圆角等。注意:如果设置了该属性,就不能设置Container的color,否则会抛出异常

    _containerDemo() {
    returnContainer(
    alignment:Alignment.center,
    child:Text('balabalabala'),
    height:49,
    margin:EdgeInsets.all(20),
    padding:EdgeInsets.only(top:10),
    // color: Colors.blueGrey,// 使用它就不能设置Container的color
    decoration:BoxDecoration(
    borderRadius:BorderRadius.only(
    topLeft:Radius.circular(10),
    bottomRight:Radius.circular(10)
    ),
    color:Colors.blue,
    border:Border.all(
    color:Color(0xFF000000),
    width:1
    )
    ),
    );
    }
  • Column、Row和Expanded

    竖直布局和水平布局也是很常见的场景。它们都可以通过 children 设置多个子 Widget ,可以设置主轴和副轴方向布局方式等。而 ExpendedColumnRow 中默认代表着平均充满。当然可以通过设置 flex 属性决定占屏比例。

    _columnDemo() {
    returnColumn(
    children:<Widget>[
    Expanded(child:Container(
    child:Text('aaa'),
    color:Colors.grey,
    )),
    Expanded(child:Container(
    child:Text('bbb'),
    color:Colors.blueGrey,
    )),
    Expanded(
    child:Container(
    child:Text('ccc'),
    color:Colors.red
    ),
    flex:2
    )
    ],
    // 主轴方向布局方式
    mainAxisAlignment:MainAxisAlignment.center,
    // 大小按照最大充满(这里使用Expanded会最大充满不受该属性设置影响)
    mainAxisSize :MainAxisSize.min,
    // 副轴方向布局方式
    crossAxisAlignment:CrossAxisAlignment.center
    );
    }

4、其它常用Widget

Flutter 中除了布局的 Widget ,还有交互显示和完整页面呈现的 Widget 。其中常见的有 MaterialAppScaffoldAppbarTextImageFlatButton 等。

常用Widget及特点

  • Text

    textAlign: 设置文本对其方式:center、left、right、start、end

    maxLines: 设置最多显示的行数

    overflow: 文本溢出时怎么处理。clip:直接切断;ellipsis:...;fade:渐变(上下渐变,不是左右)

    style: 通过TextStyle设置字体大小、颜色、weight等

    _textDemo() {
    returnContainer(
    width:100,
    child:Text(
    'balabalabalabalabalabalabalabalabalabalabalabalabalabalabala',
    textAlign:TextAlign.end,
    style:TextStyle(
    fontSize:20,
    color:Colors.white,
    fontWeight:FontWeight.w500
    ),
    maxLines:1,
    overflow:TextOverflow.fade,
    )
    );
    }
  • image

    加载图片有四种方式分别为:

    Image.asset: 加载项目资源目录中的图片,相对路径

    Image.network: 加载网络图片

    Image.file: 加载本地图片,绝对路径

    Image.memory: 加载Uint8List资源图片(没使用过😢)

    通过fit属性设置图片的填充方式:

    BoxFit.fill: 图片拉伸以充满父容器

    BoxFit.contain: 比例不变拉伸,可能会有空隙(水平和竖直方向最多有一个方向有空隙)

    BoxFit.cover: 可能被裁减,比例不变拉伸,充满父容器

    BoxFit.fitWidth/fitHeight: 宽度/高度充满

    BoxFit.scaleDown: 效果和contain类似,但图片不会被拉伸超过源图片大小

    _imageDemo() {
    returnContainer(
    alignment:Alignment.center,
    width:300,
    height:400,
    color:Colors.blue,
    child:Image.network(
    'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2527385882,2413489660&fm=26&gp=0.jpg',
    fit:BoxFit.contain,
    )
    );
    }
  • TextField

    TextFieldCupertinoTextField 是两种不同风格的 textField ,两者样式有很大差异,用法略有不同。先来看下 TextField 的基本使用:

    _textFieldDemo0() {
    returnTextField(
    maxLength:30,
    maxLines:1,
    // 是否自动聚焦
    autofocus:false,
    // 是否为密码输入框
    obscureText:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    // 装饰效果
    decoration:InputDecoration(
    // decoration是否使用fillColor填充,默认为false
    filled:false,
    fillColor:Colors.red,
    hintText:'placeholder',
    helperText:'helperText',
    labelText:'labelText'
    ),
    enabled:true,
    // 在onSubmitted之前调用。实现该方法后,点键盘return输入框不会失去焦点
    onEditingComplete: () {
    print('TextField:complete');
    }
    );
    }

    下面 CupertinoTextField (iOS风格的TextField)的基本使用:

    _textFieldDemo1() {
    returnCupertinoTextField(
    maxLength:200,
    maxLines:1,
    autofocus:false,
    // 密码
    obscureText:false,
    autocorrect:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    placeholder:'placeholder',
    clearButtonMode:OverlayVisibilityMode.editing,
    enabled:true,
    // 设置光标宽度,默认为2
    cursorWidth:2,
    // 设置光标颜色,默认为blue
    cursorColor:Colors.red,
    // 在onSubmitted之前调用。实现了该方法后,按return不会让textField失去焦点
    onEditingComplete: () {
    print('CupertinoTextField:complete');
    }
    );
    }
  • RaisedButton & FlatButton

    FlatButton用法和RaisedButton基本相同,但样式上FlatButton属于扁平化,RaisedButton属于拟物化,有立体感(凸起),可以设置Button底部阴影效果。

    // RaisedButton 凸起效果_raisedButtonDemo() {
    returnRaisedButton(
    onPressed: () =>_showMyDialog(),
    // highlight状态发生变化就调用(高亮和取消高亮都调用)
    onHighlightChanged: (value) {
    print('highlight changed: ${value}');
    },
    // 文本文字颜色
    textColor:Colors.white,
    // 背景色
    color:Colors.blue,
    child:Text('戳'),
    padding:EdgeInsets.all(10),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    color:Colors.black,
    width:1
    )
    ),
    elevation:10,
    );
    }
  • OutlineButton

    线框按钮,它的color属性不会生效(即不能通过color设置OutlineButton的背景色),带有默认边框。其余用法和效果同FlatButton,都是扁平化的效果。

    // OutlineButton 扁平化,默认带边框_outlineButtonDemo() {
    returnOutlineButton(
    onPressed: () =>_showMyDialog(),
    textColor:Colors.blue,
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // borderSide可以设置边框的颜色、宽度和边框样式(none和solid两种)
    borderSide:BorderSide(color:Colors.blue),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    // 这里的BorderSide只可以设置边框样式,并且优先级低
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }
  • RawMaterialButton

    其效果类似FlatButton,用法与上面的三种button稍有差异。上面介绍的几种button,它们的很多属性(如shape)可以设置为null,但RawMaterialButton的属性不允许有null,否则抛出异常。

    // MaterialButton 扁平化,效果类似FlatButton_rawMaterialButtonDemo(bool withBorder) {
    returnRawMaterialButton(
    onPressed: () =>_showMyDialog(),
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // 填充色/背景色
    fillColor:Colors.blue,
    textStyle:TextStyle(color:Colors.white),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }

    ⚠️ 以上这几种button都可以设置其padding值,但它们都有一个默认的padding。当这些button的上一层级Widget的尺寸足够容纳button,这时设置的padding值比默认值大才会生效。当button上一层级的Widget不足以展示button的默认padding时,设置较小的padding可以生效。我们可以通过在button外面包裹一层Container,并设置其alignment为center,再根据需求设置Container的宽高,就可以布局出自己想要的button样式。

  • 通过GestureDetector实现自定义样式的button

    由于上面介绍的几种button,都有一些无法改变的固定样式,这往往不能满足开发中的特定需求。除了上面说的利用Container包裹button以达到想要的样式之外,还可以通过GestureDetector来实现自定义样式的button(其实就是在自己布局的Widget基础上添加了点击手势功能)。

    _myButton() {
    returnGestureDetector(
    onTap: () =>_showMyDialog(),
    child:Container(
    width:70,
    height:28,
    margin:EdgeInsets.only(top:20),
    decoration:BoxDecoration(
    borderRadius:BorderRadius.circular(11.0),
    border:Border.all(width:0.5, color:Color(0xFF6BCBD7))
    ),
    child:Center(
    child:Text('点击刷新',
    style:TextStyle(
    fontSize:13,
    color:Color(0xFF6BCBD7)
    ),
    ),
    ),
    ),
    );
    }
  • CupertinoButton

    CupertinoButton是iOS风格的button

    _iosButtonDemo() {
    returnContainer(
    margin:EdgeInsets.only(top:15),
    child:CupertinoButton(
    child:Text(
    'abc',
    style:TextStyle(fontSize:17, color:Colors.white),
    ),
    color:Colors.blue,
    minSize:20,
    padding:EdgeInsets.fromLTRB(15, 10, 15, 10),
    borderRadius:BorderRadius.circular(10),
    onPressed: () =>_showMyDialog()
    )
    );
    }
    // 以上所有例子中的按钮点击事件_showMyDialog() {
    showDialog(
    context: context,
    builder: (context) {
    returnAlertDialog(
    title:Text('我是title'),
    content:Text('我是content'),
    actions:<Widget>[
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('确定')
    ),
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('取消')
    )
    ],
    );
    }
    );
    }

About

Dart简单语法介绍和Flutter中常用Widget介绍

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

一、Dart基础

1、一些重要的概念

  • Dart 所有的东西都是对象,即使是数字、函数、null也都是对象。所有的对象都继承自 Object 类。
  • Dart 动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下该类型是 dynamic(动态的)。
  • Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
  • Dart 没有public、private和protected关键字。如果标识符以下划线(_)开头,则该标识符对其库是私有的。
  • 没有初始化的变量都会被赋予默认值 null。
  • final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。

2、Dart 中的关键字

关键字

3、常量与变量

name的类型被推断为String:

var name = 'Bob';

如果对象不限于单一类型,或没有明确的类型,可用dynamic/Object:

Object name1 ='zf';
print('name1: ${name1.runtimeType}'); // String
name1 = ['a', 'b'];
print('name1: ${name1.runtimeType}'); // List<String>dynamic name2 ='zf';
print('name2: ${name2.runtimeType}'); // String
name2 =123;
print('name2: ${name2.runtimeType}'); // int

显式声明变量的类型:

String name = 'Bob';

所有未初始化的变量默认值都是null

int lineCount;
// 在生产环境,assert()会被忽略。// 在开发环境,assert(condition)的condition不为真时抛出一个异常。assert(lineCount ==null);

如果某个变量只打算设置一次值,请使用final或const修饰。final变量只能被初始化一次,const变量是一个编译时常量(const变量是隐式的final)。final修饰的变量在第一次声明的时候就要初始化。被final或者const修饰的变量,类型可以省略,但建议指定类型:

// 可以省略String这个类型声明final name0 ='Bob';
finalString name1 ='Bob';
const name2 ='Bob';
constString name3 ='Bob';

被final或const修饰的变量值不能再修改。final或const不能和var同时使用。常量如果是类级别的,建议使用static const:

static const String name = 'Tom';

const关键字不只是声明常数变量,还可以用来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有一个常量值:

// 注意:[]创建的是一个空的list集合// const []创建一个空的,不可变的列表var varList =const [];
final finalList =const [];
const constList =const [];
// 可以更改非final/const变量的值,即便它曾经具有const的值
varList = ['hahaha'];

在常量表达式中,final/const的操作对象必须为bool、num、string或null,const常量必须用const类型的值初始化:

constString name0 ='a';
finalString name1 ='a';
constString name2 ='a';
const aConstList =const ['1', '2'];
// 报错:Const variables must be initialized with a constant valueconst validConstString0 ='$name0 $name1';
// 报错:In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'const validConstString1 ='$name0 $name2 $aConstList';
// 正确写法const validConstString2 ='$name0 $name2';

4、数据类型

  • num: 是数字类型的父类,有两个子类 int 和 double
int a =1;
print(a);
double b =1.11;
print(b);
// String -> intint c =int.parse('1');
// String -> doubledouble d =double.parse('1.11');
// int -> StringString str1 = a.toString();
// 报错:The argument type 'int' can't be assigned to the parameter type 'String'print(str1 + a);
// 正确// print('${str1} ${a}');print(str1 + a.toString());
// double -> String,// toStringAsFixed()// 参数表示要保留几位小数,参数必填// 填写的整数值大于小数的小数点位数时,会补0// 返回结果四舍五入double num0 =3.141592653;
double num1 =3.14;
String str0 = num0.toStringAsFixed(0);
String str1 = num0.toStringAsFixed(3);
String str2 = num1.toStringAsFixed(4);
// str0 = 3, str1 = 3.142, str2 = 3.1400print('str0 = ${str0}, str1 = ${str1}, str2 = ${str2}');
// toStringAsExponential()// 科学计数,参数可为空,参数表示要保留几位小数,会四舍五入double num2 =12345.678;
String str3 = num2.toStringAsExponential();
String str4 = num2.toStringAsExponential(0);
String str5 = num2.toStringAsExponential(4);
// str3 = 1.2345678e+4, str4 = 1e+4, str5 = 1.2346e+4print('str3 = ${str3}, str4 = ${str4}, str5 = ${str5}');
// toStringAsPrecision()// 参数必填且>0, 表示有效数字的位数,也会四舍五入double num3 =123.654;
int num4 =1;
String str6 = num3.toStringAsPrecision(3);
String str7 = num3.toStringAsPrecision(1);
String str8 = num4.toStringAsPrecision(2);
// str6 = 124, str7 = 1e+2, str8 = 1.0print('str6 = ${str6}, str7 = ${str7}, str8 = ${str8}');
  • String: 可以使用单引号/双引号创建字符串;引号可以嵌套使用
String str1 ='aaaaa';
String str2 ='bbbbb';
int a =1;
String str3 ='$str1 a "b" ${str2.toUpperCase()} ${a.toString()}';
  • bool
    • Dart 是强bool类型检查,只有bool类型的值是true,才被认为是true
    • 只有两个对象具有bool类型:true和false,它们都是编译时常量
    • assert 是语言内置的断言函数,仅在检查模式下有效。条件不为真则引发异常
var fullName ='';
assert(fullName.isEmpty);
var a =0/0;
assert(a.isNaN);
var b;
assert(b ==null);
  • List集合: 元素类型可以是任意类型
// 使用List的构造函数。如果传递int参数,表示List固定长度,不可增删操作List list0 =newList();
List list1 = ['a', 10, true, null, ['bala', 'heihei']];
// 添加一个元素
list1.add('apple');
// 添加多个元素
list1.addAll(['orange', 'banana']);
// 获取List的长度/首个元素/最后一个元素/指定下标元素/某个元素的索引print('${list1.length}, ${list1.first}, ${list1.last}, ${list1[0]}, ${list1.indexOf('apple')}');
// 删除指定位置的元素,返回被删除的元素print(list1.removeAt(0));
// 删除指定元素,返回true/false。如果集合中有多个相同元素,只会删除第一个
list1.remove('apple');
// 删除最后一个元素,返回被删除的元素print(list1.removeLast());
// 删除指定范围的元素,含头不含尾,没有返回值
list1.removeRange(0, 1);
// 删除指定条件的元素,没有返回值
list1.removeWhere((item) => item.runtimeType ==String);
// 删除所有的元素,没有返回值
list1.clear();
  • Map集合: map的键和值可以是任何类型的对象
// 这种方式初始化map时,key不能相同,否则报错:Two keys in a map literal can't be equal.Map map0 = {'name':'aaa', 'age':18, 111:true, null:null, ['a']: {'xxx':'yyy'}};
// 另一种创建方式Map map1 =newMap();
map1['name'] ='aaa';
map1['age'] =18;
// 指定键值对的参数类型。如果指定了key/value类型,赋值时必须为指定的类型Map map2 =newMap<int, String>();
// 对同一key进行赋值会被覆盖
map2[1] ='abc';
map2[1] ='aaa';
// map里面的value和key都可以是null
map2[2] =null;
// 检索Map是否含有某keyassert(map2.containsKey(1));
// 删除某个键值对(参数为key值)
map2.remove(1);

5、异常(Exception)

  • throw

抛出固定类型的异常

throw new FormatException('Expected at least 1 section');

抛出任意类型的异常

throw 'Out of memory!';

  • on / catch

将可能出现异常的代码放到 try 语句中,可以通过 on 语句来指定需要捕获的异常类型;使用 catch 可以捕获到任意异常类型。rethrow可以重新抛出捕获的异常

test1() {
try {
test2();
} catch (e) {
print('test1 -- e -- ${e}');
}
}
test2() {
try {
throwFormatException('this is exception');
} onOutOfMemoryError { // 可以捕获指定类型的异常print('out of memory');
} onFormatExceptioncatch (e) { // 匹配不到FormatException,会继续匹配print('exception error: ${e}');
// 重新抛出异常,可以在test1的catch中重新捕获异常rethrow; } catch (e, r) { // 匹配所有类型的异常:e是异常对象,r时异常的堆栈信息print('exception details: ${e}');
print('stack trace: ${r}');
} finally { // 一定会执行,如果try/catch中有return/rethrow,会在return/rethrow之前执行print('this is finally');
}
}

6、函数(Function)

Dart 的函数有两种类型的参数:必需的和可选的。可选参数可以有一个默认值,在调用者没有指定可选参数时使用默认值。可选参数又分为:可选命名参数和可选位置参数。

  • 可选命名参数: 使用{}包围的参数属于可选命名参数,可设置默认值,在调用的时候,需要使用paramName:value的形式指定为哪个可选参数赋值。
void_testPar1(int a, {String name, int age =10}) {
print('a = ${a}, name = ${name}, age = ${age}');
}
_testPar1(123, name:'abc'); // a = 123, name = abc, age = 10_testPar1(123, age:18); // a = 123, name = null, age = 18_testPar1(123, name:'aaa', age:18); // a = 123, name = aaa, age = 18
  • 可选位置参数: 使用[]包围的参数属于可选位置参数。如果想指定某个位置上的参数,必须保证前面位置有参数(设置了默认参数也需要传)。
void _testPar2 (int a, [String str ='abc', int b]) {
print('a = ${a}, str = ${str}, b = ${b}');
}
_testPar2(123); // a = 123, str = abc, b = null_testPar2(123, 'beijing'); // a = 123, str = beijing, b = null_testPar2(123, 'beijing', 10); // a = 123, str = beijing, b = 10_testPar2(123, 10); // 会报错

7、类的构造函数

classPoint {
int x;
int y;
// 构造函数Point(int a, int b) {
x = a;
y = b;
}
// 上面的构造函数可以简化为:Point(this.x, this.y);
// 命名构造函数Point.fromJson(Map data) {
x = data['x'];
y = data['y'];
}
// 可以在构造函数打括号前来初始化实例的变量值,多个实例变量用逗号隔开Point.fromJson(Map data) : x = data['x'], y = data['y'];
}

8、Dart 中的异步

  • Event-Looper

    Dart 是单线程模型,也就没有所谓的主线程/子线程。 Dart 也是Event-Looper以及Event-Queue的模型,所有的事件都是通过EventLooper依次执行。 Dart 的Event Loop是:

    • 从EventQueue中获取Event
    • 处理Event
    • 直到EventQueue为空

    Event Queue

    这些Event包括了用户输入,点击,Timer,文件IO等

    Event Queue

  • 单线程模型

    Dart 中没有线程的概念,只有isolate,每个isolate都是隔离的,并不会共享内存。一个 Dart 程序是在Main isolate的main函数开始,在main函数结束后,Main isolate开始一个一个的处理Event Queue中的每一个Event。

    Event Queue

  • Event Queue以及Microtask Queue

    Dart 中的Main isolate只有一个Event Looper,但存在两个Queue:Event Queue和Microtask Queue。Microtask Queue存在的意义是:

    希望通过这个Queue来处理稍晚一些,但是在下一个消息到来之前需要处理完的事情。

    当Event Looper正在处理Microtask Queue中的Event时,Event Queue中的Event就停止了处理,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等。

    Event-Looper挑选Task的执行顺序为:

    • 优先全部执行完Microtask Queue中的Event
    • 直到Microtask Queue为空时,再执行Event Queue中的Event

    Event Queue

    Dart 中只知道Event处理的先后顺序,并不知道某个Event执行的具体时间点。

  • 异步任务调度

    当有代码可以在后续任务执行的时候,有两种方式,通过 dart:async 中的API即可

    • 使用Future类,可以将任务加入到Event Queue的队尾
    // 一般常用的Future构造函数newFuture(() {
    // do something
    });
    // 当需要将任务拆分成很多小任务一步一步执行时,可以使用Future.then来拆解任务newFuture(() {
    print('abc');
    return21;
    }).then((value) =>'!!!then01 $value!!!') //这里的value是上面任务的返回值
    .then((value) {
    // 这里的value为上一个任务执行完的返回结果print('===then02 $value==='); // ===then02 !!!then01 21!!!===return'balabala';
    }).then((value){
    // 这里的value是上个任务的返回值print('~~~then03 $value~~~~'); // ~~~then03 balabala~~~~
    }).whenComplete(() {
    print('complete');
    });
    // 当任务需要延时执行,可以使用Future.delay。不是绝对精准的。newFuture.delayed(constDuration(seconds:1), () {print('123');});
    • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾
     import 'dart:async';
    voidmain() {
    newFuture.delayed(constDuration(seconds:1), () {print('delay');});
    print('main---111');
    scheduleMicrotask(() {
    print('microtask---111');
    });
    newFuture(() {
    print('future');
    return21;
    }).then((value) {
    print('future-then-$value');
    }).whenComplete(() {
    print('complete');
    });
    scheduleMicrotask(() {
    print('microtask---222');
    });
    print('main---222');
    }

    上面代码的输出顺序为:

    main---111 main---222 microtask---111 microtask---222 future future-then-21 complete delay

    • 再看一个较复杂的例子
    scheduleMicrotask(() =>print('000'));
    newFuture(() =>print('111'))
    .then((_) {
    print('222');
    scheduleMicrotask(() =>print('333'));
    }).then((_) =>print('444'));
    scheduleMicrotask(() =>print('555'));
    newFuture(() =>print('666'))
    .then((_) {
    print('777');
    Future(() =>print('888'));
    }).then((_) =>print('999'));
    newFuture(() =>print('aaa'));
    scheduleMicrotask(() =>print('bbb'));

    上面输出顺序为:0-5-b-1-2-4-3-6-7-9-a-8

  • async和await

    // async关键字声明该函数内部有延时执行的任务asyncAndAwait() async {
    // 在Dart中,有await修饰的,其结果是一个Future对象var a =awaittest0();
    print('a-type:${a.runtimeType}');
    print('a = ${a}');
    var b =awaittest1();
    print('b-type:${b.runtimeType}');
    print('b = ${b}');
    }
    test0() {
    returnFuture(() =>'22222');
    }
    test1() {
    returnFuture(() =>'22222')
    .then((value) {
    print('then-value=${value}');
    }).whenComplete(() {
    print('complete~');
    });
    }

    上面代码的输出顺序依次为:

    a-type:String a = 11111 then-value=22222 complete~ b-type:Null b = null

二、Widget基础篇

1、Widget State

Flutter 中,一切能显示的都是 WidgetWidget 是一切的基础,作为响应式的渲染,属于 MVVM 的实现机制。我们可以通过修改数据,再用 setState 设置数据, Flutter 会自动通过绑定的数据更新 Widget 。我们需要做的就是实现 Widget 页面,并且和数据绑定起来。

Widget 分为 有状态无状态 两种。在 Flutter 中每个页面都是一帧。无状态就是保持在那一帧;有状态的 Widget 当数据更新时,其实就是绘制了新的 Widget

  • 无状态 StatelessWidget

    无状态的 Widget 实现很简单:创建一个继承自 StatelessWidgetWidget ,并通过 build 方法返回一个布局好的控件。WidgetWidget 之间通过 childchildren 进行嵌套。

import'package:flutter/material.dart';
classMyStatelessextendsStatelessWidget {
finalString text;
MyStateless(this.text);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:Text('StatelessWidget'),
),
body:Container(
color:Colors.white,
//Dart语法中,?? 表示如果text为空,就返回 ?? 后的内容。
child:Text(text ??"这就是无状态DMEO"),
),
);
}
}
  • 有状态 StatefulWidget

有状态的 Widget 需要管理 state ,通过 setState 改变数据触发 Widget 重新构建刷新。主要的生命周期有:

initState: 初始化 didChangeDependencies:initState 之后 build 之前调用 dispose: 销毁

classMyStatefulextendsStatefulWidget {
@overrideState<StatefulWidget> createState() {
return_MyStatefulState();
}
}
class_MyStatefulStateextendsState<MyStateful> {
int pressCount;
@overridevoidinitState() {
super.initState();
pressCount =0;
}
@overrideWidgetbuild(BuildContext context) {
returnContainer(
child:FlatButton(
onPressed: () {
setState(() {
pressCount +=1;
});
},
child:Center(
child:Text('有状态的Widget,可戳👇--$pressCount'),
)
),
);
}
}

不管是有状态的还是无状态的 Widget ,我们需要做的就是在 build 中堆积我们的布局,把数据添加到 Widget 中,有状态的可以通过 setState 改变数据,从而刷新UI,就这么简单。

2、Flutter中的Widget整理

先来看一张图,这张图中几乎包含了 Flutter 中所有的 Widget

我们先针对其中比较常用的讲解下其特性和使用方法。

3、布局相关的常用Widget

  • Container

    最常用的布局。只能包含一个 child ,默认铺满。支持配置 paddingmargincolor 、宽高、decoration (一般配置边框和阴影等)。不是所有的控件都有 paddingmargincolor 、宽高等属性,所以才会有 PaddingCenterWidget 的存在。

    alignment: 设置Container内child的对其方式(注意不是Container本身的对其方式)

    width/height/color: 设置Container的宽高和背景色

    padding/margin: padding设置Container边缘和其child内容的距离,margin设置Container和其它外部Widget的距离。

    decoration: Container的修饰器,主要设置背景、边框、圆角等。注意:如果设置了该属性,就不能设置Container的color,否则会抛出异常

    _containerDemo() {
    returnContainer(
    alignment:Alignment.center,
    child:Text('balabalabala'),
    height:49,
    margin:EdgeInsets.all(20),
    padding:EdgeInsets.only(top:10),
    // color: Colors.blueGrey,// 使用它就不能设置Container的color
    decoration:BoxDecoration(
    borderRadius:BorderRadius.only(
    topLeft:Radius.circular(10),
    bottomRight:Radius.circular(10)
    ),
    color:Colors.blue,
    border:Border.all(
    color:Color(0xFF000000),
    width:1
    )
    ),
    );
    }
  • Column、Row和Expanded

    竖直布局和水平布局也是很常见的场景。它们都可以通过 children 设置多个子 Widget ,可以设置主轴和副轴方向布局方式等。而 ExpendedColumnRow 中默认代表着平均充满。当然可以通过设置 flex 属性决定占屏比例。

    _columnDemo() {
    returnColumn(
    children:<Widget>[
    Expanded(child:Container(
    child:Text('aaa'),
    color:Colors.grey,
    )),
    Expanded(child:Container(
    child:Text('bbb'),
    color:Colors.blueGrey,
    )),
    Expanded(
    child:Container(
    child:Text('ccc'),
    color:Colors.red
    ),
    flex:2
    )
    ],
    // 主轴方向布局方式
    mainAxisAlignment:MainAxisAlignment.center,
    // 大小按照最大充满(这里使用Expanded会最大充满不受该属性设置影响)
    mainAxisSize :MainAxisSize.min,
    // 副轴方向布局方式
    crossAxisAlignment:CrossAxisAlignment.center
    );
    }

4、其它常用Widget

Flutter 中除了布局的 Widget ,还有交互显示和完整页面呈现的 Widget 。其中常见的有 MaterialAppScaffoldAppbarTextImageFlatButton 等。

常用Widget及特点

  • Text

    textAlign: 设置文本对其方式:center、left、right、start、end

    maxLines: 设置最多显示的行数

    overflow: 文本溢出时怎么处理。clip:直接切断;ellipsis:...;fade:渐变(上下渐变,不是左右)

    style: 通过TextStyle设置字体大小、颜色、weight等

    _textDemo() {
    returnContainer(
    width:100,
    child:Text(
    'balabalabalabalabalabalabalabalabalabalabalabalabalabalabala',
    textAlign:TextAlign.end,
    style:TextStyle(
    fontSize:20,
    color:Colors.white,
    fontWeight:FontWeight.w500
    ),
    maxLines:1,
    overflow:TextOverflow.fade,
    )
    );
    }
  • image

    加载图片有四种方式分别为:

    Image.asset: 加载项目资源目录中的图片,相对路径

    Image.network: 加载网络图片

    Image.file: 加载本地图片,绝对路径

    Image.memory: 加载Uint8List资源图片(没使用过😢)

    通过fit属性设置图片的填充方式:

    BoxFit.fill: 图片拉伸以充满父容器

    BoxFit.contain: 比例不变拉伸,可能会有空隙(水平和竖直方向最多有一个方向有空隙)

    BoxFit.cover: 可能被裁减,比例不变拉伸,充满父容器

    BoxFit.fitWidth/fitHeight: 宽度/高度充满

    BoxFit.scaleDown: 效果和contain类似,但图片不会被拉伸超过源图片大小

    _imageDemo() {
    returnContainer(
    alignment:Alignment.center,
    width:300,
    height:400,
    color:Colors.blue,
    child:Image.network(
    'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2527385882,2413489660&fm=26&gp=0.jpg',
    fit:BoxFit.contain,
    )
    );
    }
  • TextField

    TextFieldCupertinoTextField 是两种不同风格的 textField ,两者样式有很大差异,用法略有不同。先来看下 TextField 的基本使用:

    _textFieldDemo0() {
    returnTextField(
    maxLength:30,
    maxLines:1,
    // 是否自动聚焦
    autofocus:false,
    // 是否为密码输入框
    obscureText:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    // 装饰效果
    decoration:InputDecoration(
    // decoration是否使用fillColor填充,默认为false
    filled:false,
    fillColor:Colors.red,
    hintText:'placeholder',
    helperText:'helperText',
    labelText:'labelText'
    ),
    enabled:true,
    // 在onSubmitted之前调用。实现该方法后,点键盘return输入框不会失去焦点
    onEditingComplete: () {
    print('TextField:complete');
    }
    );
    }

    下面 CupertinoTextField (iOS风格的TextField)的基本使用:

    _textFieldDemo1() {
    returnCupertinoTextField(
    maxLength:200,
    maxLines:1,
    autofocus:false,
    // 密码
    obscureText:false,
    autocorrect:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    placeholder:'placeholder',
    clearButtonMode:OverlayVisibilityMode.editing,
    enabled:true,
    // 设置光标宽度,默认为2
    cursorWidth:2,
    // 设置光标颜色,默认为blue
    cursorColor:Colors.red,
    // 在onSubmitted之前调用。实现了该方法后,按return不会让textField失去焦点
    onEditingComplete: () {
    print('CupertinoTextField:complete');
    }
    );
    }
  • RaisedButton & FlatButton

    FlatButton用法和RaisedButton基本相同,但样式上FlatButton属于扁平化,RaisedButton属于拟物化,有立体感(凸起),可以设置Button底部阴影效果。

    // RaisedButton 凸起效果_raisedButtonDemo() {
    returnRaisedButton(
    onPressed: () =>_showMyDialog(),
    // highlight状态发生变化就调用(高亮和取消高亮都调用)
    onHighlightChanged: (value) {
    print('highlight changed: ${value}');
    },
    // 文本文字颜色
    textColor:Colors.white,
    // 背景色
    color:Colors.blue,
    child:Text('戳'),
    padding:EdgeInsets.all(10),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    color:Colors.black,
    width:1
    )
    ),
    elevation:10,
    );
    }
  • OutlineButton

    线框按钮,它的color属性不会生效(即不能通过color设置OutlineButton的背景色),带有默认边框。其余用法和效果同FlatButton,都是扁平化的效果。

    // OutlineButton 扁平化,默认带边框_outlineButtonDemo() {
    returnOutlineButton(
    onPressed: () =>_showMyDialog(),
    textColor:Colors.blue,
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // borderSide可以设置边框的颜色、宽度和边框样式(none和solid两种)
    borderSide:BorderSide(color:Colors.blue),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    // 这里的BorderSide只可以设置边框样式,并且优先级低
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }
  • RawMaterialButton

    其效果类似FlatButton,用法与上面的三种button稍有差异。上面介绍的几种button,它们的很多属性(如shape)可以设置为null,但RawMaterialButton的属性不允许有null,否则抛出异常。

    // MaterialButton 扁平化,效果类似FlatButton_rawMaterialButtonDemo(bool withBorder) {
    returnRawMaterialButton(
    onPressed: () =>_showMyDialog(),
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // 填充色/背景色
    fillColor:Colors.blue,
    textStyle:TextStyle(color:Colors.white),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }

    ⚠️ 以上这几种button都可以设置其padding值,但它们都有一个默认的padding。当这些button的上一层级Widget的尺寸足够容纳button,这时设置的padding值比默认值大才会生效。当button上一层级的Widget不足以展示button的默认padding时,设置较小的padding可以生效。我们可以通过在button外面包裹一层Container,并设置其alignment为center,再根据需求设置Container的宽高,就可以布局出自己想要的button样式。

  • 通过GestureDetector实现自定义样式的button

    由于上面介绍的几种button,都有一些无法改变的固定样式,这往往不能满足开发中的特定需求。除了上面说的利用Container包裹button以达到想要的样式之外,还可以通过GestureDetector来实现自定义样式的button(其实就是在自己布局的Widget基础上添加了点击手势功能)。

    _myButton() {
    returnGestureDetector(
    onTap: () =>_showMyDialog(),
    child:Container(
    width:70,
    height:28,
    margin:EdgeInsets.only(top:20),
    decoration:BoxDecoration(
    borderRadius:BorderRadius.circular(11.0),
    border:Border.all(width:0.5, color:Color(0xFF6BCBD7))
    ),
    child:Center(
    child:Text('点击刷新',
    style:TextStyle(
    fontSize:13,
    color:Color(0xFF6BCBD7)
    ),
    ),
    ),
    ),
    );
    }
  • CupertinoButton

    CupertinoButton是iOS风格的button

    _iosButtonDemo() {
    returnContainer(
    margin:EdgeInsets.only(top:15),
    child:CupertinoButton(
    child:Text(
    'abc',
    style:TextStyle(fontSize:17, color:Colors.white),
    ),
    color:Colors.blue,
    minSize:20,
    padding:EdgeInsets.fromLTRB(15, 10, 15, 10),
    borderRadius:BorderRadius.circular(10),
    onPressed: () =>_showMyDialog()
    )
    );
    }
    // 以上所有例子中的按钮点击事件_showMyDialog() {
    showDialog(
    context: context,
    builder: (context) {
    returnAlertDialog(
    title:Text('我是title'),
    content:Text('我是content'),
    actions:<Widget>[
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('确定')
    ),
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('取消')
    )
    ],
    );
    }
    );
    }

About

Dart简单语法介绍和Flutter中常用Widget介绍

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

一、Dart基础

1、一些重要的概念

  • Dart 所有的东西都是对象,即使是数字、函数、null也都是对象。所有的对象都继承自 Object 类。
  • Dart 动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下该类型是 dynamic(动态的)。
  • Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
  • Dart 没有public、private和protected关键字。如果标识符以下划线(_)开头,则该标识符对其库是私有的。
  • 没有初始化的变量都会被赋予默认值 null。
  • final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。

2、Dart 中的关键字

关键字

3、常量与变量

name的类型被推断为String:

var name = 'Bob';

如果对象不限于单一类型,或没有明确的类型,可用dynamic/Object:

Object name1 ='zf';
print('name1: ${name1.runtimeType}'); // String
name1 = ['a', 'b'];
print('name1: ${name1.runtimeType}'); // List<String>dynamic name2 ='zf';
print('name2: ${name2.runtimeType}'); // String
name2 =123;
print('name2: ${name2.runtimeType}'); // int

显式声明变量的类型:

String name = 'Bob';

所有未初始化的变量默认值都是null

int lineCount;
// 在生产环境,assert()会被忽略。// 在开发环境,assert(condition)的condition不为真时抛出一个异常。assert(lineCount ==null);

如果某个变量只打算设置一次值,请使用final或const修饰。final变量只能被初始化一次,const变量是一个编译时常量(const变量是隐式的final)。final修饰的变量在第一次声明的时候就要初始化。被final或者const修饰的变量,类型可以省略,但建议指定类型:

// 可以省略String这个类型声明final name0 ='Bob';
finalString name1 ='Bob';
const name2 ='Bob';
constString name3 ='Bob';

被final或const修饰的变量值不能再修改。final或const不能和var同时使用。常量如果是类级别的,建议使用static const:

static const String name = 'Tom';

const关键字不只是声明常数变量,还可以用来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有一个常量值:

// 注意:[]创建的是一个空的list集合// const []创建一个空的,不可变的列表var varList =const [];
final finalList =const [];
const constList =const [];
// 可以更改非final/const变量的值,即便它曾经具有const的值
varList = ['hahaha'];

在常量表达式中,final/const的操作对象必须为bool、num、string或null,const常量必须用const类型的值初始化:

constString name0 ='a';
finalString name1 ='a';
constString name2 ='a';
const aConstList =const ['1', '2'];
// 报错:Const variables must be initialized with a constant valueconst validConstString0 ='$name0 $name1';
// 报错:In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'const validConstString1 ='$name0 $name2 $aConstList';
// 正确写法const validConstString2 ='$name0 $name2';

4、数据类型

  • num: 是数字类型的父类,有两个子类 int 和 double
int a =1;
print(a);
double b =1.11;
print(b);
// String -> intint c =int.parse('1');
// String -> doubledouble d =double.parse('1.11');
// int -> StringString str1 = a.toString();
// 报错:The argument type 'int' can't be assigned to the parameter type 'String'print(str1 + a);
// 正确// print('${str1} ${a}');print(str1 + a.toString());
// double -> String,// toStringAsFixed()// 参数表示要保留几位小数,参数必填// 填写的整数值大于小数的小数点位数时,会补0// 返回结果四舍五入double num0 =3.141592653;
double num1 =3.14;
String str0 = num0.toStringAsFixed(0);
String str1 = num0.toStringAsFixed(3);
String str2 = num1.toStringAsFixed(4);
// str0 = 3, str1 = 3.142, str2 = 3.1400print('str0 = ${str0}, str1 = ${str1}, str2 = ${str2}');
// toStringAsExponential()// 科学计数,参数可为空,参数表示要保留几位小数,会四舍五入double num2 =12345.678;
String str3 = num2.toStringAsExponential();
String str4 = num2.toStringAsExponential(0);
String str5 = num2.toStringAsExponential(4);
// str3 = 1.2345678e+4, str4 = 1e+4, str5 = 1.2346e+4print('str3 = ${str3}, str4 = ${str4}, str5 = ${str5}');
// toStringAsPrecision()// 参数必填且>0, 表示有效数字的位数,也会四舍五入double num3 =123.654;
int num4 =1;
String str6 = num3.toStringAsPrecision(3);
String str7 = num3.toStringAsPrecision(1);
String str8 = num4.toStringAsPrecision(2);
// str6 = 124, str7 = 1e+2, str8 = 1.0print('str6 = ${str6}, str7 = ${str7}, str8 = ${str8}');
  • String: 可以使用单引号/双引号创建字符串;引号可以嵌套使用
String str1 ='aaaaa';
String str2 ='bbbbb';
int a =1;
String str3 ='$str1 a "b" ${str2.toUpperCase()} ${a.toString()}';
  • bool
    • Dart 是强bool类型检查,只有bool类型的值是true,才被认为是true
    • 只有两个对象具有bool类型:true和false,它们都是编译时常量
    • assert 是语言内置的断言函数,仅在检查模式下有效。条件不为真则引发异常
var fullName ='';
assert(fullName.isEmpty);
var a =0/0;
assert(a.isNaN);
var b;
assert(b ==null);
  • List集合: 元素类型可以是任意类型
// 使用List的构造函数。如果传递int参数,表示List固定长度,不可增删操作List list0 =newList();
List list1 = ['a', 10, true, null, ['bala', 'heihei']];
// 添加一个元素
list1.add('apple');
// 添加多个元素
list1.addAll(['orange', 'banana']);
// 获取List的长度/首个元素/最后一个元素/指定下标元素/某个元素的索引print('${list1.length}, ${list1.first}, ${list1.last}, ${list1[0]}, ${list1.indexOf('apple')}');
// 删除指定位置的元素,返回被删除的元素print(list1.removeAt(0));
// 删除指定元素,返回true/false。如果集合中有多个相同元素,只会删除第一个
list1.remove('apple');
// 删除最后一个元素,返回被删除的元素print(list1.removeLast());
// 删除指定范围的元素,含头不含尾,没有返回值
list1.removeRange(0, 1);
// 删除指定条件的元素,没有返回值
list1.removeWhere((item) => item.runtimeType ==String);
// 删除所有的元素,没有返回值
list1.clear();
  • Map集合: map的键和值可以是任何类型的对象
// 这种方式初始化map时,key不能相同,否则报错:Two keys in a map literal can't be equal.Map map0 = {'name':'aaa', 'age':18, 111:true, null:null, ['a']: {'xxx':'yyy'}};
// 另一种创建方式Map map1 =newMap();
map1['name'] ='aaa';
map1['age'] =18;
// 指定键值对的参数类型。如果指定了key/value类型,赋值时必须为指定的类型Map map2 =newMap<int, String>();
// 对同一key进行赋值会被覆盖
map2[1] ='abc';
map2[1] ='aaa';
// map里面的value和key都可以是null
map2[2] =null;
// 检索Map是否含有某keyassert(map2.containsKey(1));
// 删除某个键值对(参数为key值)
map2.remove(1);

5、异常(Exception)

  • throw

抛出固定类型的异常

throw new FormatException('Expected at least 1 section');

抛出任意类型的异常

throw 'Out of memory!';

  • on / catch

将可能出现异常的代码放到 try 语句中,可以通过 on 语句来指定需要捕获的异常类型;使用 catch 可以捕获到任意异常类型。rethrow可以重新抛出捕获的异常

test1() {
try {
test2();
} catch (e) {
print('test1 -- e -- ${e}');
}
}
test2() {
try {
throwFormatException('this is exception');
} onOutOfMemoryError { // 可以捕获指定类型的异常print('out of memory');
} onFormatExceptioncatch (e) { // 匹配不到FormatException,会继续匹配print('exception error: ${e}');
// 重新抛出异常,可以在test1的catch中重新捕获异常rethrow; } catch (e, r) { // 匹配所有类型的异常:e是异常对象,r时异常的堆栈信息print('exception details: ${e}');
print('stack trace: ${r}');
} finally { // 一定会执行,如果try/catch中有return/rethrow,会在return/rethrow之前执行print('this is finally');
}
}

6、函数(Function)

Dart 的函数有两种类型的参数:必需的和可选的。可选参数可以有一个默认值,在调用者没有指定可选参数时使用默认值。可选参数又分为:可选命名参数和可选位置参数。

  • 可选命名参数: 使用{}包围的参数属于可选命名参数,可设置默认值,在调用的时候,需要使用paramName:value的形式指定为哪个可选参数赋值。
void_testPar1(int a, {String name, int age =10}) {
print('a = ${a}, name = ${name}, age = ${age}');
}
_testPar1(123, name:'abc'); // a = 123, name = abc, age = 10_testPar1(123, age:18); // a = 123, name = null, age = 18_testPar1(123, name:'aaa', age:18); // a = 123, name = aaa, age = 18
  • 可选位置参数: 使用[]包围的参数属于可选位置参数。如果想指定某个位置上的参数,必须保证前面位置有参数(设置了默认参数也需要传)。
void _testPar2 (int a, [String str ='abc', int b]) {
print('a = ${a}, str = ${str}, b = ${b}');
}
_testPar2(123); // a = 123, str = abc, b = null_testPar2(123, 'beijing'); // a = 123, str = beijing, b = null_testPar2(123, 'beijing', 10); // a = 123, str = beijing, b = 10_testPar2(123, 10); // 会报错

7、类的构造函数

classPoint {
int x;
int y;
// 构造函数Point(int a, int b) {
x = a;
y = b;
}
// 上面的构造函数可以简化为:Point(this.x, this.y);
// 命名构造函数Point.fromJson(Map data) {
x = data['x'];
y = data['y'];
}
// 可以在构造函数打括号前来初始化实例的变量值,多个实例变量用逗号隔开Point.fromJson(Map data) : x = data['x'], y = data['y'];
}

8、Dart 中的异步

  • Event-Looper

    Dart 是单线程模型,也就没有所谓的主线程/子线程。 Dart 也是Event-Looper以及Event-Queue的模型,所有的事件都是通过EventLooper依次执行。 Dart 的Event Loop是:

    • 从EventQueue中获取Event
    • 处理Event
    • 直到EventQueue为空

    Event Queue

    这些Event包括了用户输入,点击,Timer,文件IO等

    Event Queue

  • 单线程模型

    Dart 中没有线程的概念,只有isolate,每个isolate都是隔离的,并不会共享内存。一个 Dart 程序是在Main isolate的main函数开始,在main函数结束后,Main isolate开始一个一个的处理Event Queue中的每一个Event。

    Event Queue

  • Event Queue以及Microtask Queue

    Dart 中的Main isolate只有一个Event Looper,但存在两个Queue:Event Queue和Microtask Queue。Microtask Queue存在的意义是:

    希望通过这个Queue来处理稍晚一些,但是在下一个消息到来之前需要处理完的事情。

    当Event Looper正在处理Microtask Queue中的Event时,Event Queue中的Event就停止了处理,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等。

    Event-Looper挑选Task的执行顺序为:

    • 优先全部执行完Microtask Queue中的Event
    • 直到Microtask Queue为空时,再执行Event Queue中的Event

    Event Queue

    Dart 中只知道Event处理的先后顺序,并不知道某个Event执行的具体时间点。

  • 异步任务调度

    当有代码可以在后续任务执行的时候,有两种方式,通过 dart:async 中的API即可

    • 使用Future类,可以将任务加入到Event Queue的队尾
    // 一般常用的Future构造函数newFuture(() {
    // do something
    });
    // 当需要将任务拆分成很多小任务一步一步执行时,可以使用Future.then来拆解任务newFuture(() {
    print('abc');
    return21;
    }).then((value) =>'!!!then01 $value!!!') //这里的value是上面任务的返回值
    .then((value) {
    // 这里的value为上一个任务执行完的返回结果print('===then02 $value==='); // ===then02 !!!then01 21!!!===return'balabala';
    }).then((value){
    // 这里的value是上个任务的返回值print('~~~then03 $value~~~~'); // ~~~then03 balabala~~~~
    }).whenComplete(() {
    print('complete');
    });
    // 当任务需要延时执行,可以使用Future.delay。不是绝对精准的。newFuture.delayed(constDuration(seconds:1), () {print('123');});
    • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾
     import 'dart:async';
    voidmain() {
    newFuture.delayed(constDuration(seconds:1), () {print('delay');});
    print('main---111');
    scheduleMicrotask(() {
    print('microtask---111');
    });
    newFuture(() {
    print('future');
    return21;
    }).then((value) {
    print('future-then-$value');
    }).whenComplete(() {
    print('complete');
    });
    scheduleMicrotask(() {
    print('microtask---222');
    });
    print('main---222');
    }

    上面代码的输出顺序为:

    main---111 main---222 microtask---111 microtask---222 future future-then-21 complete delay

    • 再看一个较复杂的例子
    scheduleMicrotask(() =>print('000'));
    newFuture(() =>print('111'))
    .then((_) {
    print('222');
    scheduleMicrotask(() =>print('333'));
    }).then((_) =>print('444'));
    scheduleMicrotask(() =>print('555'));
    newFuture(() =>print('666'))
    .then((_) {
    print('777');
    Future(() =>print('888'));
    }).then((_) =>print('999'));
    newFuture(() =>print('aaa'));
    scheduleMicrotask(() =>print('bbb'));

    上面输出顺序为:0-5-b-1-2-4-3-6-7-9-a-8

  • async和await

    // async关键字声明该函数内部有延时执行的任务asyncAndAwait() async {
    // 在Dart中,有await修饰的,其结果是一个Future对象var a =awaittest0();
    print('a-type:${a.runtimeType}');
    print('a = ${a}');
    var b =awaittest1();
    print('b-type:${b.runtimeType}');
    print('b = ${b}');
    }
    test0() {
    returnFuture(() =>'22222');
    }
    test1() {
    returnFuture(() =>'22222')
    .then((value) {
    print('then-value=${value}');
    }).whenComplete(() {
    print('complete~');
    });
    }

    上面代码的输出顺序依次为:

    a-type:String a = 11111 then-value=22222 complete~ b-type:Null b = null

二、Widget基础篇

1、Widget State

Flutter 中,一切能显示的都是 WidgetWidget 是一切的基础,作为响应式的渲染,属于 MVVM 的实现机制。我们可以通过修改数据,再用 setState 设置数据, Flutter 会自动通过绑定的数据更新 Widget 。我们需要做的就是实现 Widget 页面,并且和数据绑定起来。

Widget 分为 有状态无状态 两种。在 Flutter 中每个页面都是一帧。无状态就是保持在那一帧;有状态的 Widget 当数据更新时,其实就是绘制了新的 Widget

  • 无状态 StatelessWidget

    无状态的 Widget 实现很简单:创建一个继承自 StatelessWidgetWidget ,并通过 build 方法返回一个布局好的控件。WidgetWidget 之间通过 childchildren 进行嵌套。

import'package:flutter/material.dart';
classMyStatelessextendsStatelessWidget {
finalString text;
MyStateless(this.text);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:Text('StatelessWidget'),
),
body:Container(
color:Colors.white,
//Dart语法中,?? 表示如果text为空,就返回 ?? 后的内容。
child:Text(text ??"这就是无状态DMEO"),
),
);
}
}
  • 有状态 StatefulWidget

有状态的 Widget 需要管理 state ,通过 setState 改变数据触发 Widget 重新构建刷新。主要的生命周期有:

initState: 初始化 didChangeDependencies:initState 之后 build 之前调用 dispose: 销毁

classMyStatefulextendsStatefulWidget {
@overrideState<StatefulWidget> createState() {
return_MyStatefulState();
}
}
class_MyStatefulStateextendsState<MyStateful> {
int pressCount;
@overridevoidinitState() {
super.initState();
pressCount =0;
}
@overrideWidgetbuild(BuildContext context) {
returnContainer(
child:FlatButton(
onPressed: () {
setState(() {
pressCount +=1;
});
},
child:Center(
child:Text('有状态的Widget,可戳👇--$pressCount'),
)
),
);
}
}

不管是有状态的还是无状态的 Widget ,我们需要做的就是在 build 中堆积我们的布局,把数据添加到 Widget 中,有状态的可以通过 setState 改变数据,从而刷新UI,就这么简单。

2、Flutter中的Widget整理

先来看一张图,这张图中几乎包含了 Flutter 中所有的 Widget

我们先针对其中比较常用的讲解下其特性和使用方法。

3、布局相关的常用Widget

  • Container

    最常用的布局。只能包含一个 child ,默认铺满。支持配置 paddingmargincolor 、宽高、decoration (一般配置边框和阴影等)。不是所有的控件都有 paddingmargincolor 、宽高等属性,所以才会有 PaddingCenterWidget 的存在。

    alignment: 设置Container内child的对其方式(注意不是Container本身的对其方式)

    width/height/color: 设置Container的宽高和背景色

    padding/margin: padding设置Container边缘和其child内容的距离,margin设置Container和其它外部Widget的距离。

    decoration: Container的修饰器,主要设置背景、边框、圆角等。注意:如果设置了该属性,就不能设置Container的color,否则会抛出异常

    _containerDemo() {
    returnContainer(
    alignment:Alignment.center,
    child:Text('balabalabala'),
    height:49,
    margin:EdgeInsets.all(20),
    padding:EdgeInsets.only(top:10),
    // color: Colors.blueGrey,// 使用它就不能设置Container的color
    decoration:BoxDecoration(
    borderRadius:BorderRadius.only(
    topLeft:Radius.circular(10),
    bottomRight:Radius.circular(10)
    ),
    color:Colors.blue,
    border:Border.all(
    color:Color(0xFF000000),
    width:1
    )
    ),
    );
    }
  • Column、Row和Expanded

    竖直布局和水平布局也是很常见的场景。它们都可以通过 children 设置多个子 Widget ,可以设置主轴和副轴方向布局方式等。而 ExpendedColumnRow 中默认代表着平均充满。当然可以通过设置 flex 属性决定占屏比例。

    _columnDemo() {
    returnColumn(
    children:<Widget>[
    Expanded(child:Container(
    child:Text('aaa'),
    color:Colors.grey,
    )),
    Expanded(child:Container(
    child:Text('bbb'),
    color:Colors.blueGrey,
    )),
    Expanded(
    child:Container(
    child:Text('ccc'),
    color:Colors.red
    ),
    flex:2
    )
    ],
    // 主轴方向布局方式
    mainAxisAlignment:MainAxisAlignment.center,
    // 大小按照最大充满(这里使用Expanded会最大充满不受该属性设置影响)
    mainAxisSize :MainAxisSize.min,
    // 副轴方向布局方式
    crossAxisAlignment:CrossAxisAlignment.center
    );
    }

4、其它常用Widget

Flutter 中除了布局的 Widget ,还有交互显示和完整页面呈现的 Widget 。其中常见的有 MaterialAppScaffoldAppbarTextImageFlatButton 等。

常用Widget及特点

  • Text

    textAlign: 设置文本对其方式:center、left、right、start、end

    maxLines: 设置最多显示的行数

    overflow: 文本溢出时怎么处理。clip:直接切断;ellipsis:...;fade:渐变(上下渐变,不是左右)

    style: 通过TextStyle设置字体大小、颜色、weight等

    _textDemo() {
    returnContainer(
    width:100,
    child:Text(
    'balabalabalabalabalabalabalabalabalabalabalabalabalabalabala',
    textAlign:TextAlign.end,
    style:TextStyle(
    fontSize:20,
    color:Colors.white,
    fontWeight:FontWeight.w500
    ),
    maxLines:1,
    overflow:TextOverflow.fade,
    )
    );
    }
  • image

    加载图片有四种方式分别为:

    Image.asset: 加载项目资源目录中的图片,相对路径

    Image.network: 加载网络图片

    Image.file: 加载本地图片,绝对路径

    Image.memory: 加载Uint8List资源图片(没使用过😢)

    通过fit属性设置图片的填充方式:

    BoxFit.fill: 图片拉伸以充满父容器

    BoxFit.contain: 比例不变拉伸,可能会有空隙(水平和竖直方向最多有一个方向有空隙)

    BoxFit.cover: 可能被裁减,比例不变拉伸,充满父容器

    BoxFit.fitWidth/fitHeight: 宽度/高度充满

    BoxFit.scaleDown: 效果和contain类似,但图片不会被拉伸超过源图片大小

    _imageDemo() {
    returnContainer(
    alignment:Alignment.center,
    width:300,
    height:400,
    color:Colors.blue,
    child:Image.network(
    'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2527385882,2413489660&fm=26&gp=0.jpg',
    fit:BoxFit.contain,
    )
    );
    }
  • TextField

    TextFieldCupertinoTextField 是两种不同风格的 textField ,两者样式有很大差异,用法略有不同。先来看下 TextField 的基本使用:

    _textFieldDemo0() {
    returnTextField(
    maxLength:30,
    maxLines:1,
    // 是否自动聚焦
    autofocus:false,
    // 是否为密码输入框
    obscureText:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    // 装饰效果
    decoration:InputDecoration(
    // decoration是否使用fillColor填充,默认为false
    filled:false,
    fillColor:Colors.red,
    hintText:'placeholder',
    helperText:'helperText',
    labelText:'labelText'
    ),
    enabled:true,
    // 在onSubmitted之前调用。实现该方法后,点键盘return输入框不会失去焦点
    onEditingComplete: () {
    print('TextField:complete');
    }
    );
    }

    下面 CupertinoTextField (iOS风格的TextField)的基本使用:

    _textFieldDemo1() {
    returnCupertinoTextField(
    maxLength:200,
    maxLines:1,
    autofocus:false,
    // 密码
    obscureText:false,
    autocorrect:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    placeholder:'placeholder',
    clearButtonMode:OverlayVisibilityMode.editing,
    enabled:true,
    // 设置光标宽度,默认为2
    cursorWidth:2,
    // 设置光标颜色,默认为blue
    cursorColor:Colors.red,
    // 在onSubmitted之前调用。实现了该方法后,按return不会让textField失去焦点
    onEditingComplete: () {
    print('CupertinoTextField:complete');
    }
    );
    }
  • RaisedButton & FlatButton

    FlatButton用法和RaisedButton基本相同,但样式上FlatButton属于扁平化,RaisedButton属于拟物化,有立体感(凸起),可以设置Button底部阴影效果。

    // RaisedButton 凸起效果_raisedButtonDemo() {
    returnRaisedButton(
    onPressed: () =>_showMyDialog(),
    // highlight状态发生变化就调用(高亮和取消高亮都调用)
    onHighlightChanged: (value) {
    print('highlight changed: ${value}');
    },
    // 文本文字颜色
    textColor:Colors.white,
    // 背景色
    color:Colors.blue,
    child:Text('戳'),
    padding:EdgeInsets.all(10),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    color:Colors.black,
    width:1
    )
    ),
    elevation:10,
    );
    }
  • OutlineButton

    线框按钮,它的color属性不会生效(即不能通过color设置OutlineButton的背景色),带有默认边框。其余用法和效果同FlatButton,都是扁平化的效果。

    // OutlineButton 扁平化,默认带边框_outlineButtonDemo() {
    returnOutlineButton(
    onPressed: () =>_showMyDialog(),
    textColor:Colors.blue,
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // borderSide可以设置边框的颜色、宽度和边框样式(none和solid两种)
    borderSide:BorderSide(color:Colors.blue),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    // 这里的BorderSide只可以设置边框样式,并且优先级低
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }
  • RawMaterialButton

    其效果类似FlatButton,用法与上面的三种button稍有差异。上面介绍的几种button,它们的很多属性(如shape)可以设置为null,但RawMaterialButton的属性不允许有null,否则抛出异常。

    // MaterialButton 扁平化,效果类似FlatButton_rawMaterialButtonDemo(bool withBorder) {
    returnRawMaterialButton(
    onPressed: () =>_showMyDialog(),
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // 填充色/背景色
    fillColor:Colors.blue,
    textStyle:TextStyle(color:Colors.white),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }

    ⚠️ 以上这几种button都可以设置其padding值,但它们都有一个默认的padding。当这些button的上一层级Widget的尺寸足够容纳button,这时设置的padding值比默认值大才会生效。当button上一层级的Widget不足以展示button的默认padding时,设置较小的padding可以生效。我们可以通过在button外面包裹一层Container,并设置其alignment为center,再根据需求设置Container的宽高,就可以布局出自己想要的button样式。

  • 通过GestureDetector实现自定义样式的button

    由于上面介绍的几种button,都有一些无法改变的固定样式,这往往不能满足开发中的特定需求。除了上面说的利用Container包裹button以达到想要的样式之外,还可以通过GestureDetector来实现自定义样式的button(其实就是在自己布局的Widget基础上添加了点击手势功能)。

    _myButton() {
    returnGestureDetector(
    onTap: () =>_showMyDialog(),
    child:Container(
    width:70,
    height:28,
    margin:EdgeInsets.only(top:20),
    decoration:BoxDecoration(
    borderRadius:BorderRadius.circular(11.0),
    border:Border.all(width:0.5, color:Color(0xFF6BCBD7))
    ),
    child:Center(
    child:Text('点击刷新',
    style:TextStyle(
    fontSize:13,
    color:Color(0xFF6BCBD7)
    ),
    ),
    ),
    ),
    );
    }
  • CupertinoButton

    CupertinoButton是iOS风格的button

    _iosButtonDemo() {
    returnContainer(
    margin:EdgeInsets.only(top:15),
    child:CupertinoButton(
    child:Text(
    'abc',
    style:TextStyle(fontSize:17, color:Colors.white),
    ),
    color:Colors.blue,
    minSize:20,
    padding:EdgeInsets.fromLTRB(15, 10, 15, 10),
    borderRadius:BorderRadius.circular(10),
    onPressed: () =>_showMyDialog()
    )
    );
    }
    // 以上所有例子中的按钮点击事件_showMyDialog() {
    showDialog(
    context: context,
    builder: (context) {
    returnAlertDialog(
    title:Text('我是title'),
    content:Text('我是content'),
    actions:<Widget>[
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('确定')
    ),
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('取消')
    )
    ],
    );
    }
    );
    }

About

Dart简单语法介绍和Flutter中常用Widget介绍

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

一、Dart基础

1、一些重要的概念

  • Dart 所有的东西都是对象,即使是数字、函数、null也都是对象。所有的对象都继承自 Object 类。
  • Dart 动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下该类型是 dynamic(动态的)。
  • Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
  • Dart 没有public、private和protected关键字。如果标识符以下划线(_)开头,则该标识符对其库是私有的。
  • 没有初始化的变量都会被赋予默认值 null。
  • final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。

2、Dart 中的关键字

关键字

3、常量与变量

name的类型被推断为String:

var name = 'Bob';

如果对象不限于单一类型,或没有明确的类型,可用dynamic/Object:

Object name1 ='zf';
print('name1: ${name1.runtimeType}'); // String
name1 = ['a', 'b'];
print('name1: ${name1.runtimeType}'); // List<String>dynamic name2 ='zf';
print('name2: ${name2.runtimeType}'); // String
name2 =123;
print('name2: ${name2.runtimeType}'); // int

显式声明变量的类型:

String name = 'Bob';

所有未初始化的变量默认值都是null

int lineCount;
// 在生产环境,assert()会被忽略。// 在开发环境,assert(condition)的condition不为真时抛出一个异常。assert(lineCount ==null);

如果某个变量只打算设置一次值,请使用final或const修饰。final变量只能被初始化一次,const变量是一个编译时常量(const变量是隐式的final)。final修饰的变量在第一次声明的时候就要初始化。被final或者const修饰的变量,类型可以省略,但建议指定类型:

// 可以省略String这个类型声明final name0 ='Bob';
finalString name1 ='Bob';
const name2 ='Bob';
constString name3 ='Bob';

被final或const修饰的变量值不能再修改。final或const不能和var同时使用。常量如果是类级别的,建议使用static const:

static const String name = 'Tom';

const关键字不只是声明常数变量,还可以用来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有一个常量值:

// 注意:[]创建的是一个空的list集合// const []创建一个空的,不可变的列表var varList =const [];
final finalList =const [];
const constList =const [];
// 可以更改非final/const变量的值,即便它曾经具有const的值
varList = ['hahaha'];

在常量表达式中,final/const的操作对象必须为bool、num、string或null,const常量必须用const类型的值初始化:

constString name0 ='a';
finalString name1 ='a';
constString name2 ='a';
const aConstList =const ['1', '2'];
// 报错:Const variables must be initialized with a constant valueconst validConstString0 ='$name0 $name1';
// 报错:In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'const validConstString1 ='$name0 $name2 $aConstList';
// 正确写法const validConstString2 ='$name0 $name2';

4、数据类型

  • num: 是数字类型的父类,有两个子类 int 和 double
int a =1;
print(a);
double b =1.11;
print(b);
// String -> intint c =int.parse('1');
// String -> doubledouble d =double.parse('1.11');
// int -> StringString str1 = a.toString();
// 报错:The argument type 'int' can't be assigned to the parameter type 'String'print(str1 + a);
// 正确// print('${str1} ${a}');print(str1 + a.toString());
// double -> String,// toStringAsFixed()// 参数表示要保留几位小数,参数必填// 填写的整数值大于小数的小数点位数时,会补0// 返回结果四舍五入double num0 =3.141592653;
double num1 =3.14;
String str0 = num0.toStringAsFixed(0);
String str1 = num0.toStringAsFixed(3);
String str2 = num1.toStringAsFixed(4);
// str0 = 3, str1 = 3.142, str2 = 3.1400print('str0 = ${str0}, str1 = ${str1}, str2 = ${str2}');
// toStringAsExponential()// 科学计数,参数可为空,参数表示要保留几位小数,会四舍五入double num2 =12345.678;
String str3 = num2.toStringAsExponential();
String str4 = num2.toStringAsExponential(0);
String str5 = num2.toStringAsExponential(4);
// str3 = 1.2345678e+4, str4 = 1e+4, str5 = 1.2346e+4print('str3 = ${str3}, str4 = ${str4}, str5 = ${str5}');
// toStringAsPrecision()// 参数必填且>0, 表示有效数字的位数,也会四舍五入double num3 =123.654;
int num4 =1;
String str6 = num3.toStringAsPrecision(3);
String str7 = num3.toStringAsPrecision(1);
String str8 = num4.toStringAsPrecision(2);
// str6 = 124, str7 = 1e+2, str8 = 1.0print('str6 = ${str6}, str7 = ${str7}, str8 = ${str8}');
  • String: 可以使用单引号/双引号创建字符串;引号可以嵌套使用
String str1 ='aaaaa';
String str2 ='bbbbb';
int a =1;
String str3 ='$str1 a "b" ${str2.toUpperCase()} ${a.toString()}';
  • bool
    • Dart 是强bool类型检查,只有bool类型的值是true,才被认为是true
    • 只有两个对象具有bool类型:true和false,它们都是编译时常量
    • assert 是语言内置的断言函数,仅在检查模式下有效。条件不为真则引发异常
var fullName ='';
assert(fullName.isEmpty);
var a =0/0;
assert(a.isNaN);
var b;
assert(b ==null);
  • List集合: 元素类型可以是任意类型
// 使用List的构造函数。如果传递int参数,表示List固定长度,不可增删操作List list0 =newList();
List list1 = ['a', 10, true, null, ['bala', 'heihei']];
// 添加一个元素
list1.add('apple');
// 添加多个元素
list1.addAll(['orange', 'banana']);
// 获取List的长度/首个元素/最后一个元素/指定下标元素/某个元素的索引print('${list1.length}, ${list1.first}, ${list1.last}, ${list1[0]}, ${list1.indexOf('apple')}');
// 删除指定位置的元素,返回被删除的元素print(list1.removeAt(0));
// 删除指定元素,返回true/false。如果集合中有多个相同元素,只会删除第一个
list1.remove('apple');
// 删除最后一个元素,返回被删除的元素print(list1.removeLast());
// 删除指定范围的元素,含头不含尾,没有返回值
list1.removeRange(0, 1);
// 删除指定条件的元素,没有返回值
list1.removeWhere((item) => item.runtimeType ==String);
// 删除所有的元素,没有返回值
list1.clear();
  • Map集合: map的键和值可以是任何类型的对象
// 这种方式初始化map时,key不能相同,否则报错:Two keys in a map literal can't be equal.Map map0 = {'name':'aaa', 'age':18, 111:true, null:null, ['a']: {'xxx':'yyy'}};
// 另一种创建方式Map map1 =newMap();
map1['name'] ='aaa';
map1['age'] =18;
// 指定键值对的参数类型。如果指定了key/value类型,赋值时必须为指定的类型Map map2 =newMap<int, String>();
// 对同一key进行赋值会被覆盖
map2[1] ='abc';
map2[1] ='aaa';
// map里面的value和key都可以是null
map2[2] =null;
// 检索Map是否含有某keyassert(map2.containsKey(1));
// 删除某个键值对(参数为key值)
map2.remove(1);

5、异常(Exception)

  • throw

抛出固定类型的异常

throw new FormatException('Expected at least 1 section');

抛出任意类型的异常

throw 'Out of memory!';

  • on / catch

将可能出现异常的代码放到 try 语句中,可以通过 on 语句来指定需要捕获的异常类型;使用 catch 可以捕获到任意异常类型。rethrow可以重新抛出捕获的异常

test1() {
try {
test2();
} catch (e) {
print('test1 -- e -- ${e}');
}
}
test2() {
try {
throwFormatException('this is exception');
} onOutOfMemoryError { // 可以捕获指定类型的异常print('out of memory');
} onFormatExceptioncatch (e) { // 匹配不到FormatException,会继续匹配print('exception error: ${e}');
// 重新抛出异常,可以在test1的catch中重新捕获异常rethrow; } catch (e, r) { // 匹配所有类型的异常:e是异常对象,r时异常的堆栈信息print('exception details: ${e}');
print('stack trace: ${r}');
} finally { // 一定会执行,如果try/catch中有return/rethrow,会在return/rethrow之前执行print('this is finally');
}
}

6、函数(Function)

Dart 的函数有两种类型的参数:必需的和可选的。可选参数可以有一个默认值,在调用者没有指定可选参数时使用默认值。可选参数又分为:可选命名参数和可选位置参数。

  • 可选命名参数: 使用{}包围的参数属于可选命名参数,可设置默认值,在调用的时候,需要使用paramName:value的形式指定为哪个可选参数赋值。
void_testPar1(int a, {String name, int age =10}) {
print('a = ${a}, name = ${name}, age = ${age}');
}
_testPar1(123, name:'abc'); // a = 123, name = abc, age = 10_testPar1(123, age:18); // a = 123, name = null, age = 18_testPar1(123, name:'aaa', age:18); // a = 123, name = aaa, age = 18
  • 可选位置参数: 使用[]包围的参数属于可选位置参数。如果想指定某个位置上的参数,必须保证前面位置有参数(设置了默认参数也需要传)。
void _testPar2 (int a, [String str ='abc', int b]) {
print('a = ${a}, str = ${str}, b = ${b}');
}
_testPar2(123); // a = 123, str = abc, b = null_testPar2(123, 'beijing'); // a = 123, str = beijing, b = null_testPar2(123, 'beijing', 10); // a = 123, str = beijing, b = 10_testPar2(123, 10); // 会报错

7、类的构造函数

classPoint {
int x;
int y;
// 构造函数Point(int a, int b) {
x = a;
y = b;
}
// 上面的构造函数可以简化为:Point(this.x, this.y);
// 命名构造函数Point.fromJson(Map data) {
x = data['x'];
y = data['y'];
}
// 可以在构造函数打括号前来初始化实例的变量值,多个实例变量用逗号隔开Point.fromJson(Map data) : x = data['x'], y = data['y'];
}

8、Dart 中的异步

  • Event-Looper

    Dart 是单线程模型,也就没有所谓的主线程/子线程。 Dart 也是Event-Looper以及Event-Queue的模型,所有的事件都是通过EventLooper依次执行。 Dart 的Event Loop是:

    • 从EventQueue中获取Event
    • 处理Event
    • 直到EventQueue为空

    Event Queue

    这些Event包括了用户输入,点击,Timer,文件IO等

    Event Queue

  • 单线程模型

    Dart 中没有线程的概念,只有isolate,每个isolate都是隔离的,并不会共享内存。一个 Dart 程序是在Main isolate的main函数开始,在main函数结束后,Main isolate开始一个一个的处理Event Queue中的每一个Event。

    Event Queue

  • Event Queue以及Microtask Queue

    Dart 中的Main isolate只有一个Event Looper,但存在两个Queue:Event Queue和Microtask Queue。Microtask Queue存在的意义是:

    希望通过这个Queue来处理稍晚一些,但是在下一个消息到来之前需要处理完的事情。

    当Event Looper正在处理Microtask Queue中的Event时,Event Queue中的Event就停止了处理,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等。

    Event-Looper挑选Task的执行顺序为:

    • 优先全部执行完Microtask Queue中的Event
    • 直到Microtask Queue为空时,再执行Event Queue中的Event

    Event Queue

    Dart 中只知道Event处理的先后顺序,并不知道某个Event执行的具体时间点。

  • 异步任务调度

    当有代码可以在后续任务执行的时候,有两种方式,通过 dart:async 中的API即可

    • 使用Future类,可以将任务加入到Event Queue的队尾
    // 一般常用的Future构造函数newFuture(() {
    // do something
    });
    // 当需要将任务拆分成很多小任务一步一步执行时,可以使用Future.then来拆解任务newFuture(() {
    print('abc');
    return21;
    }).then((value) =>'!!!then01 $value!!!') //这里的value是上面任务的返回值
    .then((value) {
    // 这里的value为上一个任务执行完的返回结果print('===then02 $value==='); // ===then02 !!!then01 21!!!===return'balabala';
    }).then((value){
    // 这里的value是上个任务的返回值print('~~~then03 $value~~~~'); // ~~~then03 balabala~~~~
    }).whenComplete(() {
    print('complete');
    });
    // 当任务需要延时执行,可以使用Future.delay。不是绝对精准的。newFuture.delayed(constDuration(seconds:1), () {print('123');});
    • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾
     import 'dart:async';
    voidmain() {
    newFuture.delayed(constDuration(seconds:1), () {print('delay');});
    print('main---111');
    scheduleMicrotask(() {
    print('microtask---111');
    });
    newFuture(() {
    print('future');
    return21;
    }).then((value) {
    print('future-then-$value');
    }).whenComplete(() {
    print('complete');
    });
    scheduleMicrotask(() {
    print('microtask---222');
    });
    print('main---222');
    }

    上面代码的输出顺序为:

    main---111 main---222 microtask---111 microtask---222 future future-then-21 complete delay

    • 再看一个较复杂的例子
    scheduleMicrotask(() =>print('000'));
    newFuture(() =>print('111'))
    .then((_) {
    print('222');
    scheduleMicrotask(() =>print('333'));
    }).then((_) =>print('444'));
    scheduleMicrotask(() =>print('555'));
    newFuture(() =>print('666'))
    .then((_) {
    print('777');
    Future(() =>print('888'));
    }).then((_) =>print('999'));
    newFuture(() =>print('aaa'));
    scheduleMicrotask(() =>print('bbb'));

    上面输出顺序为:0-5-b-1-2-4-3-6-7-9-a-8

  • async和await

    // async关键字声明该函数内部有延时执行的任务asyncAndAwait() async {
    // 在Dart中,有await修饰的,其结果是一个Future对象var a =awaittest0();
    print('a-type:${a.runtimeType}');
    print('a = ${a}');
    var b =awaittest1();
    print('b-type:${b.runtimeType}');
    print('b = ${b}');
    }
    test0() {
    returnFuture(() =>'22222');
    }
    test1() {
    returnFuture(() =>'22222')
    .then((value) {
    print('then-value=${value}');
    }).whenComplete(() {
    print('complete~');
    });
    }

    上面代码的输出顺序依次为:

    a-type:String a = 11111 then-value=22222 complete~ b-type:Null b = null

二、Widget基础篇

1、Widget State

Flutter 中,一切能显示的都是 WidgetWidget 是一切的基础,作为响应式的渲染,属于 MVVM 的实现机制。我们可以通过修改数据,再用 setState 设置数据, Flutter 会自动通过绑定的数据更新 Widget 。我们需要做的就是实现 Widget 页面,并且和数据绑定起来。

Widget 分为 有状态无状态 两种。在 Flutter 中每个页面都是一帧。无状态就是保持在那一帧;有状态的 Widget 当数据更新时,其实就是绘制了新的 Widget

  • 无状态 StatelessWidget

    无状态的 Widget 实现很简单:创建一个继承自 StatelessWidgetWidget ,并通过 build 方法返回一个布局好的控件。WidgetWidget 之间通过 childchildren 进行嵌套。

import'package:flutter/material.dart';
classMyStatelessextendsStatelessWidget {
finalString text;
MyStateless(this.text);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:Text('StatelessWidget'),
),
body:Container(
color:Colors.white,
//Dart语法中,?? 表示如果text为空,就返回 ?? 后的内容。
child:Text(text ??"这就是无状态DMEO"),
),
);
}
}
  • 有状态 StatefulWidget

有状态的 Widget 需要管理 state ,通过 setState 改变数据触发 Widget 重新构建刷新。主要的生命周期有:

initState: 初始化 didChangeDependencies:initState 之后 build 之前调用 dispose: 销毁

classMyStatefulextendsStatefulWidget {
@overrideState<StatefulWidget> createState() {
return_MyStatefulState();
}
}
class_MyStatefulStateextendsState<MyStateful> {
int pressCount;
@overridevoidinitState() {
super.initState();
pressCount =0;
}
@overrideWidgetbuild(BuildContext context) {
returnContainer(
child:FlatButton(
onPressed: () {
setState(() {
pressCount +=1;
});
},
child:Center(
child:Text('有状态的Widget,可戳👇--$pressCount'),
)
),
);
}
}

不管是有状态的还是无状态的 Widget ,我们需要做的就是在 build 中堆积我们的布局,把数据添加到 Widget 中,有状态的可以通过 setState 改变数据,从而刷新UI,就这么简单。

2、Flutter中的Widget整理

先来看一张图,这张图中几乎包含了 Flutter 中所有的 Widget

我们先针对其中比较常用的讲解下其特性和使用方法。

3、布局相关的常用Widget

  • Container

    最常用的布局。只能包含一个 child ,默认铺满。支持配置 paddingmargincolor 、宽高、decoration (一般配置边框和阴影等)。不是所有的控件都有 paddingmargincolor 、宽高等属性,所以才会有 PaddingCenterWidget 的存在。

    alignment: 设置Container内child的对其方式(注意不是Container本身的对其方式)

    width/height/color: 设置Container的宽高和背景色

    padding/margin: padding设置Container边缘和其child内容的距离,margin设置Container和其它外部Widget的距离。

    decoration: Container的修饰器,主要设置背景、边框、圆角等。注意:如果设置了该属性,就不能设置Container的color,否则会抛出异常

    _containerDemo() {
    returnContainer(
    alignment:Alignment.center,
    child:Text('balabalabala'),
    height:49,
    margin:EdgeInsets.all(20),
    padding:EdgeInsets.only(top:10),
    // color: Colors.blueGrey,// 使用它就不能设置Container的color
    decoration:BoxDecoration(
    borderRadius:BorderRadius.only(
    topLeft:Radius.circular(10),
    bottomRight:Radius.circular(10)
    ),
    color:Colors.blue,
    border:Border.all(
    color:Color(0xFF000000),
    width:1
    )
    ),
    );
    }
  • Column、Row和Expanded

    竖直布局和水平布局也是很常见的场景。它们都可以通过 children 设置多个子 Widget ,可以设置主轴和副轴方向布局方式等。而 ExpendedColumnRow 中默认代表着平均充满。当然可以通过设置 flex 属性决定占屏比例。

    _columnDemo() {
    returnColumn(
    children:<Widget>[
    Expanded(child:Container(
    child:Text('aaa'),
    color:Colors.grey,
    )),
    Expanded(child:Container(
    child:Text('bbb'),
    color:Colors.blueGrey,
    )),
    Expanded(
    child:Container(
    child:Text('ccc'),
    color:Colors.red
    ),
    flex:2
    )
    ],
    // 主轴方向布局方式
    mainAxisAlignment:MainAxisAlignment.center,
    // 大小按照最大充满(这里使用Expanded会最大充满不受该属性设置影响)
    mainAxisSize :MainAxisSize.min,
    // 副轴方向布局方式
    crossAxisAlignment:CrossAxisAlignment.center
    );
    }

4、其它常用Widget

Flutter 中除了布局的 Widget ,还有交互显示和完整页面呈现的 Widget 。其中常见的有 MaterialAppScaffoldAppbarTextImageFlatButton 等。

常用Widget及特点

  • Text

    textAlign: 设置文本对其方式:center、left、right、start、end

    maxLines: 设置最多显示的行数

    overflow: 文本溢出时怎么处理。clip:直接切断;ellipsis:...;fade:渐变(上下渐变,不是左右)

    style: 通过TextStyle设置字体大小、颜色、weight等

    _textDemo() {
    returnContainer(
    width:100,
    child:Text(
    'balabalabalabalabalabalabalabalabalabalabalabalabalabalabala',
    textAlign:TextAlign.end,
    style:TextStyle(
    fontSize:20,
    color:Colors.white,
    fontWeight:FontWeight.w500
    ),
    maxLines:1,
    overflow:TextOverflow.fade,
    )
    );
    }
  • image

    加载图片有四种方式分别为:

    Image.asset: 加载项目资源目录中的图片,相对路径

    Image.network: 加载网络图片

    Image.file: 加载本地图片,绝对路径

    Image.memory: 加载Uint8List资源图片(没使用过😢)

    通过fit属性设置图片的填充方式:

    BoxFit.fill: 图片拉伸以充满父容器

    BoxFit.contain: 比例不变拉伸,可能会有空隙(水平和竖直方向最多有一个方向有空隙)

    BoxFit.cover: 可能被裁减,比例不变拉伸,充满父容器

    BoxFit.fitWidth/fitHeight: 宽度/高度充满

    BoxFit.scaleDown: 效果和contain类似,但图片不会被拉伸超过源图片大小

    _imageDemo() {
    returnContainer(
    alignment:Alignment.center,
    width:300,
    height:400,
    color:Colors.blue,
    child:Image.network(
    'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2527385882,2413489660&fm=26&gp=0.jpg',
    fit:BoxFit.contain,
    )
    );
    }
  • TextField

    TextFieldCupertinoTextField 是两种不同风格的 textField ,两者样式有很大差异,用法略有不同。先来看下 TextField 的基本使用:

    _textFieldDemo0() {
    returnTextField(
    maxLength:30,
    maxLines:1,
    // 是否自动聚焦
    autofocus:false,
    // 是否为密码输入框
    obscureText:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    // 装饰效果
    decoration:InputDecoration(
    // decoration是否使用fillColor填充,默认为false
    filled:false,
    fillColor:Colors.red,
    hintText:'placeholder',
    helperText:'helperText',
    labelText:'labelText'
    ),
    enabled:true,
    // 在onSubmitted之前调用。实现该方法后,点键盘return输入框不会失去焦点
    onEditingComplete: () {
    print('TextField:complete');
    }
    );
    }

    下面 CupertinoTextField (iOS风格的TextField)的基本使用:

    _textFieldDemo1() {
    returnCupertinoTextField(
    maxLength:200,
    maxLines:1,
    autofocus:false,
    // 密码
    obscureText:false,
    autocorrect:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    placeholder:'placeholder',
    clearButtonMode:OverlayVisibilityMode.editing,
    enabled:true,
    // 设置光标宽度,默认为2
    cursorWidth:2,
    // 设置光标颜色,默认为blue
    cursorColor:Colors.red,
    // 在onSubmitted之前调用。实现了该方法后,按return不会让textField失去焦点
    onEditingComplete: () {
    print('CupertinoTextField:complete');
    }
    );
    }
  • RaisedButton & FlatButton

    FlatButton用法和RaisedButton基本相同,但样式上FlatButton属于扁平化,RaisedButton属于拟物化,有立体感(凸起),可以设置Button底部阴影效果。

    // RaisedButton 凸起效果_raisedButtonDemo() {
    returnRaisedButton(
    onPressed: () =>_showMyDialog(),
    // highlight状态发生变化就调用(高亮和取消高亮都调用)
    onHighlightChanged: (value) {
    print('highlight changed: ${value}');
    },
    // 文本文字颜色
    textColor:Colors.white,
    // 背景色
    color:Colors.blue,
    child:Text('戳'),
    padding:EdgeInsets.all(10),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    color:Colors.black,
    width:1
    )
    ),
    elevation:10,
    );
    }
  • OutlineButton

    线框按钮,它的color属性不会生效(即不能通过color设置OutlineButton的背景色),带有默认边框。其余用法和效果同FlatButton,都是扁平化的效果。

    // OutlineButton 扁平化,默认带边框_outlineButtonDemo() {
    returnOutlineButton(
    onPressed: () =>_showMyDialog(),
    textColor:Colors.blue,
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // borderSide可以设置边框的颜色、宽度和边框样式(none和solid两种)
    borderSide:BorderSide(color:Colors.blue),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    // 这里的BorderSide只可以设置边框样式,并且优先级低
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }
  • RawMaterialButton

    其效果类似FlatButton,用法与上面的三种button稍有差异。上面介绍的几种button,它们的很多属性(如shape)可以设置为null,但RawMaterialButton的属性不允许有null,否则抛出异常。

    // MaterialButton 扁平化,效果类似FlatButton_rawMaterialButtonDemo(bool withBorder) {
    returnRawMaterialButton(
    onPressed: () =>_showMyDialog(),
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // 填充色/背景色
    fillColor:Colors.blue,
    textStyle:TextStyle(color:Colors.white),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }

    ⚠️ 以上这几种button都可以设置其padding值,但它们都有一个默认的padding。当这些button的上一层级Widget的尺寸足够容纳button,这时设置的padding值比默认值大才会生效。当button上一层级的Widget不足以展示button的默认padding时,设置较小的padding可以生效。我们可以通过在button外面包裹一层Container,并设置其alignment为center,再根据需求设置Container的宽高,就可以布局出自己想要的button样式。

  • 通过GestureDetector实现自定义样式的button

    由于上面介绍的几种button,都有一些无法改变的固定样式,这往往不能满足开发中的特定需求。除了上面说的利用Container包裹button以达到想要的样式之外,还可以通过GestureDetector来实现自定义样式的button(其实就是在自己布局的Widget基础上添加了点击手势功能)。

    _myButton() {
    returnGestureDetector(
    onTap: () =>_showMyDialog(),
    child:Container(
    width:70,
    height:28,
    margin:EdgeInsets.only(top:20),
    decoration:BoxDecoration(
    borderRadius:BorderRadius.circular(11.0),
    border:Border.all(width:0.5, color:Color(0xFF6BCBD7))
    ),
    child:Center(
    child:Text('点击刷新',
    style:TextStyle(
    fontSize:13,
    color:Color(0xFF6BCBD7)
    ),
    ),
    ),
    ),
    );
    }
  • CupertinoButton

    CupertinoButton是iOS风格的button

    _iosButtonDemo() {
    returnContainer(
    margin:EdgeInsets.only(top:15),
    child:CupertinoButton(
    child:Text(
    'abc',
    style:TextStyle(fontSize:17, color:Colors.white),
    ),
    color:Colors.blue,
    minSize:20,
    padding:EdgeInsets.fromLTRB(15, 10, 15, 10),
    borderRadius:BorderRadius.circular(10),
    onPressed: () =>_showMyDialog()
    )
    );
    }
    // 以上所有例子中的按钮点击事件_showMyDialog() {
    showDialog(
    context: context,
    builder: (context) {
    returnAlertDialog(
    title:Text('我是title'),
    content:Text('我是content'),
    actions:<Widget>[
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('确定')
    ),
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('取消')
    )
    ],
    );
    }
    );
    }

About

Dart简单语法介绍和Flutter中常用Widget介绍

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

一、Dart基础

1、一些重要的概念

  • Dart 所有的东西都是对象,即使是数字、函数、null也都是对象。所有的对象都继承自 Object 类。
  • Dart 动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下该类型是 dynamic(动态的)。
  • Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
  • Dart 没有public、private和protected关键字。如果标识符以下划线(_)开头,则该标识符对其库是私有的。
  • 没有初始化的变量都会被赋予默认值 null。
  • final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。

2、Dart 中的关键字

关键字

3、常量与变量

name的类型被推断为String:

var name = 'Bob';

如果对象不限于单一类型,或没有明确的类型,可用dynamic/Object:

Object name1 ='zf';
print('name1: ${name1.runtimeType}'); // String
name1 = ['a', 'b'];
print('name1: ${name1.runtimeType}'); // List<String>dynamic name2 ='zf';
print('name2: ${name2.runtimeType}'); // String
name2 =123;
print('name2: ${name2.runtimeType}'); // int

显式声明变量的类型:

String name = 'Bob';

所有未初始化的变量默认值都是null

int lineCount;
// 在生产环境,assert()会被忽略。// 在开发环境,assert(condition)的condition不为真时抛出一个异常。assert(lineCount ==null);

如果某个变量只打算设置一次值,请使用final或const修饰。final变量只能被初始化一次,const变量是一个编译时常量(const变量是隐式的final)。final修饰的变量在第一次声明的时候就要初始化。被final或者const修饰的变量,类型可以省略,但建议指定类型:

// 可以省略String这个类型声明final name0 ='Bob';
finalString name1 ='Bob';
const name2 ='Bob';
constString name3 ='Bob';

被final或const修饰的变量值不能再修改。final或const不能和var同时使用。常量如果是类级别的,建议使用static const:

static const String name = 'Tom';

const关键字不只是声明常数变量,还可以用来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有一个常量值:

// 注意:[]创建的是一个空的list集合// const []创建一个空的,不可变的列表var varList =const [];
final finalList =const [];
const constList =const [];
// 可以更改非final/const变量的值,即便它曾经具有const的值
varList = ['hahaha'];

在常量表达式中,final/const的操作对象必须为bool、num、string或null,const常量必须用const类型的值初始化:

constString name0 ='a';
finalString name1 ='a';
constString name2 ='a';
const aConstList =const ['1', '2'];
// 报错:Const variables must be initialized with a constant valueconst validConstString0 ='$name0 $name1';
// 报错:In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'const validConstString1 ='$name0 $name2 $aConstList';
// 正确写法const validConstString2 ='$name0 $name2';

4、数据类型

  • num: 是数字类型的父类,有两个子类 int 和 double
int a =1;
print(a);
double b =1.11;
print(b);
// String -> intint c =int.parse('1');
// String -> doubledouble d =double.parse('1.11');
// int -> StringString str1 = a.toString();
// 报错:The argument type 'int' can't be assigned to the parameter type 'String'print(str1 + a);
// 正确// print('${str1} ${a}');print(str1 + a.toString());
// double -> String,// toStringAsFixed()// 参数表示要保留几位小数,参数必填// 填写的整数值大于小数的小数点位数时,会补0// 返回结果四舍五入double num0 =3.141592653;
double num1 =3.14;
String str0 = num0.toStringAsFixed(0);
String str1 = num0.toStringAsFixed(3);
String str2 = num1.toStringAsFixed(4);
// str0 = 3, str1 = 3.142, str2 = 3.1400print('str0 = ${str0}, str1 = ${str1}, str2 = ${str2}');
// toStringAsExponential()// 科学计数,参数可为空,参数表示要保留几位小数,会四舍五入double num2 =12345.678;
String str3 = num2.toStringAsExponential();
String str4 = num2.toStringAsExponential(0);
String str5 = num2.toStringAsExponential(4);
// str3 = 1.2345678e+4, str4 = 1e+4, str5 = 1.2346e+4print('str3 = ${str3}, str4 = ${str4}, str5 = ${str5}');
// toStringAsPrecision()// 参数必填且>0, 表示有效数字的位数,也会四舍五入double num3 =123.654;
int num4 =1;
String str6 = num3.toStringAsPrecision(3);
String str7 = num3.toStringAsPrecision(1);
String str8 = num4.toStringAsPrecision(2);
// str6 = 124, str7 = 1e+2, str8 = 1.0print('str6 = ${str6}, str7 = ${str7}, str8 = ${str8}');
  • String: 可以使用单引号/双引号创建字符串;引号可以嵌套使用
String str1 ='aaaaa';
String str2 ='bbbbb';
int a =1;
String str3 ='$str1 a "b" ${str2.toUpperCase()} ${a.toString()}';
  • bool
    • Dart 是强bool类型检查,只有bool类型的值是true,才被认为是true
    • 只有两个对象具有bool类型:true和false,它们都是编译时常量
    • assert 是语言内置的断言函数,仅在检查模式下有效。条件不为真则引发异常
var fullName ='';
assert(fullName.isEmpty);
var a =0/0;
assert(a.isNaN);
var b;
assert(b ==null);
  • List集合: 元素类型可以是任意类型
// 使用List的构造函数。如果传递int参数,表示List固定长度,不可增删操作List list0 =newList();
List list1 = ['a', 10, true, null, ['bala', 'heihei']];
// 添加一个元素
list1.add('apple');
// 添加多个元素
list1.addAll(['orange', 'banana']);
// 获取List的长度/首个元素/最后一个元素/指定下标元素/某个元素的索引print('${list1.length}, ${list1.first}, ${list1.last}, ${list1[0]}, ${list1.indexOf('apple')}');
// 删除指定位置的元素,返回被删除的元素print(list1.removeAt(0));
// 删除指定元素,返回true/false。如果集合中有多个相同元素,只会删除第一个
list1.remove('apple');
// 删除最后一个元素,返回被删除的元素print(list1.removeLast());
// 删除指定范围的元素,含头不含尾,没有返回值
list1.removeRange(0, 1);
// 删除指定条件的元素,没有返回值
list1.removeWhere((item) => item.runtimeType ==String);
// 删除所有的元素,没有返回值
list1.clear();
  • Map集合: map的键和值可以是任何类型的对象
// 这种方式初始化map时,key不能相同,否则报错:Two keys in a map literal can't be equal.Map map0 = {'name':'aaa', 'age':18, 111:true, null:null, ['a']: {'xxx':'yyy'}};
// 另一种创建方式Map map1 =newMap();
map1['name'] ='aaa';
map1['age'] =18;
// 指定键值对的参数类型。如果指定了key/value类型,赋值时必须为指定的类型Map map2 =newMap<int, String>();
// 对同一key进行赋值会被覆盖
map2[1] ='abc';
map2[1] ='aaa';
// map里面的value和key都可以是null
map2[2] =null;
// 检索Map是否含有某keyassert(map2.containsKey(1));
// 删除某个键值对(参数为key值)
map2.remove(1);

5、异常(Exception)

  • throw

抛出固定类型的异常

throw new FormatException('Expected at least 1 section');

抛出任意类型的异常

throw 'Out of memory!';

  • on / catch

将可能出现异常的代码放到 try 语句中,可以通过 on 语句来指定需要捕获的异常类型;使用 catch 可以捕获到任意异常类型。rethrow可以重新抛出捕获的异常

test1() {
try {
test2();
} catch (e) {
print('test1 -- e -- ${e}');
}
}
test2() {
try {
throwFormatException('this is exception');
} onOutOfMemoryError { // 可以捕获指定类型的异常print('out of memory');
} onFormatExceptioncatch (e) { // 匹配不到FormatException,会继续匹配print('exception error: ${e}');
// 重新抛出异常,可以在test1的catch中重新捕获异常rethrow; } catch (e, r) { // 匹配所有类型的异常:e是异常对象,r时异常的堆栈信息print('exception details: ${e}');
print('stack trace: ${r}');
} finally { // 一定会执行,如果try/catch中有return/rethrow,会在return/rethrow之前执行print('this is finally');
}
}

6、函数(Function)

Dart 的函数有两种类型的参数:必需的和可选的。可选参数可以有一个默认值,在调用者没有指定可选参数时使用默认值。可选参数又分为:可选命名参数和可选位置参数。

  • 可选命名参数: 使用{}包围的参数属于可选命名参数,可设置默认值,在调用的时候,需要使用paramName:value的形式指定为哪个可选参数赋值。
void_testPar1(int a, {String name, int age =10}) {
print('a = ${a}, name = ${name}, age = ${age}');
}
_testPar1(123, name:'abc'); // a = 123, name = abc, age = 10_testPar1(123, age:18); // a = 123, name = null, age = 18_testPar1(123, name:'aaa', age:18); // a = 123, name = aaa, age = 18
  • 可选位置参数: 使用[]包围的参数属于可选位置参数。如果想指定某个位置上的参数,必须保证前面位置有参数(设置了默认参数也需要传)。
void _testPar2 (int a, [String str ='abc', int b]) {
print('a = ${a}, str = ${str}, b = ${b}');
}
_testPar2(123); // a = 123, str = abc, b = null_testPar2(123, 'beijing'); // a = 123, str = beijing, b = null_testPar2(123, 'beijing', 10); // a = 123, str = beijing, b = 10_testPar2(123, 10); // 会报错

7、类的构造函数

classPoint {
int x;
int y;
// 构造函数Point(int a, int b) {
x = a;
y = b;
}
// 上面的构造函数可以简化为:Point(this.x, this.y);
// 命名构造函数Point.fromJson(Map data) {
x = data['x'];
y = data['y'];
}
// 可以在构造函数打括号前来初始化实例的变量值,多个实例变量用逗号隔开Point.fromJson(Map data) : x = data['x'], y = data['y'];
}

8、Dart 中的异步

  • Event-Looper

    Dart 是单线程模型,也就没有所谓的主线程/子线程。 Dart 也是Event-Looper以及Event-Queue的模型,所有的事件都是通过EventLooper依次执行。 Dart 的Event Loop是:

    • 从EventQueue中获取Event
    • 处理Event
    • 直到EventQueue为空

    Event Queue

    这些Event包括了用户输入,点击,Timer,文件IO等

    Event Queue

  • 单线程模型

    Dart 中没有线程的概念,只有isolate,每个isolate都是隔离的,并不会共享内存。一个 Dart 程序是在Main isolate的main函数开始,在main函数结束后,Main isolate开始一个一个的处理Event Queue中的每一个Event。

    Event Queue

  • Event Queue以及Microtask Queue

    Dart 中的Main isolate只有一个Event Looper,但存在两个Queue:Event Queue和Microtask Queue。Microtask Queue存在的意义是:

    希望通过这个Queue来处理稍晚一些,但是在下一个消息到来之前需要处理完的事情。

    当Event Looper正在处理Microtask Queue中的Event时,Event Queue中的Event就停止了处理,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等。

    Event-Looper挑选Task的执行顺序为:

    • 优先全部执行完Microtask Queue中的Event
    • 直到Microtask Queue为空时,再执行Event Queue中的Event

    Event Queue

    Dart 中只知道Event处理的先后顺序,并不知道某个Event执行的具体时间点。

  • 异步任务调度

    当有代码可以在后续任务执行的时候,有两种方式,通过 dart:async 中的API即可

    • 使用Future类,可以将任务加入到Event Queue的队尾
    // 一般常用的Future构造函数newFuture(() {
    // do something
    });
    // 当需要将任务拆分成很多小任务一步一步执行时,可以使用Future.then来拆解任务newFuture(() {
    print('abc');
    return21;
    }).then((value) =>'!!!then01 $value!!!') //这里的value是上面任务的返回值
    .then((value) {
    // 这里的value为上一个任务执行完的返回结果print('===then02 $value==='); // ===then02 !!!then01 21!!!===return'balabala';
    }).then((value){
    // 这里的value是上个任务的返回值print('~~~then03 $value~~~~'); // ~~~then03 balabala~~~~
    }).whenComplete(() {
    print('complete');
    });
    // 当任务需要延时执行,可以使用Future.delay。不是绝对精准的。newFuture.delayed(constDuration(seconds:1), () {print('123');});
    • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾
     import 'dart:async';
    voidmain() {
    newFuture.delayed(constDuration(seconds:1), () {print('delay');});
    print('main---111');
    scheduleMicrotask(() {
    print('microtask---111');
    });
    newFuture(() {
    print('future');
    return21;
    }).then((value) {
    print('future-then-$value');
    }).whenComplete(() {
    print('complete');
    });
    scheduleMicrotask(() {
    print('microtask---222');
    });
    print('main---222');
    }

    上面代码的输出顺序为:

    main---111 main---222 microtask---111 microtask---222 future future-then-21 complete delay

    • 再看一个较复杂的例子
    scheduleMicrotask(() =>print('000'));
    newFuture(() =>print('111'))
    .then((_) {
    print('222');
    scheduleMicrotask(() =>print('333'));
    }).then((_) =>print('444'));
    scheduleMicrotask(() =>print('555'));
    newFuture(() =>print('666'))
    .then((_) {
    print('777');
    Future(() =>print('888'));
    }).then((_) =>print('999'));
    newFuture(() =>print('aaa'));
    scheduleMicrotask(() =>print('bbb'));

    上面输出顺序为:0-5-b-1-2-4-3-6-7-9-a-8

  • async和await

    // async关键字声明该函数内部有延时执行的任务asyncAndAwait() async {
    // 在Dart中,有await修饰的,其结果是一个Future对象var a =awaittest0();
    print('a-type:${a.runtimeType}');
    print('a = ${a}');
    var b =awaittest1();
    print('b-type:${b.runtimeType}');
    print('b = ${b}');
    }
    test0() {
    returnFuture(() =>'22222');
    }
    test1() {
    returnFuture(() =>'22222')
    .then((value) {
    print('then-value=${value}');
    }).whenComplete(() {
    print('complete~');
    });
    }

    上面代码的输出顺序依次为:

    a-type:String a = 11111 then-value=22222 complete~ b-type:Null b = null

二、Widget基础篇

1、Widget State

Flutter 中,一切能显示的都是 WidgetWidget 是一切的基础,作为响应式的渲染,属于 MVVM 的实现机制。我们可以通过修改数据,再用 setState 设置数据, Flutter 会自动通过绑定的数据更新 Widget 。我们需要做的就是实现 Widget 页面,并且和数据绑定起来。

Widget 分为 有状态无状态 两种。在 Flutter 中每个页面都是一帧。无状态就是保持在那一帧;有状态的 Widget 当数据更新时,其实就是绘制了新的 Widget

  • 无状态 StatelessWidget

    无状态的 Widget 实现很简单:创建一个继承自 StatelessWidgetWidget ,并通过 build 方法返回一个布局好的控件。WidgetWidget 之间通过 childchildren 进行嵌套。

import'package:flutter/material.dart';
classMyStatelessextendsStatelessWidget {
finalString text;
MyStateless(this.text);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:Text('StatelessWidget'),
),
body:Container(
color:Colors.white,
//Dart语法中,?? 表示如果text为空,就返回 ?? 后的内容。
child:Text(text ??"这就是无状态DMEO"),
),
);
}
}
  • 有状态 StatefulWidget

有状态的 Widget 需要管理 state ,通过 setState 改变数据触发 Widget 重新构建刷新。主要的生命周期有:

initState: 初始化 didChangeDependencies:initState 之后 build 之前调用 dispose: 销毁

classMyStatefulextendsStatefulWidget {
@overrideState<StatefulWidget> createState() {
return_MyStatefulState();
}
}
class_MyStatefulStateextendsState<MyStateful> {
int pressCount;
@overridevoidinitState() {
super.initState();
pressCount =0;
}
@overrideWidgetbuild(BuildContext context) {
returnContainer(
child:FlatButton(
onPressed: () {
setState(() {
pressCount +=1;
});
},
child:Center(
child:Text('有状态的Widget,可戳👇--$pressCount'),
)
),
);
}
}

不管是有状态的还是无状态的 Widget ,我们需要做的就是在 build 中堆积我们的布局,把数据添加到 Widget 中,有状态的可以通过 setState 改变数据,从而刷新UI,就这么简单。

2、Flutter中的Widget整理

先来看一张图,这张图中几乎包含了 Flutter 中所有的 Widget

我们先针对其中比较常用的讲解下其特性和使用方法。

3、布局相关的常用Widget

  • Container

    最常用的布局。只能包含一个 child ,默认铺满。支持配置 paddingmargincolor 、宽高、decoration (一般配置边框和阴影等)。不是所有的控件都有 paddingmargincolor 、宽高等属性,所以才会有 PaddingCenterWidget 的存在。

    alignment: 设置Container内child的对其方式(注意不是Container本身的对其方式)

    width/height/color: 设置Container的宽高和背景色

    padding/margin: padding设置Container边缘和其child内容的距离,margin设置Container和其它外部Widget的距离。

    decoration: Container的修饰器,主要设置背景、边框、圆角等。注意:如果设置了该属性,就不能设置Container的color,否则会抛出异常

    _containerDemo() {
    returnContainer(
    alignment:Alignment.center,
    child:Text('balabalabala'),
    height:49,
    margin:EdgeInsets.all(20),
    padding:EdgeInsets.only(top:10),
    // color: Colors.blueGrey,// 使用它就不能设置Container的color
    decoration:BoxDecoration(
    borderRadius:BorderRadius.only(
    topLeft:Radius.circular(10),
    bottomRight:Radius.circular(10)
    ),
    color:Colors.blue,
    border:Border.all(
    color:Color(0xFF000000),
    width:1
    )
    ),
    );
    }
  • Column、Row和Expanded

    竖直布局和水平布局也是很常见的场景。它们都可以通过 children 设置多个子 Widget ,可以设置主轴和副轴方向布局方式等。而 ExpendedColumnRow 中默认代表着平均充满。当然可以通过设置 flex 属性决定占屏比例。

    _columnDemo() {
    returnColumn(
    children:<Widget>[
    Expanded(child:Container(
    child:Text('aaa'),
    color:Colors.grey,
    )),
    Expanded(child:Container(
    child:Text('bbb'),
    color:Colors.blueGrey,
    )),
    Expanded(
    child:Container(
    child:Text('ccc'),
    color:Colors.red
    ),
    flex:2
    )
    ],
    // 主轴方向布局方式
    mainAxisAlignment:MainAxisAlignment.center,
    // 大小按照最大充满(这里使用Expanded会最大充满不受该属性设置影响)
    mainAxisSize :MainAxisSize.min,
    // 副轴方向布局方式
    crossAxisAlignment:CrossAxisAlignment.center
    );
    }

4、其它常用Widget

Flutter 中除了布局的 Widget ,还有交互显示和完整页面呈现的 Widget 。其中常见的有 MaterialAppScaffoldAppbarTextImageFlatButton 等。

常用Widget及特点

  • Text

    textAlign: 设置文本对其方式:center、left、right、start、end

    maxLines: 设置最多显示的行数

    overflow: 文本溢出时怎么处理。clip:直接切断;ellipsis:...;fade:渐变(上下渐变,不是左右)

    style: 通过TextStyle设置字体大小、颜色、weight等

    _textDemo() {
    returnContainer(
    width:100,
    child:Text(
    'balabalabalabalabalabalabalabalabalabalabalabalabalabalabala',
    textAlign:TextAlign.end,
    style:TextStyle(
    fontSize:20,
    color:Colors.white,
    fontWeight:FontWeight.w500
    ),
    maxLines:1,
    overflow:TextOverflow.fade,
    )
    );
    }
  • image

    加载图片有四种方式分别为:

    Image.asset: 加载项目资源目录中的图片,相对路径

    Image.network: 加载网络图片

    Image.file: 加载本地图片,绝对路径

    Image.memory: 加载Uint8List资源图片(没使用过😢)

    通过fit属性设置图片的填充方式:

    BoxFit.fill: 图片拉伸以充满父容器

    BoxFit.contain: 比例不变拉伸,可能会有空隙(水平和竖直方向最多有一个方向有空隙)

    BoxFit.cover: 可能被裁减,比例不变拉伸,充满父容器

    BoxFit.fitWidth/fitHeight: 宽度/高度充满

    BoxFit.scaleDown: 效果和contain类似,但图片不会被拉伸超过源图片大小

    _imageDemo() {
    returnContainer(
    alignment:Alignment.center,
    width:300,
    height:400,
    color:Colors.blue,
    child:Image.network(
    'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2527385882,2413489660&fm=26&gp=0.jpg',
    fit:BoxFit.contain,
    )
    );
    }
  • TextField

    TextFieldCupertinoTextField 是两种不同风格的 textField ,两者样式有很大差异,用法略有不同。先来看下 TextField 的基本使用:

    _textFieldDemo0() {
    returnTextField(
    maxLength:30,
    maxLines:1,
    // 是否自动聚焦
    autofocus:false,
    // 是否为密码输入框
    obscureText:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    // 装饰效果
    decoration:InputDecoration(
    // decoration是否使用fillColor填充,默认为false
    filled:false,
    fillColor:Colors.red,
    hintText:'placeholder',
    helperText:'helperText',
    labelText:'labelText'
    ),
    enabled:true,
    // 在onSubmitted之前调用。实现该方法后,点键盘return输入框不会失去焦点
    onEditingComplete: () {
    print('TextField:complete');
    }
    );
    }

    下面 CupertinoTextField (iOS风格的TextField)的基本使用:

    _textFieldDemo1() {
    returnCupertinoTextField(
    maxLength:200,
    maxLines:1,
    autofocus:false,
    // 密码
    obscureText:false,
    autocorrect:false,
    textAlign:TextAlign.left,
    style:TextStyle(
    fontSize:17,
    color:Colors.black
    ),
    onChanged: (text) {
    print('onChanged:--$text--');
    },
    onSubmitted: (text) {
    print('onSubmitted:--$text--');
    },
    placeholder:'placeholder',
    clearButtonMode:OverlayVisibilityMode.editing,
    enabled:true,
    // 设置光标宽度,默认为2
    cursorWidth:2,
    // 设置光标颜色,默认为blue
    cursorColor:Colors.red,
    // 在onSubmitted之前调用。实现了该方法后,按return不会让textField失去焦点
    onEditingComplete: () {
    print('CupertinoTextField:complete');
    }
    );
    }
  • RaisedButton & FlatButton

    FlatButton用法和RaisedButton基本相同,但样式上FlatButton属于扁平化,RaisedButton属于拟物化,有立体感(凸起),可以设置Button底部阴影效果。

    // RaisedButton 凸起效果_raisedButtonDemo() {
    returnRaisedButton(
    onPressed: () =>_showMyDialog(),
    // highlight状态发生变化就调用(高亮和取消高亮都调用)
    onHighlightChanged: (value) {
    print('highlight changed: ${value}');
    },
    // 文本文字颜色
    textColor:Colors.white,
    // 背景色
    color:Colors.blue,
    child:Text('戳'),
    padding:EdgeInsets.all(10),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    color:Colors.black,
    width:1
    )
    ),
    elevation:10,
    );
    }
  • OutlineButton

    线框按钮,它的color属性不会生效(即不能通过color设置OutlineButton的背景色),带有默认边框。其余用法和效果同FlatButton,都是扁平化的效果。

    // OutlineButton 扁平化,默认带边框_outlineButtonDemo() {
    returnOutlineButton(
    onPressed: () =>_showMyDialog(),
    textColor:Colors.blue,
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // borderSide可以设置边框的颜色、宽度和边框样式(none和solid两种)
    borderSide:BorderSide(color:Colors.blue),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    // 这里的BorderSide只可以设置边框样式,并且优先级低
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }
  • RawMaterialButton

    其效果类似FlatButton,用法与上面的三种button稍有差异。上面介绍的几种button,它们的很多属性(如shape)可以设置为null,但RawMaterialButton的属性不允许有null,否则抛出异常。

    // MaterialButton 扁平化,效果类似FlatButton_rawMaterialButtonDemo(bool withBorder) {
    returnRawMaterialButton(
    onPressed: () =>_showMyDialog(),
    child:Text('balabala'),
    padding:EdgeInsets.all(10),
    // 填充色/背景色
    fillColor:Colors.blue,
    textStyle:TextStyle(color:Colors.white),
    shape:RoundedRectangleBorder(
    borderRadius:BorderRadius.circular(20),
    side:BorderSide(
    style:BorderStyle.solid
    )
    )
    );
    }

    ⚠️ 以上这几种button都可以设置其padding值,但它们都有一个默认的padding。当这些button的上一层级Widget的尺寸足够容纳button,这时设置的padding值比默认值大才会生效。当button上一层级的Widget不足以展示button的默认padding时,设置较小的padding可以生效。我们可以通过在button外面包裹一层Container,并设置其alignment为center,再根据需求设置Container的宽高,就可以布局出自己想要的button样式。

  • 通过GestureDetector实现自定义样式的button

    由于上面介绍的几种button,都有一些无法改变的固定样式,这往往不能满足开发中的特定需求。除了上面说的利用Container包裹button以达到想要的样式之外,还可以通过GestureDetector来实现自定义样式的button(其实就是在自己布局的Widget基础上添加了点击手势功能)。

    _myButton() {
    returnGestureDetector(
    onTap: () =>_showMyDialog(),
    child:Container(
    width:70,
    height:28,
    margin:EdgeInsets.only(top:20),
    decoration:BoxDecoration(
    borderRadius:BorderRadius.circular(11.0),
    border:Border.all(width:0.5, color:Color(0xFF6BCBD7))
    ),
    child:Center(
    child:Text('点击刷新',
    style:TextStyle(
    fontSize:13,
    color:Color(0xFF6BCBD7)
    ),
    ),
    ),
    ),
    );
    }
  • CupertinoButton

    CupertinoButton是iOS风格的button

    _iosButtonDemo() {
    returnContainer(
    margin:EdgeInsets.only(top:15),
    child:CupertinoButton(
    child:Text(
    'abc',
    style:TextStyle(fontSize:17, color:Colors.white),
    ),
    color:Colors.blue,
    minSize:20,
    padding:EdgeInsets.fromLTRB(15, 10, 15, 10),
    borderRadius:BorderRadius.circular(10),
    onPressed: () =>_showMyDialog()
    )
    );
    }
    // 以上所有例子中的按钮点击事件_showMyDialog() {
    showDialog(
    context: context,
    builder: (context) {
    returnAlertDialog(
    title:Text('我是title'),
    content:Text('我是content'),
    actions:<Widget>[
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('确定')
    ),
    FlatButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child:Text('取消')
    )
    ],
    );
    }
    );
    }

About

Dart简单语法介绍和Flutter中常用Widget介绍

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages