Flutter第二篇Flutter核心知识点概讲

840 阅读10分钟

第1章 Flutter的特点和核心概念

1.1 一切皆为组件

组件(Widget)是Flutter应用程序用哦过户界面的基本构建块。按钮,输入框,卡片,列表可以作为组件。Android中的View的布局方式,动画处理都是组件。所以Flutter可以被认为,Widget就如同Java中的Object。

1.2 组件嵌套

复杂的功能页面有一个个简单功能的组件组装完成。有的组件负责布局,有的负责定位,有的负责大小,有的负责渐变处理,等等。这种嵌套组合方式,带来最大的好处就是解耦。下面是flutter的hello world.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }
}

image.png

无状态组件就是界面不会发生变化的组件,或者说,不需要和用户进行交互操作的组件,所谓交互就是点击,长按,滑动。有状态组件是界面会发生变化的组件,如滚动组件。

1.3.构建组件

可以重写widget的build方法来构建一个组件,如上面的HelloWorld程序。build返回的是一个Widget对象,不管是单个组件还是嵌套组合的组件,都是Widget实例。

1.4.处理用户交互

对于StatefulWidget,他们的可变状态存储在State子类中,当改变State对象时,必须调用setState()来通知框架,框架会再次调用State的构建方法来更新界面。

//主页需要继承自StatefulWidget
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  //标题
  final String title;

  //必须重写createState方法
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
//状态类必须继承State类,注意后面需要指定为<MyHomePage>
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;//计数器

  void _incrementCounter() {
    //调用State类里的setState方法来更改状态值,使得计数器加1
    setState(() {
      //计数器变量,每次点击让其加1
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      //居中布局
      body: Center(

        //垂直布局
        child: Column(
          //主轴居中对齐
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '你点击右下角按钮的次数:',
            ),
            Text(
              '$_counter',//绑定计数器的值
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,//按下+号按钮调用自增函数
        tooltip: '增加',
        child: Icon(Icons.add),
      ),
    );
  }
}

1.5.什么是State

界面是由组件搭建的,组件中最重要的概念是State,State是一个组件的UI数据模型,是组件渲染时候的数据依据,同时内部也有对应的View的定义,如上面例子中的code。一些用户操作,比如点击,长按,滑动,推动State中的UI数据改变,然后通过调用setState()方法,实现View层的刷新。也就是build方法拿最新的数据重新渲染。flutter的状态的生命周期如下:

image.png

第2章 Flutter基础知识

2.1 入口程序

每一个flutter项目的/lib目录下都有一个main.dart文件,这里面有一个main(),这就是Flutter运行程序的入口,代码如下所示:

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

2.2 Flutter主题

主题分为全局主题和局部主题,全局主题就是由应用程序根MaterialApp创建的主题。

2.2.1 创建应用主题

创建主题的方法是讲ThemeData对象提供给MaterialApp构造函数,这样就可以在整个应用程序中共享包含颜色和字体样式的主题。ThemeData的主要属性通过查看类的构造函数的依赖参数可以了解。索引如下theme的部分:

@override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "",
      theme: ThemeData(brightness:Brightness.dark),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Oh,Plugin example app'),
        ),

主题常用的有40个参数可以配置

2.2.2 创建局部主题

如果我们想要在应用程序的某一部分使用特殊的颜色,那么就要覆盖全局的主题。有两种方法:创建特有的主题数据或者扩展父主题。

  • 创建特有的主题数据 直接实例化一个ThemeData并传递给Theme对象,代码如下:
theme: ThemeData(
        //应用程序整体主题的亮度
        brightness: Brightness.light,
        //App主要部分的背景色
        primaryColor: Colors.lightGreen[600],
        //前景色(文本、按钮等)
        accentColor: Colors.orange[600],
      ),
  • 扩展父主题 扩展父主题时候无需覆盖所有的主题属性,我们通过使用copyWith来实现,代码如下:
floatingActionButton: Theme(
        //使用copyWith的方式获取accentColor
        data: Theme.of(context).copyWith(accentColor: Colors.grey),
        child: FloatingActionButton(
          onPressed: null,
          child: Icon(Icons.computer),
        ),
      ),
2.2.3 使用主题

主题定义好之后就可以使用它了。函数Tmeme.of(context)可以通过上下文来获取主题,方式是查找最近的主题,如果找不到就会找整个应用的主题。

下面是一个小sample,应用的主题颜色定义为绿色,没解密那中间再加一个带有背景色的文本。

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final appName = '自定义主题';

    return MaterialApp(
      title: appName,
      //主题配置
      theme: ThemeData(
        //应用程序整体主题的亮度
        brightness: Brightness.light,
        //App主要部分的背景色
        primaryColor: Colors.lightGreen[600],
        //前景色(文本、按钮等)
        accentColor: Colors.orange[600],
      ),
      home: MyHomePage(
        title: appName,
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  //标题
  final String title;
  //接收title值 key为widget的唯一标识
  MyHomePage({Key key, @required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Container(
          //获取主题的accentColor
          color: Theme.of(context).accentColor,
          child: Text(
            '带有背景颜色的文本组件',
            //获取主题的文本样式
            style: Theme.of(context).textTheme.title,
          ),
        ),
      ),
      floatingActionButton: Theme(
        //使用copyWith的方式获取accentColor
        data: Theme.of(context).copyWith(accentColor: Colors.grey),
        child: FloatingActionButton(
          onPressed: null,
          child: Icon(Icons.computer),
        ),
      ),
    );
  }
}

2.3 使用包资源

Flutter包就是flutter插件。类似于jar包。是世界上优秀的开源软件工作者创造的轮子,访问pub.dev网站可以搜索自己需要的轮子。

比如路由处理(fluro),网页跳转(url_launcher),第三方平台SDK(Firebase)。

image.png

image.png

image.png

第3章 Dart语言简述

Dart作为Flutter SDK指定的语言,我们需要了解Dart语言的基础知识,包括语法特性,基本语句,面向对象。

3.1 Dart重要概念那和常用开发库

Dart的声明式变成布局已有阅读和可视化看,因此不再需要单独的声明式布局语言XML和可视化界面构建器。这让界面渲染的速度更快。 Dart的重要概念如下:

  • Dart代码在运行前解析。指定数据类型和编译时的常量,可以提高运行速度。
  • Dart是弱类型的的语言。
  • Dart类似于Java中的一切皆对象。
  • Dart程序有统一的函数入口,main()。
  • Dart没有public,protected和private的该你那。私有特性通过变量或函数加上下划线来表示。
  • Dart的warning不妨碍程序运行,errors,可以是斌阿姨时候的错误,也可能是运行时候的村务。
  • Dartzhichi async、await异步处理
  • Dart有关键字56个;

Dart语言常用库如下:

包名                                            描述
dart:async                 异步编程支持,提供Future和Stream类
dart:collection            对dart:core提供更多的集合支持
dart:convert               不同类型(JSON,UTF-8)间的字符编码、解码支持
dart:core                    Dart语言内建的类型、对象以及dart语言核心的功能
dart:html                    网页开发用到的库
dart:io                        文件读写I/O相关操作的库
dart:math                   数字常量及函数,提供随机数算法
dart:svg                     事件和动画的矢量图像支持

以下三个库的使用频率最高
①.dart:core:核心库,包括strings、numbers、collections、errors、dates、URIs等
②.dart:html:网页开发里DOM相关的一些库
③.dart:io:I/O命令行使用的I/O库

dart:core库是Dart语言初始已经包含的库,其他的任何库在使用前都需要加上import语句。

3.2 变量与基本数据类型

Dart中的变量用var关键字声明,如下:

var name = '靓哥' 变量如果没有初始化,那么默认是空;

3.2.1.常量和固定值

final或者const来声明常量。const是一个编译时常量,final的值只能赋值一次.

Dart中const与final区别:如果常量是编译期就能初始化的就用const;如果常量是运行时期初始化的就用final。如果使用 const 修饰类中的变量,则必须加上 static 关键字,即 static const(顺序不能颠倒)

3.2.2.基本数据类型
语言DartOC
数字类型num、int、doubleint、float、double、long long、NSInteger、NSNumber、NSValue
布尔类型bool(true、false)bool、BOOL
字符串String:单双引号均可定义String nickname = 'Bobby';NSString、NSMutableStringNSString *nickname = @”Bobby”;
数组List:Dart 在 2.3 引入了扩展操作符(...)和 空感知扩展操作符(...?),它们提供了一种将多个元素插入集合的简洁方法Dart 还同时引入了 集合中的if 和 集合中的 for 操作NSArray、NSMutableArray
集合Setvar halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};NSSet、NSMutableSet
字典Mapvar gifts = Map<String, String>();gifts['first'] = 'partridge';NSDictionary、NSMutableDictionary
枚举类型enum Color { red, green, blue }enum、NS_ENUM、NS_OPTIONS
动态类型dynamic: Indicates that you want to disablestatic checking. Usually you should use Object or Object? instead.id
类型定义符typedeftypedef
变量var(根据初始化赋值推断数据类型)必须声明时指定数据(对象)类型
常量const、finalconst
类型判断is、is!isKindOfClass、isMemberOfClass
类型转换as(类型)、__bridge
数据输出int num = 1;print("整数类型 num: $ {num} int num = 1;NSLog(@"整数类型 num:%@", @(num))
基类ObjectNSObjcet
函数类型Function
其他runes、symbol、late(变量首次使用的时候初始化)
3.2.3.详细的讲讲基本数据类型

(1).Number类型

包含java中的int整形和double浮点型

(2).String类型

可以使用三个单引号或者双引号来定义多行的String类型,在Flutter中专门用三个引号表示大文本块。示例代码如下:

var s1 = '''
请注意这是一个用三个单引号包裹起来的字符串,
可以用来添加多行数据。换成三个双引号也是一样的。
双引号是“这样的。
‘’‘

(3).Boolean类型

Dart是强bool类型检查,只有bool类型的值是true才是true,所以java中的if(1>0)这样的判断是不支持的。

(4).List类型

Dart里面的List是具有相同类型的一组数据。类似java中被泛型限定的集合。或者说,就像是Java里面的数组。示例如下:

var list = [1,2,3];

List对象的第一个元素的索引是0,最后一个元素的索引是list.length-1.

(5).Map类型

Map是键值对,Key必须是唯一的。如下示例是一个Map,分别是数组方式和构造方式创建Map对象:

var week = {
      'Monday' : '星期一',
      'Tuesday': '星期二',
      'Wednesday' : '星期三',
      'Thursday': '星期四',
      'Friday': '星期五',
      'Saturday': '星期六',
      'Sunday': '星期日',
    };

    var week = new Map();
    week['Monday'] = '星期一';
    week['Tuesday'] = '星期二';
    week['Wednesday'] = '星期三';
    week['Thursday'] = '星期四';
    week['Friday'] = '星期五';
    week['Saturday'] = '星期六';
    week['Sunday'] = '星期日';
    week['0'] = '星期一';

3.3 函数

函数也是对象,每个函数实例属于Function类型的对象。函数可以作为参数传递给其他函数。便于回调处理。

3.3.1.可选参数

将函数中的参数使用中括号括起来,用来表明是可选位置参数。例如,总共传入了三个参数,其中name和sex是必须传入的参数,from参数可以不传,代码如下:

//获取用户信息
String getUserInfo(String name, String sex, [String from]) {
    var info = '$name的性别是$sex';
    if (from != null) {
        info = '$info来自$from';
    }
    return info;
}

void test() {
    print(getUserInfo('小王', '男'));
}

调用上面的test方法输出”小王的性别是男“,但是不会输出来自哪里

3.3.2.参数默认值

如果参数指定了默认值,当不传入值时,函数会使用这个默认值。如果传入了值,会使用传入的值取代默认值。通常参数的默认值为null。示例如下:

String getUserInfo(String name, String sex, [String from = '中国']) {
    var info = '$name的性别是$sex';
    if (from != null) {
      info = '$info来自$from';
    }
    return info;
  }

  void test(){
    print(getUserInfo('小王', '男'));
  }

调用上面的test方法输出”小王的性别是男来自中国“

3.3.3.函数的返回值

在Dart中,函数的返回值有如下特点:

  • 所有的函数都会有返回值
  • 如果没有指定函数返回值,则默认的返回值是null
  • 没有返回值的函数,系统会在最后添加隐式的return语句

3.4 运算符

所有语言运算符都是差不多的。首先运算符肯定有优先级,优先级的问题通过括号可以解决,不建议用脑力记忆。

运算符也都是差不多,dart就是多了些。有如下几个:

3.4.1. 算术运算符

+,-,*,/,~/,%,++,--

3.4.2. 关系运算符

==,!=,>,<,<=,>=,

3.4.3. 类型测试运算符
操作符含义Java中
as类型转换
is当对象是相应类型时返回trueinstance of
is!当对象不是相应类型时返回true!instance of
3.4.4. 赋值运算符

要仅在比那辆为null时候赋值,使用??=运算符,示例如下:

//赋值给a
a= value;
//如果b为空,则将值分配给b;否则,b保持不变
b ?? = a;

赋值运算符包括复合赋值运算符局,将操作与赋值相结合和。+=,-=,这种,和java一样。

3.4.5. 逻辑运算符
操作符含义Java中
!expr反转一下表达式same
&&逻辑或same
&&逻辑与same

逻辑或'||'符号怎么通过markdown表格绕过检查呢?大家懂就好

3.4.6. 条件表达式

Dart可以用if,else,也可以通过三元表达式。还有简介版本的三元表达式

expr1 ?? expr2,如果expr1为非空,返回expr1,否则返回expr2的表达式结果。

3.4.7. 级联操作

类似java里面的Build模式,级联通过两个点(..)表示,对同一个对象一直调用方法。

3.5 流程控制语句

和java一样,也是如下这些控制语句:

  1. if和else
  2. for
  3. while和do-while
  4. break和continue
  5. switch和case

Dart中switch/case语句使用==操作来比较整数,字符串或其他编译过程中的常量,从而实现分支的作用。switct/case语句的前后操作数必须是相同类型的对象实例。每个非空的case字句最后都必须跟上breaak语句。和java差不多。

  1. assert

断言,如果执行判断是false则发生中断。

  1. try-catch和throw

3.6 异常处理

Dart可以抛出并捕获异常,但是和java不同处,Dart的所有异常都是未检查的异常。方法不声明他们可能抛出哪些异常,也不需要捕获任何异常。神奇的是,Dart程序可以抛出任何非空对象。如下: throw '数据为空' 稳健的程序一定是做了大量的异常处理,所以用Dart写程序,需要我们尽量考虑到可能发生的异常。这有点像是垃圾的JS。排查问题挨个断点了。

Dart的异常处理也是try-catch-finally

3.7 面向对象

Dart支持基于mixin的继承方式,就是多继承。 Dart的类中所有的变量都会隐式的定义setter方法,针对非空的变量会额外增加getter方法。

3.7.1.构造函数

Dart中有命名的构造函数,类似User.fromJson(Map json){name = json['name']},从另一个类或者现有数据中快速实现构造函数。

Dart中有构函数初始化列表,类似 User(name,age):name = name,age=age;,当调用new User('hello',20)时候,会更快的完成实例的成员变量的赋值。

3.7.2.读取和写入对象

get()和set()方法专门用于读取和写入对象的属性。每一个类的实例,系统都会隐式地包含有get()和set方法。里一个例子如下:

class Rectangle {
  num left;
  num top;
  num width;
  num height;
  Rectangle(this.left, this.top, this.width, this.height);
  //获取right值
  num get right         => left + width;
  //设置right值 同时left也发生变化
  set right(num value)  => left = value - width;
  //获取bottom值
  num get bottom        => top + height;
  //设置bottom值 同时top也发生变化
  set bottom(num value) => top = value - height;
}


main() {
  var rect = new Rectangle(3, 4, 20, 15);
  print('left:'+rect.left.toString());
  print('right:'+rect.right.toString());
  rect.right = 30;
  print('更改right值为30');
  print('left:'+rect.left.toString());
  print('right:'+rect.right.toString());
  print('top:'+rect.top.toString());
  print('bottom:'+rect.bottom.toString());
  rect.bottom = 50;
  print('更改bottom值为50');
  print('top:'+rect.top.toString());
  print('bottom:'+rect.bottom.toString());
}

3.7.3.重载操作

Dart中可以重载算术运算符,比如某个类中重载+,类对象调用+,将会按照重载方法执行

//定义一个向量类
class Vector {
  final int x;
  final int y;
  const Vector(this.x, this.y);
  //重载加号 + (a + b).
  Vector operator +(Vector v) {
    return new Vector(x + v.x, y + v.y);
  }

  //重载减号 - (a - b).
  Vector operator -(Vector v) {
    return new Vector(x - v.x, y - v.y);
  }
}

main() {
  //实例化两个向量
  final v = new Vector(2, 3);
  final w = new Vector(2, 2);
  final r1 = v + w;
  print('r1.x='+r1.x.toString() + ' r1.y=' + r1.y.toString());
  final r2 = v - w;
  print('r2.x='+r2.x.toString() + ' r2.y=' + r2.y.toString());
}
3.7.4.继承类

和java一样,通过extends和super实现继承。

3.7.5.抽象类

Dart中的抽象类相当于java中的接口。抽象类里没有具体实现方法。使用abstract关键字。

3.7.6.枚举类型

和java里面的枚举一样的,也是用enum关键字

3.7.7.Mixins

Mixins是多继承,也就是说使用这个关键字可以实现继承多个类。使用with关键字来实现Mixins的功能。这个概念比较复杂,建议单独从网上找一篇博客看看。我简要说一下,就是使用类似如下的方式:

class T = A with S;

则声明了一个T类,T类同时继承了A类和S类了。创建T类的实例就可以调用A类和S类的方法了。

3.8 泛型

Dart中使用<'T'>的方式来定义泛型,作用的地方如下:

3.8.1.用于集合类型

List和Map,例子如下:

var names = <'String'> ['a','b','c'];

var weeks = <String,String> {}

3.8.2.在构造函数中参数化

var user = new Map<String,User>();

3.9 库的使用

3.9.1.引用库

通过import语句在一个库中引用另一个库的文件。注意如下:

  • 在import语句后面需要接上库文件的路径。
  • 对Dart语言提供的库文件使用dart:xx格式。
  • 对第三方的库文件使用package:xx格式。
import 'dart.io';
import 'pakcage:mylib/mylib.dart';
3.9.2.通过as指定一个库的前缀

对于两个库有相同的类,则可以给某个库设置一个别名

import 'pakcage:mylib/mylib.dart’;
import 'pakcage:mylib2/mylib.dart' as lib2;
3.9.3.引用库的一部分

如果只需要使用库的一部分内容,可以通过show和hide关键字实现有选择的引用。

  • show关键字:只引用一点
  • hide关键字:除此之外都引用。
//只导入foo
import 'pakcage:mylib/mylib.dart’ show foo;

//除了foo都导入
import 'pakcage:mylib/mylib.dart’ hide foo;

代码中的第一行只引用mylib.dart下的foo方法,第二行代码则除了foo方法,都导入了。

3.10 异步支持

使用async函数和await表达式实现异步操作。 Dart库给提供asynchronous功能,该功能提供接口来进行耗费时间的操作,比如文件读写,网络请求。返回值为Future或者Stream对象。

async和await总是成对出现,必须在一个使用了async关键字标记的函数中使用await表达式,示例如下:

fileOperate() async {
  //读取文件
  var file = await readFile();
  //其他处理
}

3.11 元数据

元数据是以@开始的修饰符 ,目前Dart提供三个@修饰符: @deprecated 被弃用的 @override 重写 @proxy 代理 元数据可以修饰libraray,class,typedef,type parameter,constructor,factory,function,field,parameter,variable declaration。

就是一个功能不够丰富的java注解。

3.12 注释

Dart支持三种注释类型:

  1. 单行注释 //
  2. 多行注释 /* */
  3. 文档注释 /**或者///开头

第4章 常用组件

第5章 页面布局

第6章 手势

第7章 资源和图片

第8章 路由与导航

第9章 组件装饰器

第10章 动画

第11章 Flutter插件

第12章 组件检查器