flutter基础学习.md
Flutter基础
学习来源:《Flutter实战·第二版》
介绍
Flutter 是 Google 推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。
技术特点:
- 跨平台自绘引擎。底层使用 Skia 作为其 2D 渲染引擎。
- 高性能;
- dart开发; 开发效率高、 高性能、 快速内存分配、 类型安全和空安全。
Flutter
框架图:
安装
# 使用archlinuxcn源安装 yay -S flutter # 追加flutter组 sudo gpasswd -a {user} flutterusers # 重载配置 source /etc/profile # 更换组 newgrp flutterusers # 其他 # 配置仓库 (加入环境变量) export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn # 检查 flutter doctor
运行
# 1. 创建新项目 flutter create hello # 2. 运行 flutter run
Dart语言
变量声明
var
关键字
// var被赋值之后,类型将确定,不可再更改类型 var t = 'hello'; // t的类型为String t = 100; // 错误
dynamic
和Object
// Object是所有对象的根基类。 // 所以任何数据类型都可以赋值给Object对象 dynamic t; Object c; t = 'as'; c = 'world'; t = 100; c = 200; // 正确 // 两者不同之处在于 dynamic可以使用所有可能的方法。而 Object只能使用Object的方法 dynamic a = ''; Object b = ''; // 正常 print(a.length); // 报错 The getter 'length' is not defined for the class 'Object' print(b.length);
final
和const
// final和const都是常量,不可变 // 区别在于 const是编译期初始化。而final为运行是初始化
空安全
int i = 9; // 默认不为空,定义时必须初始化 int j?; // 定义可为空类型,可以不初始化 // 预期变量之后初始化,但在定义时无法确定值,可以使用late (编译器提示,要求使用前必须初始化) late int k; k = 9; // 可以显式指定已经初始化完成,使用`!` int i?; if(i!=null) { print(i! * 8); //因为已经判过空,所以能走到这 i 必不为null,如果没有显式申明,则 IDE 会报错 } // 如果函数变量可空时,调用的时候可以用语法糖 fun?.call();
函数
// 函数声明 bool isNobel(int a) { return a > 0; } // 不指定返回类型,将当作 dynamic处理 isNobel(int a) { return a > 0; } typedef bool CALLBACK(); void test(CALLBACK b) { print(b()); } test(isNobel); // 错误:类型不一致 // 简写 bool isNobel(int a) => true; // 函数作为变量 var say = (str) { print(str); }; say("asda"); // 函数作为参数 void exe(CALLBACK cb) { cb(); } // 可选的位置参数 String say(String f, String msg, [String? device]) { var result = '$f says $msg'; if (device != null) { result = '$result with a $device'; } return result; } // 可选的命名参数 void enable({bool display, bool hidden}) { // ... } enable(bold: true, hidden: true);
mixin
组合
dart不支持多继承。使用mixin
进行组合。类似interface
。\
如果多个mixin 中有同名方法,with 时,会默认使用最后面的 mixin 的,mixin 方法中可以通过 super 关键字调用之前 mixin 或类中的方法。
class Person { say() { print('say'); } } mixin Eat { eat() { print('eat'); } } mixin Walk { walk() { print('walk'); } } mixin Code { code() { print('key'); } } class Dog with Eat, Walk{} class Man extends Person with Eat, Walk, Code{}
异步支持
Future
与JavaScript中的Promise
非常相似。
// Future.then Future.delayed(Duration(seconds: 2), (){ return 'hi'; }).then((data) { print(data) }); // Future.catchError Future.delayed(Duration(seconds: 2),(){ throw AssertionError("Error"); }).then((data){ print("success"); }).catchError((e){ print(e); }); // then函数有个onError错误参数来捕获异常 Future.delayed(Duration(seconds: 2), () { //return "hi world!"; throw AssertionError("Error"); }).then((data) { print("success"); }, onError: (e) { print(e); }); // whenComplete 无论成功失败,都做这个事情 Future.delayed(Duration(seconds: 2),(){ //return "hi world!"; throw AssertionError("Error"); }).then((data){ //执行成功会走到这里 print(data); }).catchError((e){ //执行失败会走到这里 print(e); }).whenComplete((){ //无论成功或失败都会走到这里 }); // Future.wait 等待事件完成之后,再运行之后代码 Future.wait([ // 2秒后返回结果 Future.delayed(Duration(seconds: 2), () { return "hello"; }), // 4秒后返回结果 Future.delayed(Duration(seconds: 4), () { return " world"; }) ]).then((results){ print(results[0]+results[1]); }).catchError((e){ print(e); }); // async 和 await 只是个Future的语法糖
Stream
Stream
也可以进行接收异步事件,但它可以同时接收多个异步操作的结果。
Stream.fromFutures([ // 1秒后返回结果 Future.delayed(Duration(seconds: 1), () { return "hello 1"; }), // 抛出一个异常 Future.delayed(Duration(seconds: 2),(){ throw AssertionError("Error"); }), // 3秒后返回结果 Future.delayed(Duration(seconds: 3), () { return "hello 3"; }) ]).listen((data){ print(data); }, onError: (e){ print(e.message); },onDone: (){ }); /* 结果为: I/flutter (17666): hello 1 I/flutter (17666): Error I/flutter (17666): hello 3 */
基本介绍
Widget
flutter
中几乎所有的对象都是一个 widget
。用来表示组件
和功能
。
widget
是描述一个UI元素的配置信息。
StatelessWidget
和StatefulWidget
继承了Widget
。
flutter
框架的的处理流程是这样的:
- 根据
widget
树生成Element
树。Element
树的节点都继承Element
类; - 根据
Element
树生成Render
树,渲染树的节点都继承RenderObject
类; - 根据
Render
树生成Layer
树,然后显式,Layer
树的节点都继承Layer
类。
StatelessWidget
StatelessWidget
用于不需要维护状态的场景。在build
方法中嵌套其他widget
来构建UI
。
StatefulWidget
abstract class StatefulWidget extends Widget { const StatefulWidget({ Key key }) : super(key: key); @override StatefulElement createElement() => StatefulElement(this); @protected State createState(); }
在StatefulWidget 中,State 对象和StatefulElement
具有一一对应的关系。当一个 StatefulWidget 同时插入到 widget 树的多个位置时,Flutter 框架就会调用该方法为每一个位置生成一个独立的State实例。
State
一个 StatefulWidget 类会对应一个 State 类。State
信息可以:
- 在
widget
被构建时同步读取; - 调用
setState
通知重新构建widgte
树。
State
有两个常用属性:
widget
:表示与该 State 实例关联的 widget 实例,由Flutter 框架动态设置。当在重新构建时,如果 widget 被修改了,Flutter 框架会动态设置State. widget 为新的 widget 实例。context
:StatefulWidget对应的 BuildContext,作用同StatelessWidget 的BuildContext。
状态管理
管理状态的最常见的方法:
-
Widget 管理自己的状态。
-
Widget 管理子 Widget 状态。
-
混合管理(父 Widget 和子 Widget 都管理状态)。
-
如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。
-
如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。
-
如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。
自身管理
import 'package:flutter/material.dart'; void main() { runApp(const TapboxA()); } class TapboxA extends StatefulWidget { const TapboxA({super.key}); @override _TapboxAState createState() => _TapboxAState(); } class _TapboxAState extends State<TapboxA> { bool _active = false; void _handleTap() { setState(() { _active = !_active; }); debugPrint("active: $_active"); } @override Widget build(BuildContext context) { final tb = TextButton( onPressed: _handleTap, child: Container( width: 200.0, height: 200.0, decoration: BoxDecoration( color: _active ? Colors.lightBlue[700] : Colors.grey[600]), child: Center( child: Text( _active ? "active" : "inactive", style: const TextStyle(fontSize: 32.0, color: Colors.white), textDirection: TextDirection.ltr, ), ), ), ); return Directionality(textDirection: TextDirection.ltr, child: tb); } }
父Widget管理子Widget
import 'package:flutter/material.dart'; void main() { runApp(const ParentWidget()); } class ParentWidget extends StatefulWidget { const ParentWidget({super.key}); @override _ParentWidgetState createState() => _ParentWidgetState(); } class _ParentWidgetState extends State<ParentWidget> { bool _active = false; void _handleTap(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Directionality( textDirection: TextDirection.ltr, child: TapBoxB(active: _active, onChanged: _handleTap)); } } class TapBoxB extends StatelessWidget { final bool active; final ValueChanged<bool> onChanged; const TapBoxB({Key? key, this.active = false, required this.onChanged}) : super(key: key); void _handleTap() { onChanged(!active); } @override Widget build(BuildContext context) { return TextButton( onPressed: _handleTap, child: Container( width: 200.0, height: 200.0, decoration: BoxDecoration( color: active ? Colors.lightBlue[700] : Colors.grey[600]), child: Center( child: Text( active ? "Active" : "Inactive", style: const TextStyle(fontSize: 32.0, color: Colors.white), ), ), )); } }
路由管理
路由在移动开发中通常指页面。
简单路由:
// 路由 class NewRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("New Route"), ), body: const Center( child: Text("新页面"), )); } } // 导航代码 TextButton( onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (context) { return NewRoute(); })); }, child: const Text("跳转新路由") )
MaterialPageRoute
MaterialPageRoute
继承PageRoute
类。PageRoute
定义路由构建及切换时过度动画相关接口及属性。
MaterialPageRoute({ WidgetBuilder builder, // 回调函数。构建路由页面的具体内容,返回值是一个widget RouteSettings settings, // 包含路由的配置信息,如路由名称、是否初始路由 bool maintainState = true, // 表示原来的路由是否被保存。如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为 false bool fullscreenDialog = false, // 表示新的路由页面是否是一个全屏的模态对话框 })
Navigator
Navigator
是一个路由管理的组件。
- Future push(BuildContext context, Route route) :将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的返回数据。
- bool pop(BuildContext context, [ result ]): 将栈顶路由出栈,result 为页面关闭时返回给上一个页面的数据。
路由传值
// 新页面 class TipRoute extends StatelessWidget { final String text; const TipRoute({super.key, required this.text}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("提示"), ), body: Padding( padding: const EdgeInsets.all(18), child: Center( child: Column( children: <Widget>[ Text(text), ElevatedButton( onPressed: () => Navigator.pop(context, "返回值是我"), // 弹出时,在这里返回参数 child: const Text("返回"), ) ], ), ), ), ); } } class RouterTestRoute extends StatelessWidget { const RouterTestRoute({super.key}); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( onPressed: () async { var result = await Navigator.push(context, // 异步等待返回信息 MaterialPageRoute(builder: (context) { return const TipRoute(text: "传入123"); // 构造参数 })); debugPrint("$result"); }, child: const Text("打开新页面"), ), ); } }
命名路由
使用路由表
进行命名路由管理。
// 1. 路由表 Map<String, WidgeBuilder> routes; // 2. 注册路由表 MaterialApp ( // ..... routes: { "new_paget" : (context) => NewRoute(), "/" : (context) => MyHome() // 注册路由首页 } ); // 3. 打开路由 Navigator.pushNamed(context, "new_page"); // 4. 获取路由参数 var args = ModalRoute.of(context).settings.arguments;
路由钩子
MaterialApp( // .......... onGenerateRoute: (RouteSettings settings) { // 仅对命名路由生效 return MaterialPageRoute(builder: (context) { String routeName = settings.name; // 判读 return xxx(); }); } )
包管理
flutter
的项目默认配置为: pubspec.yaml
可以在https://pub.dev
查找依赖。
name: study1 # 项目名称 description: A new Flutter project. # 项目描述 publish_to: 'none' # 阻止使用 flutter pub publish version: 1.0.0+1 # 版本 environment: sdk: '>=3.0.3 <4.0.0' dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 flutter: # flutter相关配置 uses-material-design: true
资源管理
Flutter APP 安装包中会包含代码和 assets(资源)两部分。Assets 是会打包到程序安装包中的,可在运行时访问。
使用pubspec.yaml
文件来管理应用程序所需的资源。
flutter: assets: - assets/icon1.png # flutter会加载相邻子目录的同命文件。如果有资源 assets/a/icon1.png,它也会被加载 - assets/icon2.png
加载assets
-
加载文本资源
- 使用
rootBundle
访问。位于package:flutter/services.dart
- 使用
DefaultAssetBundle
:用于运行时动态替换不同的AssetBundle
- 使用
-
加载图片资源
-
声明分辨率相关的图片
- .../2.0x/icon.png .../3.0x/icon.png
-
加载图片
AssetImage("assets/background.png"); Image.asset("assets/background.png"); // 返回widget // 使用默认asset bundle,内部会自动处理分辨率
-
调试
-
debugger()
debugger(when a > 20) -
debug
debugPrint
flutter logs
-
assert
异常捕获
dart
为单线程,出现异常后不会导致进程的退出。
dart有两个队列:
- 微任务队列: 优先级别高。主要来源于dart内部。
- 事件队列:所有的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等。,如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久,对于GUI应用来说最直观的表现就是比较卡。可以在事件队列中插入新的微任务和事件任务。
-
flutter
框架异常捕获:flutter
框架对build
添加了异常捕获。所以build
出错后会弹出错误提示:@override void performRebuild() { ... try { //执行build方法 built = build(); } catch (e, stack) { // 有异常时则弹出错误提示 built = ErrorWidget.builder(_debugReportException('building $this', e, stack)); } ... } static void reportError(FlutterErrorDetails details) { ... if (onError != null) onError(details); //调用了onError回调 } // 自己上报异常,只需要提供一个自定义的错误处理回调即可 FlutterError.onError = (FlutterErrorDetails details) { reportError(details); }; -
其他异常捕获与日志收集
-
同步异常:使用
try
、catch
-
异步异常:使用
runZone()
的handleUncaughtError
runZoned( () => runApp(MyApp()), zoneSpecification: ZoneSpecification( // 拦截print print: (Zone self, ZoneDelegate parent, Zone zone, String line) { parent.print(zone, "Interceptor: $line"); }, // 拦截未处理的异步错误 handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, Object error, StackTrace stackTrace) { parent.print(zone, '${error.toString()} $stackTrace'); }, ), );
-
布局类组件
布局原理和约束
尺寸限制类用于限制容器大小。比如ConstrainedBox
、SizedBox
、UnconstrainedBox
、AspectRatio
等。
布局模型
- 基于
RenderBox
的盒模型布局 - 基于
Sliver
(RenderSliver
)按需加载列表布局
两者细节略有不同,但大体一致。布局流程如下:
- 上层向下层组件传递约束条件(
BoxConstraints
) - 下层确定自己的大小,然后通知上层。下层必须符合上层的约束
- 上层组件确定下层组件相对于自身的偏移和确定自身的大小
ConstrainedBox
用于对子组件添加约束。如果想让子组件最小高度为80,则使用const ConstraintsBox(minHeight: 80.0)
@override Widget build(BuildContext context) { Widget redBox = DecoratedBox(decoration: BoxDecoration(color: Colors.red)); return Column( children: [ ConstrainedBox( constraints: BoxConstraints( minWidth: 50.0, minHeight: 50.0 ), child: Container( height: 5.0, // 即使指定高度为5,由于约束,高度实际为50 child: redBox, ), ) ], );
当多重限制时,取父组件数值较大的值。
SizedBox
用于给子元素指定固定的宽高。
SizedBox( width: 20.0, height: 20.0, child: redBox, ) // SizedBox由BoxConstraints实现
UnconstrainedBox
UnconstrainedBox
可以”去除“约束。
ConstrainedBox( constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0), child: UnconstrainedBox( // '去除'限制 child: ConstrainedBox( constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0), child: redBox, ), ), )
实际上,子组件不可能违反父组件的约束(如果违反父组件的约束,则无法绘制)。
线性布局
沿水平或垂直排列子组件的布局。使用Row
和Column
来实现线性布局(两者均继承Flex
)。
- 主轴: 布局沿水平方向 主轴对齐
MainAxisAlignment
- 纵轴:布局沿垂直方向 纵轴对齐
CrossAxisAlignment
Row
的定义:
Row({ // ... TextDirection textDirection, // 文字方向。默认为从左往右 MainAxisSize mainAxisSzie = MainAxisSize.max, // Row在主轴方向占用的空间。默认是max,即尽可能多,不管子组件宽度,Row的宽度始终等于水平方向的最大宽度。min表示尽可能少的占用空间,此时Row的宽度等于所有子组件的宽度 MainAxisAligment mainAxisAligment = MainAxisAligment.start //表示子组件在Row内的水平空间对齐方式。只有size为max时才有意义。start表示沿textDirection的初始方向对齐。center表示居中对齐 VerticalDirection verticalDirection = VerticalDirection.down // 表示Row纵轴的对齐方向,默认为从上到下 CrossAxisAligment crossAxisAligment = CrossAxisAligment.center, // 表示子组件在纵轴的对齐方式。Row的高度为子组件最高的高度。当verticalDirection为down时,start表示顶部对齐。当verticalDirection为up时,start表示底部对齐 List<Widget> children = const <Widget>[], })
Column
的定义同Row
当Row
里面嵌套Row
,或者Column
里面嵌套Column
时,只有外面的Row
或Column
会占用尽可能大的空间。里面的为实际大小。可以使用Expanded
组件抵销此影响
Flex 弹性布局
Flex
组件可以沿着水平或垂直方向排列子组件。Row
和Column
都继承Flex
,参数基本相同。
// Flex定义 Flex({ // ........ required this.direction, // 默认为水平方向 children: const <Widget>[] })
Expanded
只能作为Flex
的孩子。可以按照比例扩展子组件占用的空间。
// Expanded定义 Expanded({ int flex = 1, child })
流式布局
Wrap
用于折行
// wrap定义 Wrap({ // ... direction = Axis.horizontal, alignment = start, spacing = 0.0, // 主轴方向子Widget的间距 runAlignment = start, // 纵轴方向的对齐方式 runSpacing = 0.0, // 纵轴方向的间距 crossAxisAlignment = start, textDirection, certicalDirection = down, children })
Flow
层叠布局
层叠布局与Web中的绝对定位相似。子组件可以根据父容器的四个角位置来确定自身位置。
// Stack定义 Stack({ alignment = topStart, // 决定没有定位 或部分定位的子组件 textDirection, fit = losse, // 没有定位的子组件任何适应Stack的大小。loose表示使用子组件的大小,expand表示扩展到Stack的大小 clipBehavior = hardEdge, // 决定超出stack的如何裁剪。hardEdge表示直接裁剪,不使用抗抗锯齿 children }); // Positioned Positioned({ left, // 表示距离stack左、上、右、底四边的距离 top, right, bottom, width, // left right width 只能使用2个,其余一个会计算出来 height, // 用于配合top bottom的 child })
对齐和相对定位Align
// Align定义 Align({ alignment: center, // 表示子组件在父组件的启始位置 widthFactor, // 用于确定Align组件本身宽高的属性:乘以子组件的宽高,就是Align组件的宽高 heightFactor, child })
容器类组件
容器类组件和布局类Widget都作用于其子Widget,不同的是:
- 布局类子组件为children,容器类子组件为child
- 布局类组件对其子组件进行排列,容器类组件对其子组件进行包装。
Padding填充
// Padding Padding({ //... padding, // 定义填充的方法 child })
DecoratedBox 装饰容器
// DecoratedBox DecoratedBox({ decoration, // 代表要绘制的装饰 position = DecorationPosition.background, // 决定在哪里装饰,默认为背景。foreground为前景 child })
// BoxDecoration定义 BoxDecoration({ color, // 颜色 image, // 图片 border, // 边框 borderRadius, // 圆角 boxShadow, // 阴影 gradient, // 渐变 backgroundBlendMode, // 背景混合模式 shape = BoxShape.rectangle // 形状 }); // 示例 Widget build(BuildContext context) { return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient(colors: [Colors.red, Colors.orange.shade200]), // 背景渐变 boxShadow: [ BoxShadow( color: Colors.black87, offset: Offset(2.0, 2.0), blurRadius: 4.0 ) ] ), child: Padding( padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0), child: Text("Login", style: TextStyle(color: Colors.white)), ), ); }
Tranform变换
Transform
可以实现子组件绘制时的特效。Matrix4
时一个4D矩阵。
// 变换 Widget build(BuildContext context) { return Container( color: Colors.black, child: Transform( alignment: Alignment.topLeft, // 相对于原点的对齐方式 transform: Matrix4.skewY(0.3), // 沿Y轴倾斜0.3的弧度 child: Container( padding: const EdgeInsets.all(8.0), color: Colors.deepPurple, child: Text("dsaas"), ), ), ); }
// 平移 @override Widget build(BuildContext context) { return DecoratedBox( decoration: BoxDecoration( color: Colors.red, ), child: Transform.translate( offset: Offset(-20.0, -5.0), child: Text('hello'), ), ); }
// 旋转 Widget build(BuildContext context) { return DecoratedBox( decoration: BoxDecoration( color: Colors.red, ), child: Transform.rotate( angle: pi / 2 , child: Text('hello'), ), );
// 缩放 Widget build(BuildContext context) { return DecoratedBox( decoration: BoxDecoration( color: Colors.red, ), child: Transform.scale( scale: 2.0, child: Text('hello'), ), ); }
Container容器
Container
组件是DecpratedBox
、ConstrainedBox
、Transform
、Padding
、Align
等组件的一个多功能容器。
// container定义 Container({ this.alignment, this.padding, color, decoration, foregroundDecoration, width, height, constraints, margin, transform, child })
Clip裁剪
裁剪Widget | 默认行为 |
---|---|
ClipOval | 子组件为正方形时裁剪为内贴圆形;为矩形时裁剪为内贴椭圆 |
ClipRRect | 将子组件裁剪为圆角矩形 |
ClipRect | 默认裁剪掉子组件布局外的绘制内容 |
ClipPath | 按照自定义路径裁剪 |
Widget build(BuildContext context) { Widget avatar = Image.network('xxx', width: 60); return Column( children: [ avatar, // 不裁剪 ClipOval(child: avatar,), // 裁剪为圆形 ClipRRect( borderRadius: BorderRadius.circular(5.0), child: avatar, ), // 裁剪为圆角矩形 ], ); }
空间适配 FittedBox
遇到子组件大小超过父组件时,父组件会将自身最大空间作为约束传递给子组件。当子组件原始大小超过父组件的约束区域,则需要进行一些缩放、裁剪或其他处理。不同组件的处理方式是特定的。Text
组件默认为换行。如果我们想改变此行为,需要使用FittedBox
。本质为子组件如何适配父组件的空间。
// FittedBox定义 FittedBox({ fit = BoxFit.contain, // 适配方式 alignment = Alignment.center, // 对齐方式 clipBehavior = Clip.none, // 是否裁剪 child })
页面骨架 Scaffold
可滚动组件
简介
Flutter
有两种布局模型:
- 基于
RenderBox
的盒模型布局 - 基于
Sliver
按需加载列表布局
Sliver
模型是按需加载模型,只有在viewport中才会加载。ListView
和GridView
都是。
Scrollable
:用于处理滑动手势、确定滑动偏移ViewPort
:显示的视窗Sliver
:视窗里显示的元素
// Scrollable Scrollable({ // ... axisDirection = AxisDirection.down, // 滚动方向 controller, // 控制滚动位置及监听滚动事件 physics, // 定义如何响应用户的操作 viewportBuilder // 构建Viewport的回调 }); // Viewport Viewport({ axisDirection - AxisDirection.down, crossAxisDirection, anchor = 0.0, offset, // 用户的滚动偏移 center, cacheExtent, // 预渲染区域 cacheExtentStyle = CacheExtentStyle.pixel, // 描述cacheExtent的含义 clipBehavior = Clip.hardEdge, slivers });
SingleChildScrollView
只能接收一个子组件。它没有延迟加载模型,所以只应当在期望的内容不会超出屏幕太多时使用。
SingleChildScrollView({ scrollDirection = Axis.vertical, //滚动方向,默认是垂直 reverse = false, padding, primary, physics, controllrt, child })
Widget build(BuildContext context) { String s = "ahsdksadhksahdkashdskahdakhdak"; return Scrollbar( // 显示进度条 child: SingleChildScrollView( padding: EdgeInsets.all(16.0), child: Center( child: Column( children: s.split("").map((e)=> Text(e, textScaleFactor: 2.0,)).toList(), ), ), ), ); }
ListView
ListView
可以沿一个方向线性排列所有子组件,也支持懒加载。
// ListView定义 ListView({ // ... scrollDirection = Axis.vertical, // 滚动方向 reverse = false, controller, bool primary, physics, padding, itemExtent, // 参数不为null时,强制children的‘长度’为此值。指定它会有更好的性能,因为不需要构建 子组件时再去计算 prototypeItem, // 如果知道列表每一项长度都相同,但不知道具体长度,可以指定它。滚动组件会在layout时,计算它的长度。所以和itemExtent性能一样好,但两者互斥。 shrinkWrap = false, // 是否根据使所有子组件长度设置ListView的长度。默认会尽可能占用更多的空间。当滚动方向无边界时,必须设置为true。 addAutomaticKeepAlives = true, addRepaintBoundaries = true, // 是否包含在repaint组件中,此组件重绘开销特别小。 cacheExtent, // 预渲染区域长度 children // 适合少量的子组件数量。否则,应该使用ListView.builder })
Widget build(BuildContext context) { List<Text> texts = "ahsdksadhksahdkashdskahdasssssssssssssssssssssssssssssssssssssskhdak".split("") .map((e) => Text(e)).toList(); return Scrollbar(child:ListView( shrinkWrap: true, padding: const EdgeInsets.all(10.0), children: texts, )); } // 默认为懒加载,但使用children仍然要提前创建Widget
ListViewBuilder
// ListView.builder ListView.builder({ // ... IndexedWidgetBuilder itemBuilder, // 列表的构建项,返回具体index项时的子组件 int itemCount, // 列表项的数量 // .... })
Widget build(BuildContext context) { return Scrollbar( child: ListView.builder( itemCount: 50, itemExtent: 50, itemBuilder: (BuildContext context, int index) { return ListTile(title: Text("$index")); }, ), );
ListView.separated
可以在列表项之间添加一个分割组件
Widget build(BuildContext context) { Widget divider1 = Divider(color: Colors.blue); Widget divider2 = Divider(color: Colors.red); return Scrollbar( child: ListView.separated( itemCount: 100, itemBuilder: (context, index) { return ListTile(title: Text("$index")); }, separatorBuilder: (context, index) { return index%2 == 0? divider1 : divider2; }, ), );
滚动监听及控制
可以使用ScrollController
来控制可滚动组件的滚动位置。
ScrollController
// ScrollController ScrollController({ initialScrollOffest = 0.0,// 初始滚动位置 keepScrollOffset = true }); offset // 可滚动组件的当前位置 jumpTo() // 无动画 animateTo() // 动画滚动
_MyScrollState createState() => _MyScrollState(); } class _MyScrollState extends State<MyScroll> { ScrollController _controller = ScrollController(); bool showBtn = false; @override void initState() { super.initState(); // 监听滚动事件,打印滚动位置 _controller.addListener(() { print(_controller.offset); if (_controller.offset < 1000 && showBtn) { setState(() { showBtn = false; }); } else if (_controller.offset >= 1000 && showBtn == false) { setState(() { showBtn = true; }); } }); } @override void dispose() { // 销毁 _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("滚动控制"),), body: Scrollbar( child: ListView.builder( itemCount: 150, itemExtent: 50.0, controller: _controller, itemBuilder: (cobtext, index) { return ListTile(title: Text("现在是$index"),); }, ), ), floatingActionButton: !showBtn ? null : FloatingActionButton( child: Icon(Icons.arrow_upward), onPressed: () { _controller.animateTo( .0, duration: Duration(seconds: 1), curve: Curves.ease, ); }, ), ) ; } }
PageStorage
是一个保存页面相关数据 的组件,并不会参与UI。每次滚动结束,可滚动组件都将滚动位置存储至PostStorage
中。
bitsdojo_window
功能:自定义窗口大小及位置。
使用:flutter pub add bitsdojo_window
// main.dart import 'package:flutter/material.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; void main() { // 处理原生与flutter通信 WidgetsFlutterBinding.ensureInitialized(); runApp(const MyApp()); doWhenWindowReady(() { const initialSize = Size(800, 600); appWindow.minSize = initialSize; // 最小窗口 appWindow.size = initialSize; // 默认窗口 appWindow.maxSize = const Size(800, 800); // 最大窗口 appWindow.alignment = Alignment.topLeft; // 打开位置 appWindow.show(); }); }
自定义窗口导航栏:
https://pub.dev/packages/bitsdojo_window#for-linux-apps
import 'package:flutter/material.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; class WindowButtons extends StatefulWidget { const WindowButtons({super.key}); @override State<WindowButtons> createState() => _WindowButtonsState(); } class _WindowButtonsState extends State<WindowButtons> { final buttonColors = WindowButtonColors( iconNormal: Colors.grey[600], mouseOver: Colors.grey[400], mouseDown: Colors.grey[400], iconMouseOver: Colors.grey[600], iconMouseDown: Colors.grey[600] ); void maximizeOrRestore() { appWindow.maximizeOrRestore(); } @override Widget build(BuildContext context) { return Row(children: [ MouseRegion( cursor: SystemMouseCursors.click, child: MinimizeWindowButton(colors: buttonColors), ), MouseRegion( cursor: SystemMouseCursors.click, child: appWindow.isMaximized ? RestoreWindowButton( colors: buttonColors, onPressed: maximizeOrRestore, ) : MaximizeWindowButton( colors: buttonColors, onPressed: maximizeOrRestore, ), ), MouseRegion( cursor: SystemMouseCursors.click, child: CloseWindowButton(colors: buttonColors), ) ],); } }
本文作者:nsfoxer
本文链接:https://www.cnblogs.com/nsfoxer/p/16403538.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)