flutter学习笔记 - Dart语言;Flutter组件;状态管理;事件处理;路由管理;网络编程;数据存储;

此文章并不能构成体系,仅作为个人学习笔记使用


Dart 语言

一、特性

1,一切皆对象
Dart 是纯对象的编程语言,处理的所有值,包括基本类型函数都是堆绣
2,面向接口编程
Dart 核心思想是关注对象行为而非内部实现:
(1)没有final方法,允许重写除内置操作符以外所有方法
(2)语言是基于接口,而不是基于类。 所有类都隐含一个接口,能被其他类实现,处理数值、布尔、字符串
(3)Dart 把所有对象都进行了抽象封装,确保外部操作都通过存取方式改变对象状态
(4)构造函数允许对对象进行缓存,或从子类创建
3,类型可选

二、变量常量、类型、运算符

1,变量
var 定义,默认变量值为null

Dart 是强类型语言,和js这种弱类型语言明显区别:Dart 第一次将变量赋值为字符串,则后续不能将变量更改为其他类型【或用dynamic关键字修饰,可以改变类型】; js可以随便更改类型

2,常量
final / const 都可以定义常量,区别是 const 在编译时检查值,final 在运行时检查值
3,内置类型
(1)数值型:int / double 【没有float】
int 可以赋值给double,反过来则不行
(2)布尔型bool: true / false
(3)字符串
单引号、双引号、r 创建原始字符串、''' ''' 创建多行信息;
支持插值表达式 ${name}
(4)列表:集合和数组是同一个概念

//创建列表
var l1 = ['a','b','c']
var l2 = new List()
  ..add(1)
  ..add(2)

(5)键值对:Map,未明确指定类型时,默认Map<dynamic,dynamic>
(6)动态类型和Object
Dart 中所有对象的父类都是Object
支持 is !is as
4,运算符
(1)三目运算符 ?: ??
(2)取商运算符: ~/
(3)级联运算符: .. 链式调用
(4)自定义运算符

三、循环、遍历、异常、set&get

1, 循环
for while switch
3,异常
java 中 throw 的是一个异常对象 NullPointerException,Dart 中可以 throw 任何类型的异常

try{
}on String catch(e){// 预料到可能回抛出一个字符串类型异常
}catch(e){
}final{
}

4,set&get
如果属性是共有的,不用写set&get函数,可以直接使用;
如果属性是私有的['_'],需要手动实现set&get

四、函数、抽象、接口、继承、异步

1,函数
(1)main 顶层入口函数
(2)可选参数

void setUser( { String name,int age } ) //可不传,可传一,可传二

(3)可选位置参数

void setUser( { String name,int age, [String sex] } ) 

(4)默认参数

void setUser( { String name = 'admin' , @require int age} ) 

(5)函数可以作为参数传递
2,抽象 abstract
3,接口
Dart 中有接口,但没有接口声明,可以定义抽象类描述接口
4,继承
Flutter 只支持单继承,可用@override 重写,@super 调用超类
5,异步
Dart 是单线程的,执行耗时操作就会堵塞;因此使用Future 对象执行异步操作,搭配 async 和 await 关键字
(1)Future 是泛型对象,用来标志函数,返回值是 Future<T>
该函数将加入待完成的队列;当操作结束后,回返回值或错误

Future<String> getFileStr(File file){}
s_file.getFileStr(file)
  ..then((f){...})
  ..catchErroe((err){...})
  ..whenComplete() => {...}

(2)async await 减轻异步操作的工作量,减少嵌套地狱

tasks() async{
  String task1Result = await task1('task1');
  String task2Result = await task2(task1Result );
  String task3Result = await task3(task2Result );
}
五、泛型、mixin

1,泛型
2,mixin
Dart 是单线程的,Flutter 引入 mixin 解决多继承的问题;用非继承的方式,来复用类中的方法
可以把自己的方法提供给其他类,而不用成为其父类
(1)当三者都有时,调用同一方法,运行顺序是: mixin extends implements


Flutter组件

一、基础组件

1,Text
2,Button
RaisedButton、FlatButton、IconButton、OutlineButton、FloatingActionButton
3,Icon
4,Image

//网络图片
Image(
  image:NetworkImage('')
)
//本地图片
Image(
  image:AssetImage('')
)
//图片文件
Image.file(
  file('')
)
二、单一子元素组件

1,Container
(1)对齐方式: alignmen: Alignment.center/centerLeft/topCenter/topLeft...
(2)默认占满父组件,可以使用约束: constraints,控制Container组件大小,根据子组件内容适当调整大小

Container(
  constraints:BoxConstraints(
    maxHeight:200,  
    minHeight:100,  
    maxWidth:200,  
    maxWidth:100,  
  )
)

扩展到最大,则使用 constraints:BoxConstraints.extends
(3) padding margin

EdgeInsets.only({top  bottom  left   right})
EdgeInsets.symmetric({ vertical:top/bottom, horizontal:left/right })
EdgeInsets.fromgLTRB() // 分别指定四个方向
EdgeInsets.All() //所有方向用同一个数值填充

(4)docoration: BoxDecoration
2,Padding
Flutter 淡化了Padding和Margin的区别
3,Align: 设置child 的对齐方式
(1)widthFactor 和 heightFactor : Align会随着这两个属性改变自己的尺寸
4,Center: 继承自Align
5,FittedBox: 类似于 ImageView
(1)fit: 缩放方式,类似于 scaleType
6,AspectRatio :根据设置,调整子元素child 的宽高比
AspectRatio 会首先在布局约束的范围内尽可能的扩展, aspectRatio 参考值
7,SingleChildScrollView :类似于 ScrollView
8,ConstrainedBox 约束性的组件,必须设置 constraints 属性
9,Baseline,基线组件,控制不相干的几个组件在同一个水平线上对齐

三、多子元素组件

1,Scaffold 脚手架,标准化布局容器
其结构集成了AppBar body bottomNavigationBar floatingActionButton drawer
2,AppBar 顶部导航栏
控制App路由,显示顶部标题栏


AppBar

3,Row 和 Column
4,ListView
四种创建方式:
(1)直接使用

ListView(
  itemExtent:30, // 子组件高度 【可以不设置,自适应;设置后效率更高】
  children:<widget>[
        Text('1'),
        Text('2'),
        Text('3'),
  ]
)

(2)ListView.builder

ListView.builder(
  itemExtent:30,
  itemCount:4,
  itemBuilder:(context,position){
    return Text('$position')
  }
)

(3)ListView.separated 带分隔符

ListView.builder(
  itemCount:4,
  itemBuilder:(context,position){
    return Text('$position')
  },
  separatorBuilder:(context,position){
    return Container(
      width:500,
      height:20,
      color:Colors.red
    )
  }
)

(4)ListView.custom 自定义功能
ListView.custom 可以通过 SliverChildListDelegate 来接收 IndexedWidgetBuilder ,并为 ListView 生成列表项,实现自定义功能
ListView.builder 和 ListView.separated 内部都是通过 ListView.custom 实现的
5,GridView
gridView 的创建,可使用 ListView 的创建方式
但其还有两个独特的方式
(1)设置每行固定展示两个item

GridView.count(
  crossAxisCount:2,
  mainAxisSpacing:10,
  crossAxisSpacing:10,
  children:<widget>[
        Text('1'),
        Text('2'),
        Text('3'),
  ]
)

(2)设置每item最大像素宽度 maxCrossAxisExtent ,自动适配个数,能放下几个就放几个

GridView.extent(
  maxCrossAxisExtent :130,
  mainAxisSpacing:10,
  crossAxisSpacing:10,
  children:<widget>[
        Text('1'),
        Text('2'),
        Text('3'),
  ]
)

6,CustomScrollView
可以包含不止一个滚动组件
7,Stack:绝对布局组件
8,IndexedStack: 通过 index 属性,直接切换子组件
类似于 PageView,没有动画过渡
9,Table
10,Flex: 弹性布局
flex 弹性系数,类似于 layout_weight
11,Wrap: 类似于Row,支持自动换行
12,Flow:承继Wrap 功能,主要用于自定义布局,或性能较高场景

四、其他组件

1,TextField 文本输入框

属性 说明
controller 输入框监听器,类似于 TextWatcher
decoration 输入框装饰属性
textAlign 内容对齐方式
textAlignVertical 文本垂直对齐
textDirection 文字方向
cursorColor cursorHeight cursorWidth 光标
showCursor 是否显示光标
obsureText true 时为密码属性,展示 *
keyboardType 限制输入的文本类型
readOnly 是否只读
autofocus 自动对焦
maxLength 最大文本长度
maxLines minLines 最大 最小行数
onEditingComplete onSubminted 编辑完成触发的回调
onChanged 文本改变时回调

2,TextFormField 文本输入框
TextField 本身不能设置默认值,也不支持表单数据的前置校验;TextFormField 针对TextField 又封装了一层

3,侧滑菜单 Drawer + ListTile
4,轮播 Swiper


状态管理

一、基础组件

Flutter 中一切皆组件Widget,组件分为 StatelessWidget 和 StatefulWidget 两大类。
从上到下构成 Widget树 , 每个Context 对应一个Widget,也就构成 Context树
1,StatelessWidget 无状态组件
特性:
(1)内部属性声明为final,无法更改,
(2)生命周期只有:初始化 -> build() 界面渲染
2,StatefulWidget 状态组件
特性:
(1)创建组件的时候,也会创建一个 state 对象,通过这个对象,和用户交互并刷新页面
(2)内部属性声明可以通过 setState() 方法更改,
(3)组件由两部分构成:主体部分、State部分
  1)主体部分继承 StatefulWidget ; 但这里并没有 State 对象,因此主体部分的变量也是无法更改的
  2)State 部分继承 State
(4)生命周期

橙色为APP生命周期,右侧为Widget生命周期

二、State、Key

1,State 是对组件的行为和布局的描述
(1)生命周期

  • initState: 用于初始化数据,和绑定controller; 此时未关联Context,无法访问Context
  • didChangeDependencies : 在 state 对象依赖发生变化时调用;此时可以访问Context
  • build :构建Widget树
  • reassemble :debug 模式下,热重载时调用
  • didUpdateWidget : 重新构建Widget 树时调用,一般用于检测新旧Widget属性,看看属性值是否改变
  • deactivate :
  • dispose : 永久移除时调用;在此处移除监听,释放资源

(2)生命周期场景
  1)场景 1:打开页面:

  • constructor
  • createState
  • initState
  • didChangeDependencies
  • build

  2)场景 2:退出页面:

  • deactivate
  • dispose

  3)场景 3:热重载:

  • reassemble
  • didUpdateWidget
  • build

  4)场景 4:横竖屏切换

  • didUpdateWidget
  • build
  • didUpdateWidget
  • build

2,Key
每个Widget 都有独有的key,在组件渲染时生成的。当 Widget 的属性发生变化时,Flutter 框架会根据Key来判断是否需要重新创建 Widget,还是复用已有的 Widget。
满足以下定义:

  • 更新元素时,新提供的Widget 的key,必须与之前关联Widget的key相等
  • 同一个父节点下,各个子节点的key,必须不同
  • 要实现key的子类,必须继承 GlobalKey,或 LocalKey
key的分类

(1)GlobalKey
GlobalKey是全局唯一的,它可以跨 Widget 树使用,用于获取子 Widget 的状态和方法。这在需要在不同 Widget 之间共享状态,或者在 Widget 树的不同层级访问某个 Widget 时非常有用。
(2)LocalKey
LocalKey用于在同一父 Widget 的子 Widget 之间进行唯一标识
  1) ValueKey:通过一个值来标识 Widget
  2) ObjectKey:基于一个对象来标识 Widget
  3) UniqueKey 总是生成一个完全唯一的 Key,即使传入相同的值,每次调用都不同。它适合在动态添加或删除组件时,确保组件在重新排列时强制销毁和重建。

三、InheritedWidget

InheritedWidget 是很特殊的组件,支持跨级数据传递。支持在Widget 树中共享数据。

setState 方法用于通知 Flutter 更新 Widget 的状态,比如重建当前 Widget 及其子树,它是从上到下进行通知的,但作用域仅停留在当前的 StatefulWidget 范围内。
如果有两个同级的 StatefulWidget ,比如 WidgetA 和 WidgetB ,当 WidgetA 的数据发生改变,想要通知 WidgetB 对应改变。
InheritedWidget 控件的使用:左边栏和右边栏是两个同级的 Widget ,右边栏中点击修改名称,左边栏中显示对应的修改内容。


事件处理

一、原始指针

1,Pointer Event
原始指针需要通过命中测试,获取触摸的操作区域,找到对应的Widget

  this.onPointerDown(PointerDownEvent event), //手指按下回调
  this.onPointerMove(PointerMoveEvent event), //手指移动回调
  this.onPointerUp(PointerUpEvent event),//手指抬起回调
  this.onPointerCancel(PointerCancelEvent event),//触摸事件取消回调 -> 基本不会用到

2,PointerEvent
PointerDownEvent、PointerMoveEvent 、PointerUpEvent 、PointerCancelEvent 都是PointerEvent的子类,存在以下属性:

  • position : Offset,指针相对于全局坐标的偏移
  • delta : Offset,两次指针移动事件的距离
  • orientation : double,检测到的物体的方向(指针移动方向),以弧度为单位

3,组件嵌套
组件嵌套时,事件传递机制如何控制?
通过两个组件: IgnorePointer 和 AbsorbPointer

  • IgnorePointer :此节点和其子节点,都将忽略点击事件,使用ignoring 区分是否忽略(true)
  • AbsorbPointer : 此节点响应点击事件,但阻止事件传递到子节点

4,命中测试
当手指按下、移动、抬起时,flutter 会给每个事件新建一个对象,PointerDownEvent、PointerMoveEvent 、PointerUpEvent
对每个事件对象,Flutter 都会执行命中测试:
(1)从最底层widget 开始执行命中测试。 是否命中看 hitTestChildren() 或 hitTestSelf()
(2)从下往上,直到找到第一个命中Widget,将它加入命中列表。 同时,将其父Widget也加入命中列表。

5,事件处理流程

  • 命中测试:当手指按下时,触发 PointerDownEvent 事件,按照深度优先遍历当前渲染(render object)树,对每一个渲染对象进行“命中测试”(hit test),如果命中测试通过,则该渲染对象会被添加到一个 HitTestResult 列表当中。
  • 事件分发:命中测试完毕后,会遍历 HitTestResult 列表,调用每一个渲染对象的事件处理方法(handleEvent)来处理 PointerDownEvent 事件,该过程称为“事件分发”(event dispatch)。随后当手指移动时,便会分发 PointerMoveEvent 事件。
  • 事件清理:当手指抬( PointerUpEvent )起或事件取消时(PointerCancelEvent),会先对相应的事件进行分发,分发完毕后会清空 HitTestResult 列表
二、手势操作监听 GestureDetector组件

父组件GestureDetector,可以对其子组件进行手势监听
1,常用属性
支持点击(Tap)、双击(Double Tap)、长按(Long Press)、拖动(Drag)、缩放(Scale)、压力感应(Force Press)等20+种手势事件,


单击手势

双击手势

长按手势

拖动手势

路由管理

一、定义

1,静态路由:明确知道跳转界面。直接注册,不能传递参数。
(1)MaterialApp 组件的 routes 定义路由表Map

runApp(
  MaterialApp(
      title: '一个Flutter应用', home: HomePage(), 
      routes: {
            '/home': (BuildContext context) => HomePage(),
            '/detail': (BuildContext context) => DetailPage()
      },
      onUnknownRoute: (setting) {
           return MaterialPageRoute(builder: (_) => Home());
       },//路由出错,找不到界面
        home: Page1(title: "主页面",),//主界面
))

(2)路由跳转 - 跳转静态路由

onPressed:(){
  Navigator.pushNamed( context, '/home' )
}

(3)返回
也就是出栈

Navigator.pop(context)

(4)也可以传参

 '/detail': (BuildContext context) => DetailPage(arguments: arguments)

2,动态路由,动态携带参数跳转

Navigator.push(context,MaterialPageRoute(builder: (context){
  return new SecondPage(title: "SecondPage");
}); )

3,参数回传
A -> B , B 返回时,传值A

//A -> B
Navigator.push(
                context,
                MaterialPageRoute(
                  // 传递title为SecondPage,跳转到第二个界面就会把标题设置为SecondPage
                  builder: (context) => SecondPage(title: "SecondPage"),
                  // 调用then等待接收返回数据
                )).then((value) => print(value));

//B -> A
Navigator.pop(context, "返回传递数据");

4,路由栈对应
(1)Navigator.push 和 Navigator.pop 对应 standard 启动模式。
(2)Navigator.of(context).pushReplacementNamed(),替换
(3)Navigator.of(context).pushNamedAndMoveUntil(),清空路由栈以上
(3)Navigator.popUtil( context, ModalRoute.withNage('/page2')),清空路由栈,直到page2,并直接返回page2

三方库 fluro

网络

http 库、dio库

单例模式、拦截器、适配器

json解析

1,手动解析:(dart:convert )

/*将字符串转成json  返回的是键值对的形式*/
Map<String, dynamic> news = jsonDecode(jsonData);
String sats = news['result']['stat'];

2,自动解析
自动生成 bean 文件后

/*先将字符串转成json*/
Map<String, dynamic> json = jsonDecode(jsonData);
/*将Json转成实体类*/
NewsBean newsBean=NewsBean.fromJson(news);
/*取值*/
String sats = newsBean.result.stat;

@JsonKey 注解

三、异步

数据存储

一、SharedPreferences

1,和安卓的区别:
(1)都是存储键值对,安卓是同步的,flutter 是异步的
(2)运行在安卓上的应用基于SharedPreferences,运行ios上基于 NSUserDefaults 开发
2,使用
(1)导库 yaml
shared_preferences: ^0.5.7+3
(2)基本操作

SharedPreferences prefs = await SharedPreferences.getInstance();
//存
await prefs.setString('username', 'FlutterDev');
//取
String? username = prefs.getString('username');
//删
await prefs.remove('username');
// 清空
await prefs.clear();
SQLite
文件存储 path_provider

path_provider可以创建和管理任意类型的文件,包括文本、图片、音频、视频等
1,App 存储目录类型
(1)临时目录
临时目录是系统可以随时清空的缓存文件夹

  • iOS对应的实现方式是 NSCachesDirectory
  • Android对应的实现方式是getCacheDir()

(2)文档目录
文档目录用于存储只能由该应用访问的文件,系统不会清除该目录,只有在删除应用时才会消失。

  • iOS对应的实现方式是 NSDocumentDirectory
  • Android对应的实现方式是 AppData

(3)应用程序支持目录
应用程序支持目录用于不想向用户公开的文件,也就是你不想给用户看到的文件可放置在该目录中,系统不会清除该目录,只有在删除应用时才会消失。

  • iOS对应的实现方式是 NSApplicationSupportDirectory
  • Android对应的实现方式是 getFilesDir()

(4)应用程序持久文件目录
该目录主要存储持久文件的目录,并且不会对用户公开,常用于存储数据库文件,比如sqlite.db等。
(5)外部存储目录
主要用于获取外部存储目录,如SD卡等,但iOS不支持外部存储目录,目前只有Android才支持。
(6)外部存储缓存目录
主要用户获取应用程序特定外部缓存数据的目录,比如从SD卡或者手机上有多个存储目录的,但iOS不支持外部存储目录,目前只有Android才支持。
(7)外部存储目录(单独分区)
可根据类型获取外部存储目录,如SD卡、单独分区等,和外部存储目录不同在于他是获取一个目录数组。但iOS不支持外部存储目录,目前只有Android才支持。

2,path_provider 提供的方法和说明


path_provider
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容