flutter基础学习.md

Flutter基础

学习来源:《Flutter实战·第二版》

介绍

Flutter 是 Google 推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。

技术特点:

  1. 跨平台自绘引擎。底层使用 Skia 作为其 2D 渲染引擎。
  2. 高性能;
  3. dart开发; 开发效率高、 高性能、 快速内存分配、 类型安全和空安全。

Flutter框架图:

1-1.82c25693

安装

# 使用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; // 错误

dynamicObject

// 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);

finalconst

// 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元素的配置信息。

StatelessWidgetStatefulWidget继承了Widget

flutter 框架的的处理流程是这样的:

  1. 根据widget树生成Element树。Element树的节点都继承Element类;
  2. 根据Element树生成Render树,渲染树的节点都继承RenderObject类;
  3. 根据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信息可以:

  1. widget被构建时同步读取;
  2. 调用setState通知重新构建widgte树。

State有两个常用属性:

  1. widget:表示与该 State 实例关联的 widget 实例,由Flutter 框架动态设置。当在重新构建时,如果 widget 被修改了,Flutter 框架会动态设置State. widget 为新的 widget 实例。
  2. context:StatefulWidget对应的 BuildContext,作用同StatelessWidget 的BuildContext。

2-5.a59bef97

状态管理

管理状态的最常见的方法:

  • 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是一个路由管理的组件。

  1. Future push(BuildContext context, Route route) :将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的返回数据。
  2. 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

  1. 加载文本资源

    • 使用rootBundle访问。位于package:flutter/services.dart
    • 使用DefaultAssetBundle:用于运行时动态替换不同的AssetBundle
  2. 加载图片资源

    • 声明分辨率相关的图片

      • .../2.0x/icon.png .../3.0x/icon.png
    • 加载图片

      AssetImage("assets/background.png");
      Image.asset("assets/background.png"); // 返回widget
      // 使用默认asset bundle,内部会自动处理分辨率

调试

  1. debugger()

    debugger(when a > 20)
  2. debug debugPrint flutter logs

  3. assert

异常捕获

dart为单线程,出现异常后不会导致进程的退出。

图2-21

dart有两个队列:

  • 微任务队列: 优先级别高。主要来源于dart内部。
  • 事件队列:所有的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等。,如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久,对于GUI应用来说最直观的表现就是比较卡。可以在事件队列中插入新的微任务和事件任务。
  1. 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);
    };
    1. 其他异常捕获与日志收集

    2. 同步异常:使用trycatch

    3. 异步异常:使用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');
      },
      ),
      );

布局类组件

布局原理和约束

尺寸限制类用于限制容器大小。比如ConstrainedBoxSizedBoxUnconstrainedBoxAspectRatio等。

布局模型

  • 基于RenderBox的盒模型布局
  • 基于SliverRenderSliver)按需加载列表布局

两者细节略有不同,但大体一致。布局流程如下:

  1. 上层向下层组件传递约束条件(BoxConstraints
  2. 下层确定自己的大小,然后通知上层。下层必须符合上层的约束
  3. 上层组件确定下层组件相对于自身的偏移和确定自身的大小

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,
),
),
)

实际上,子组件不可能违反父组件的约束(如果违反父组件的约束,则无法绘制)。

线性布局

沿水平或垂直排列子组件的布局。使用RowColumn来实现线性布局(两者均继承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时,只有外面的RowColumn会占用尽可能大的空间。里面的为实际大小。可以使用Expanded组件抵销此影响

Flex 弹性布局

Flex组件可以沿着水平或垂直方向排列子组件。RowColumn都继承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,不同的是:

  1. 布局类子组件为children,容器类子组件为child
  2. 布局类组件对其子组件进行排列,容器类组件对其子组件进行包装。

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组件是DecpratedBoxConstrainedBoxTransformPaddingAlign等组件的一个多功能容器。

// 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中才会加载。ListViewGridView都是。

  1. Scrollable:用于处理滑动手势、确定滑动偏移
  2. ViewPort:显示的视窗
  3. 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 中国大陆许可协议进行许可。

posted @   nsfoxer  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起