flutter
1.intro
flutter 本质是 widget 树
Text
: 格式文本Row
: 水平布局Column
: 垂直布局Stack
: 线性布局,Positioned
定位Container
: 矩阵,BoxDecoration
装饰Container
Expanded
: 填充剩余空间Navigator
: 管理Widget
栈Scaffold
: Material 的一种布局结构GestureDetector
: 识别用户手势,onTap
回调StatefulWidget
: 有状态控件,实现createState
返回一个继承自State<WidgetType
的状态型控件State
: 可以通过给action
来分层,Widget 都存在 State 里,通过 AcationWidget 修改 State 里的值,达到渲染 DisplayWdiget 的目的,一个负责渲染/发送 Action,一个负责渲染数据
2.layout
- 数据结构为树
- 所有 layout 本质都是
widget
- 所有布局
widget
都含有child
和children
属性child
: 包含一个子widget
children
: 含有多个子项,布局子项,直至最后布局子项包含一个可见Widget
- 纵向用 Column 横向用 Row 可嵌套
AxisAligment
: 表示对齐Expanded
:可以自适应布局高宽flex
: 弹性系数
Container
: 可扩展padding,margins,borders,background color
等其他装饰元素decoration
GridView
: 可以做滚动网格ListView
: 滚动列表Stack
: 栈,先进后出,也就是后面的Widget
盖到前一个Widget
上,形成一个Widget
栈Card
是Material
中的一个组件EdgeInsets
: 间距
自适应: 根据设备发生变化。
响应式: 根据屏幕大小发生变化。
自适应应用
- Single Child
Align
: 子级内部对齐AspectRatio
: 为子级指定比例ConstrainedBox
: 对子级施加最大最小尺寸限制CustomSingleChildLayout
: 代理方法对子级进行定位Expanded,Flexble
:Row,Column
填充剩余空间FractionallySizedBox
: 基于剩余控件比例限定子级大小LayoutBuilder
: 让子级可以基于父级的尺寸重新调整其布局SingleChildScrollView
: 为单一子级添加滚动
- Multi Child
Column,Row,Flex
CustomMultiChildLayout
: 代理多个子级定位Flow
: 类似 ⬆️ListView,GridView,CustomScrollView
: 为所有子级添加滚动Stack
: 基于Stack
边界对多个子级定位Table
: 表格布局Wrap
: 将子级顺序显示在多行/多列内
视觉密度
widget 的疏密程度,紧凑程度等意思,其实就是一个动态的值来自动调整宽高/间距什么的
-
VisualDensity
double densityAmt = enableTouchMode ? 0.0 : -1.0;
// 水平竖直
VisualDensity density = VisualDensity(horizontal: density, vertical: density);
return MaterialApp(
// 应用到 MaterialApp 主题
theme: ThemeData(visualDensity: density),
...
);
// 使用主题的疏密度
VisualDensity density = Theme.of(context).visualDensity;
return Padding(
padding: EdgeInsets.all(Insets.large + density.vertical * 4),
child: ...
);
基于 Context 的布局
-
MediaQuery
: 媒体查询,给了一些分界点ScreenType getFormFactor(BuildContext context) {
// Use .shortestSide to detect device type regardless of orientation
double deviceWidth = MediaQuery.of(context).size.shortestSide;
if (deviceWidth > FormFactor.desktop) return ScreenType.Desktop;
if (deviceWidth > FormFactor.tablet) return ScreenType.Tablet;
if (deviceWidth > FormFactor.handset) return ScreenType.Handset;
return ScreenType.Watch;
}
以下是反转屏幕的一个判断,可以基于媒体查询判断是否反转,从顶传入子,使得整个 UI 做出屏幕翻转的响应
bool isHandset = MediaQuery.of(context).size.width < 600; return Flex( children: [...], direction: isHandset ? Axis.vertical : Axis.horizontal );
layout builder
layout builder 和之前不同的是,提供了一些对象,让你更好的做出判断和使用
Widget foo = LayoutBuilder(builder: (_, constraints, __){ bool useVerticalLayout = constraints.maxWidth < 400.0; return Flex( children: [...], direction: useVerticalLayout ? Axis.vertical : Axis.horizontal ); });
设备细分
// Platform对象判断设备类型 bool get isMobileDevice => !kIsWeb && (Platform.isIOS || Platform.isAndroid); // macos windows bool get isDesktopDevice => !kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux); // browser bool get isMobileDeviceOrWeb => kIsWeb || isMobileDevice; bool get isDesktopDeviceOrWeb => kIsWeb || isDesktopDevice;
input
-
scrollView,ListView
: 自动支持onPointerSingal.PointerScrollEvent
滑轮输入 -
实现自定义滑动:
Listener Widget
的onPointerSingal.PointerScrollEvent
-
FocusableActionDetector
: 在元素外包裹,处理焦点,悬浮,鼠标跟随 -
RawKeyboardListener
: 监听键盘事件 -
Shortcuts
: 直接绑定快捷键 -
RawKeyboard.instance.addListener(_handleKey);
全局监听键盘事件// 单一快捷键是否按下了
static bool isKeyDown(Set keys) {
return
keys.intersection(RawKeyboard.instance.keysPressed).isNotEmpty;
}void _handleKey(event){
if (event is RawKeyDownEvent) {
// 两个 shift 组合键
bool isShiftDown = isKeyDown({
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
});
if (isShiftDown && event.logicalKey == LogicalKeyboardKey.keyN) {
_createNewItem();
}
}
}
鼠标
MouseRegion return MouseRegion( // 进入 onEnter: (_) => setState(() => _isMouseOver = true), // 离开 onExit: (_) => setState(() => _isMouseOver = false), // 悬停 onHover: (PointerHoverEvent e) => print(e.localPosition), child: ..., );
用户期望
-
ScrollBar
: 根据平台切换状态// 不同设备的输入是不同的
static bool get isMultiSelectModifierDown {
bool isDown = false;
// 使用 DeviceOS 来判断设备
if (DeviceOS.isMacOS) {
isDown = isKeyDown([LogicalKeyboardKey.metaLeft, LogicalKeyboardKey.metaRight]);
} else {
isDown = isKeyDown([LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.controlRight]);
}
return isDown;
} -
SelectableText
: 文字是否可以被选中 -
SelectableText.rich(TexSpan)
: 富文本选中 -
Tooltip
: 悬停
布局约束
首先,上层 widget 向下层 widget 传递约束条件。
然后,下层 widget 向上层 widget 传递大小信息。
最后,上层 widget 决定下层 widget 的位置。
长宽计算更像是 子向父报备,父汇集各个子然后计算,在向父父汇报
ConstrainedBox
: 从父级接收约束施加给子级Center( child: ConstrainedBox())
: 允许控制UnconstrainedBox
: 允许子级改变为任意大小OverflowBox
: 可溢出,需要在本级做出限制
限制 外到内,外告知内可小于外宽松约束,外告知内必须变成某个大小严格约束
添加互动
StatefulWidget
: 和 React 一样- 其实就是状态管理,类似 React 的 State,这个 State 可以是自己的 State,也可以是自己的 Props,从父级 State 传入过来,只不过这里依靠构造函数显示声明
- 当然可以从父级传回调函数在子级改变父级状态
GestureDetector
: 添加其他互动,什么是互动啊,就是用户的行为能改变数据(切换图标也算是数据变动)的地方就是有互动,理解了吧
指定资源
flutter: assets: - assets/my_icon.png - assets/background.png flutter: assets: - directory/ - directory/subdirectory/ // 直接读文件了 Future<String> loadAsset() async { return await rootBundle.loadString('assets/config.json'); } // 不同分辨率的图片,可FLutter可以对应不同的设备自动切换 .../my_icon.png .../2.0x/my_icon.png .../3.0x/my_icon.png // 使用别的包的图像,也要声明出来 flutter: assets: - packages/fancy_backgrounds/backgrounds/background1.png
注意: 读取 android/ios 图片插件和方式不同,文件夹内有 android
,ios
两个文件夹,需要添加各自的插件,图标
,预加载图
也是如此
路由
安卓启动深度路由 AndroidManifest.xml
<!-- Deep linking --> <meta-data android:name="flutter_deeplinking_enabled" android:value="true" /> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="flutterbooksample.com" /> <data android:scheme="https" /> </intent-filter>
配合 adb 测试
adb shell am start -a android.intent.action.VIEW \ -c android.intent.category.BROWSABLE \ -d "http://flutterbooksample.com/book/1"
IOS Info.plist
<key>FlutterDeepLinkingEnabled</key> <true/> <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLName</key> <string>flutterbooksample.com</string> <key>CFBundleURLSchemes</key> <array> <string>customscheme</string> </array> </dict> </array>
配合 xsrun
xshell
xcrun simctl openurl booted customscheme://flutterbooksample.com/book/1
动画
pass
http
import 'package:http/http.dart' as http; var url = Uri.parse('https://example.com/whatsit/create'); var response = await http.post(url, body: {'name': 'doodle', 'color': 'blue'}); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); print(await http.read('https://example.com/foobar.txt'));
安卓需要在 AndroidManifest.xml
中添加需要访问的网络权限
<manifest xmlns:android...> ... <uses-permission android:name="android.permission.INTERNET" /> <application ... </manifest>
json
class User { final String name; final String email; User(this.name, this.email); // 解析json User.fromJson(Map<String, dynamic> json) : name = json['name'], email = json['email']; // 转成json Map<String, dynamic> toJson() => { 'name': name, 'email': email, }; }
或者
dependencies: # Your other regular dependencies here json_annotation: <latest_version> dev_dependencies: # Your other dev_dependencies here build_runner: <latest_version> json_serializable: <latest_version>
引入依赖,使用注解
@JsonSerializable() class User { User(this.name, this.email); String name; String email; /// A necessary factory constructor for creating a new User instance /// from a map. Pass the map to the generated `_$UserFromJson()` constructor. /// The constructor is named after the source class, in this case, User. factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); /// `toJson` is the convention for a class to declare support for serialization /// to JSON. The implementation simply calls the private, generated /// helper method `_$UserToJson`. Map<String, dynamic> toJson() => _$UserToJson(this); }
@JsonKey
: 指定 json 名称@JsonSerializable(fieldRename: FieldRename.snake)
: json 风格@JsonKey(defaultValue: false)
,@JsonKey(required: true)
,@JsonKey(ignore: true)
: json 校验flutter pub run build_runner build
使用这个命令配合注解
生成,xxx.g.dartflutter pub run build_runner watch
持续生成@JsonSerializable(explicitToJson: true)
: 子类一起 JSON
i18n
dependencies: flutter: sdk: flutter flutter_localizations: # Add this line sdk: flutter # Add this line return const MaterialApp( title: 'Localizations Sample App', localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: [ Locale('en', ''), // English, no country code Locale('es', ''), // Spanish, no country code ], home: MyHomePage(), );
添加自定义国际化文件
dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter intl: ^0.17.0 # Add this line # The following section is specific to Flutter. flutter: generate: true # Add this line
然后添加 arb 配置
arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart
arb 文件
{ "helloWorld": "Hello World!", "@helloWorld": { "description": "The conventional newborn programmer greeting" } }
导入本地配置
localizationsDelegates: [ AppLocalizations.delegate, // Add this line `` ],
代码中使用
Text(AppLocalizations.of(context)!.helloWorld);
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于