行走日记 - Flutter
type: channel
link: http://blog.baikeyang.com/tag/Flutter/
items: Flutter根据应用状态判断APP是否后台运行
items: Flutter获取屏幕大小及像素密度
items: Flutter监听TV遥控器按键
items: 在Dart中常见的类型转换
items: [Flutter]Flutter 常用命令行
items: [Flutter]Decoration装饰器的基本使用
items: [Flutter]首页导航基本实现方法
items: [Flutter]APP拍照功能实现
items: [Flutter]初识动画Animation
items: [Flutter]解决ERROR:D8: Cannot fit requested classes in a single dex file的问题

Flutter根据应用状态判断APP是否后台运行
type: item
date: 2024-12-18T12:29:00+00:00
description: 举例，比如Bilibili的应用可以设置后台停止播放的功能。那么如何在Flutter中实现呢？Flutter根据应用状态判断APP是否后台运行，通过以下代码可以实现：SystemChannels.lifecycle.setMessageHandler((msg) async { print('SystemChannels====================$msg'); switch(msg) { case "AppLifecycleState.paused": print(msg); print("当app被切到后台暂停时，暂停视频播放"); break; case "AppLifecycleState.resumed": print(msg); print("当app从后台回到前台时，继续播放视频"); break; case "AppLifecycleState.inactive": print(msg); print("AppLifecycleState.inactive"); // assetsAudioPlayer.stop(); break; default: break; } });

Flutter获取屏幕大小及像素密度
type: item
date: 2024-10-18T03:16:00+00:00
description: 在Flutter中获取屏幕尺寸信息是通过MediaQuery.of(context)来获取。属性说明size一个包含宽度和高度的对象，单位是dp（乘以密度就是你设备的像素）devicePixelRatio密度（像素比）textScaleFactor每个逻辑像素的字体像素数platformBrightness主机平台当前亮度模式viewInsets完全被系统UI(通常是设备的键盘)遮挡的显示部分padding我们通常取上边刘海高度和下边导航高度alwaysUse24HourFormat格式化时间时是否使用24小时格式accessibleNavigation用户是否使用TalkBack或VoiceOver等辅助功能服务与应用程序进行交互invertColors设备是否反转平台的颜色disableAnimations平台是否要求尽可能禁用或减少动画boldText平台是否请求使用粗体字体重绘制文本具体代码如下：//屏幕大小 Size mSize = MediaQuery.of(context).size; //密度 double mRatio = MediaQuery.of(context).devicePixelRatio; //设备像素 double width = mSize.width * mRatio; double height = mSize.height * mRatio; // 上下边距 （主要用于 刘海 和 内置导航键） double topPadding = MediaQuery.of(context).padding.top; double bottomPadding = MediaQuery.of(context).padding.bottom; // 每个逻辑像素的字体像素数 double textScaleFactor = MediaQuery.of(context).textScaleFactor; // 主机平台当前亮度模式 Brightness platformBrightness = MediaQuery.of(context).platformBrightness; // 遮挡的显示部分 EdgeInsets viewInsets = MediaQuery.of(context).viewInsets; // 上边刘海高度和下边导航高度 EdgeInsets padding = MediaQuery.of(context).padding; // 格式化时间时是否使用24小时格式 bool alwaysUse24HourFormat = MediaQuery.of(context).alwaysUse24HourFormat; // 用户是否使用TalkBack或VoiceOver等辅助功能服务与应用程序进行交互 bool accessibleNavigation = MediaQuery.of(context).accessibleNavigation; // 设备是否反转平台的颜色 bool invertColors = MediaQuery.of(context).invertColors; // 平台是否要求尽可能禁用或减少动画 bool disableAnimations = MediaQuery.of(context).disableAnimations; // 平台是否请求使用粗体字体重绘制文本 bool boldText = MediaQuery.of(context).boldText;

Flutter监听TV遥控器按键
type: item
date: 2023-12-12T07:38:00+00:00
description: 如果使用FLutter开发TV电视端的APP，如果正确的根据按钮指令去电视上操作呢？下面就是关于监听遥控器的有些相关代码： import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class HomeIndexPage extends StatefulWidget { const HomeIndexPage({super.key}); @override State<HomeIndexPage> createState() => _HomeIndexPageState(); } class _HomeIndexPageState extends State<HomeIndexPage> { final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); @override Widget build(BuildContext context) { return RawKeyboardListener( focusNode: FocusNode(), onKey: (value) { //debugPrint("遥控器：${value.data}"); if (value.data is RawKeyEventDataAndroid) { var key = value.data as RawKeyEventDataAndroid; if (key.flags == 520) { //上19 下20 左21 右22 // 8是遥控器按下，520是遥控器 弹起 if (key.keyCode == 19) { debugPrint("遥控器：${value.data}"); // controller.previous(); } else if (key.keyCode == 20) { debugPrint("遥控器：${value.data}"); // controller.next(); } else if (key.keyCode == 21) { debugPrint("遥控器：${value.data}"); // controller.showMenu(); } else if (key.keyCode == 22) { debugPrint("遥控器：${value.data}"); // controller.showMenu(show: false); } else if (key.keyCode == 23) { debugPrint("遥控器：${value.data}"); // controller.ok(); _scaffoldKey.currentState?.openDrawer(); } } } }, child: Scaffold( key: _scaffoldKey, drawer:Drawer( child: Text('This is Drawer'), ), body: Center( child: Column( children: [ Text("YouthTV"), TextButton( onPressed: () { _scaffoldKey.currentState?.openDrawer(); }, child: Text("打开侧栏") ), ], ), ), ) ); } } 运行代码后，可以通过点击TV遥控器的确认键、上、下、左、右 等键来操作电视APP。

在Dart中常见的类型转换
type: item
date: 2023-10-16T08:42:00+00:00
description: int --> Stringint n = 3; String str = n.toString(); // "3"String --> intString str = "10"; int n = int.parse(str); // 10String --> doubleString str = "10.6"; double d = doulbe.parse(str); // 10double --> Stringdouble d = 14.96515; String str = d.toStringAdFixed(3); // 保留小数点后三位 14.965int --> doubleint n = 14; double d = n.toDouble(); // 14.0double --> intdouble d = 21.63; int n = d.round(); // 四舍五入 22 String --> MapString str = '{"name":"张三","age":25}'; Map<String, dynamic> res = jsonDecode(str); print(res["name"]); // 张三String --> ListString str = '[20,30,60,90,100]'; List<int> list = <int>[]; for(var data in JsonDecoder().convert(str)) { list.add(data); } list.forEach((d){ print(d); }); // 20 // 30 // 60 // 90 // 100参考资料：https://blog.csdn.net/cxy18137478587/article/details/114382701

[Flutter]Flutter 常用命令行
type: item
date: 2023-05-24T07:45:00+00:00
description: Flutter命令行工具是开发人员（或代表开发人员的IDE）与Flutter交互的方式。对于与Dart相关的命令，可以使用Dart命令行工具。以下是如何使用Flutter工具创建、分析、测试和运行应用程序： flutter create my_app cd my_app flutter analyze flutter test flutter run lib/main.dart要使用Flutter运行pub命令，请执行以下操作：flutter pub get flutter pub outdated flutter pub upgrade要查看Flutter支持的所有命令：flutter --help --verbose要获得Flutter SDK的当前版本，包括其框架、引擎和工具：flutter --versionFlutter 指令:下表展示了可以与Flutter一起使用的命令：命令示例说明analyzeflutter analyze -d <DEVICE_ID>分析项目的Dart源代码，用于代替Dart分析assembleflutter assemble -o 组装和构建Flutter资源attachflutter attach -d <DEVICE_ID>连接到正在运行的应用程序bash-completionflutter bash-completion输出命令行shell完成设置脚本buildflutter build Flutter构建命令channelflutter channel <CHANNEL_NAME>列出或切换Flutter的Channelscleanflutter clean删除build/和.dart_tool/目录configflutter config --build-dir=配置Flutter设置。若要删除设置，请将其配置为空字符串createflutter create 创建一个新的项目custom-devicesflutter custom-devices list添加、删除、列出和重置自定义设备devicesflutter devices -d <DEVICE_ID>列出所有连接的设备doctorflutter doctor显示有关已安装工具的信息downgradeflutter downgrade将Flutter降级到当前通道的最后一个活动版本driveflutter drive为当前项目运行Flutter设备程序测试emulatorsflutter emulators列出、启动和创建模拟器formatflutter format <DIRECTORY|DART_FILE>格式化Flutter源代码。已弃用，请改用dart格式gen-l10nflutter gen-l10n 为Flutter项目生成本地化installflutter install -d <DEVICE_ID>在连接的设备上安装Flutter应用程序logsflutter logs显示运行Flutter应用程序的日志输出precacheflutter precache 填充Flutter工具的二进制工件缓存pubflutter pub <PUB_COMMAND>适用于程序包。使用而不是dart-pubrunflutter run <DART_FILE>运行Flutter程序screenshotflutter screenshot从连接的设备上截取Flutter应用程序的屏幕截图。symbolizeflutter symbolize --input=<STACK_TRACK_FILE>表示来自AOT编译的flutter应用程序的堆栈跟踪testflutter test [<DIRECTORY|DART_FILE>]在此包中运行测试。使用而不是dart测试upgradeflutter upgrade升级你的Flutter版本有关任何命令的其他帮助，请输入flutter help＜command＞进行操作。您还可以获得关于pub命令的详细信息，例如，flutter help pub outdated参考文档：Flutter中文社区

[Flutter]Decoration装饰器的基本使用
type: item
date: 2023-05-17T14:31:00+00:00
description: Decoration是一个装饰类。这个类提供了所有装饰的抽象接口。具体示例请参见BoxDecoration。decoration : 装饰器接口，Decoration是个抽象接口，BoxDecoration是它其一个具体实现类，Decoration装饰器的绘 制最终会由BoxPainter来完成，实现一个Decoration装饰器可以通过createBoxPainter方法来获取一个BoxPainter，最终通过BoxPainter来进行绘制。position: 是个枚举类型，用来控制装饰器在哪里绘制，取值有background跟foreground，前者代表是在背景绘制，后者是在前景绘制BoxDecoration BoxDecoration({ Color? color, DecorationImage? image, BoxBorder? border, BorderRadiusGeometry? borderRadius, List<BoxShadow>? boxShadow, Gradient? gradient, BlendMode? backgroundBlendMode, BoxShape shape = BoxShape.rectangle, })BoxDecoration是Decoration的一个实现类，BoxDecoration提供了背景颜色、边框、阴影、圆角、颜色渐变、形状等等装饰能力，BoxDecoration的定义如下：BoxDecoration BoxDecoration({ Color? color, DecorationImage? image, BoxBorder? border, BorderRadiusGeometry? borderRadius, List<BoxShadow>? boxShadow, Gradient? gradient, BlendMode? backgroundBlendMode, BoxShape shape = BoxShape.rectangle, })color ： 背景颜色image ：背景图片，会绘制在背景颜色上面border ： 边框，会绘制在背景颜色背景图片上面borderRadius ： 边框圆角boxShadow ： 背景阴影gradient ： 填充渐变颜色backgroundBlendMode ： 背景混合模式shape ： 形状下面是Decoration的具体使用实例：import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: '演示Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const TestPage1(), ); } } class TestPage1 extends StatefulWidget { const TestPage1({Key? key}) : super(key: key); @override State<TestPage1> createState() => _TestPage1State(); } class _TestPage1State extends State<TestPage1> { @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: const Text("功能演示页面"), ), body: Container( width: 120, height: 120, // color: Colors.black26, // 背景颜色 注意：Color和decoration二选一 /// alignment元素对齐: /// 1、AlignmentDirectional 排列方向 /// 2、AlignmentGeometry 几何校准 /// 3、Alignment 对齐 /// 其中 Alignment、AlignmentDirectional 均继承自 alignment: Alignm…

[Flutter]首页导航基本实现方法
type: item
date: 2023-05-08T07:43:00+00:00
description: 要想实现首页导航需要哪些材料?ScaffoldBottomNavigationBarPageViewPageController什么是Scaffold widget？Scaffold是一个实现了基本的material design的布局结构class Scaffold extends StatefulWidget { /// Creates a visual scaffold for material design widgets. const Scaffold({ Key key, this.appBar, this.body, this.floatingActionButton, this.floatingActionButtonLocation, this.floatingActionButtonAnimator, this.persistentFooterButtons, this.drawer, this.endDrawer, this.bottomNavigationBar, this.bottomSheet, this.backgroundColor, this.resizeToAvoidBottomPadding = true, this.primary = true, }) : assert(primary != null), super(key: key); ...顶部导航import 'package:flutter/material.dart'; class TabbedAppBarSample extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: DefaultTabController( length: choices.length, child: Scaffold( appBar: AppBar( title: const Text('Tabbed AppBar'), bottom: TabBar( isScrollable: true, tabs: choices.map((Choice choice) { return Tab( text: choice.title, icon: Icon(choice.icon), ); }).toList(), ), ), body: TabBarView( children: choices.map((Choice choice) { return Padding( padding: const EdgeInsets.all(16.0), child: ChoiceCard(choice: choice), ); }).toList(), ), ), ), ); } } class Choice { const Choice({this.title, this.icon}); final String title; final IconData icon; } const List<Choice> choices = const <Choice>[ const Choice(title: 'CAR', icon: Icons.directions_car), const Choice(title: 'BICYCLE', icon: Icons.directions_bike), const Choice(title: 'BOAT', icon: Icons.directions_boat), const Choice(title: 'BUS', icon: Icons.directions_bus), const Choice(title: 'TRAIN', icon: Icons.directions_railway), const Choice(title: 'WALK', icon: Icons.directions_walk), ]; class ChoiceCard extends StatelessWidget { const ChoiceCard({Key key, this.choice}) : super(key: key); final Choice choice; @override Widget build(BuildContext context) { final TextStyle textStyle = Theme.of(context).textTheme.display1; return Card( color: Colors.white, child: Center( chil…

[Flutter]APP拍照功能实现
type: item
date: 2023-05-04T03:28:00+00:00
description: 利用Flutter实现APP拍照功能，效果图具体如下：点击[拍照]按钮 选择【拍照】选项，调用设备镜头采集影像 按下快门后，确定选定该影像照片 选择【从相册选择】选项，从相册选择照片 显示当前选中的照片图像 开发环境：Flutter 3.7.0 • channel stable • https://github.com/flutter/flutter.git Framework • revision b06b8b2710 (3 months ago) • 2023-01-23 16:55:55 -0800 Engine • revision b24591ed32 Tools • Dart 2.19.0 • DevTools 2.20.1引用插件：image_picker: ^0.8.7以下为具体实现代码：main.dart 代码：import 'package:flutter/material.dart'; import 'package:blog/photo_app.dart'; void main() { runApp(const PApp()); } class PApp extends StatefulWidget { const PApp({Key? key}) : super(key: key); @override State<PApp> createState() => _PAppState(); } class _PAppState extends State<PApp> { @override Widget build(BuildContext context) { return const MaterialApp( title: "拍照APP", home: PhotoApp(title: "拍照APP"), ); } } photo_app.dart 代码： import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; class PhotoApp extends StatefulWidget { const PhotoApp({Key? key, required this.title}) : super(key: key); final String title; @override State<PhotoApp> createState() => _PhotoAppState(); } class _PhotoAppState extends State<PhotoApp> { late List<XFile> _imageFileList; void _setImageFileListFromFile(XFile? value) { if(value != null) { _imageFileList.add(value); } } @override void deactivate() { super.deactivate(); } @override void dispose() { super.dispose(); } @override void initState() { _imageFileList = []; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title!), ), body: Center( child: Center( child: Wrap( spacing: 5, runSpacing: 5, children: _genImages(), ), ), ), floatingActionButton: FloatingActionButton( onPressed: _pickImage, tooltip: "选择图片", child: const Icon(Icons.add_a_photo), ), ); } _pickImage() { showModalBottomSheet( context: context, builder: (context) => Container( height: 160, child: Column( children: [ _item("拍照", true), _item("从相册选择", false), ], ), ) ); } _item(String title, boo…

[Flutter]初识动画Animation
type: item
date: 2023-05-01T09:09:00+00:00
description: 初始动画Animation，我们应该了解的知识：在Flutter中有哪些类型的动画？如何使用动画库中的基础类给widget添加动画？如何为动画添加监听器？该什么时候使用AnimatedWidget与AnimatedBuilder?如何使用Hero动画？精心设计的动画会让应用界面感觉更直观、流畅，能改善用户体验。 Flutter的动画支持可以轻松实现各种动画类型。各种widget，特别是Material Design widgets， 都带有在其设计规范中定义的标准动画效果，但也可以自定义这些效果。在Flutter中有哪些类型的动画？在Flutter中动画分为两类：基于tween或基于物理的。补间(Tween)动画：在补间动画中，定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过渡到结束点。基于物理的动画：在基于物理的动画中，运动被模拟为与真实世界的行为相似。例如，当你掷球时，它在何处落地，取决于抛球速度有多快、球有多重、距离地面有多远。 类似地，将连接在弹簧上的球落下（并弹起）与连接到绳子上的球放下的方式也是不同。如何使用动画库中的基础类给widget添加动画？在为widget添加动画之前，先让我们认识下动画的几个朋友：Animation：是Flutter动画库中的一个核心类，它生成指导动画的值；CurvedAnimation：Animation的一个子类，将过程抽象为一个非线性曲线；AnimationController：Animation的一个子类，用来管理Animation；Tween：在正在执行动画的对象所使用的数据范围之间生成值。例如，Tween可生成从红到蓝之间的色值，或者从0到255；Animation在Flutter中，Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类，它拥有其当前值和状态（完成或停止）。其中一个比较常用的Animation类是Animation<double>。Flutter中的Animation对象是一个在一段时间内依次生成一个区间之间值的类。Animation对象的输出可以是线性的、曲线的、一个步进函数或者任何其他可以设计的映射。 根据Animation对象的控制方式，动画可以反向运行，甚至可以在中间切换方向。Animation还可以生成除double之外的其他类型值，如：Animation<Color> 或 Animation<Size>；Animation对象有状态。可以通过访问其value属性获取动画的当前值；Animation对象本身和UI渲染没有任何关系；CurvedAnimationCurvedAnimation将动画过程定义为一个非线性曲线。final CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);注: Curves 类定义了许多常用的曲线，也可以创建自己的，例如：class ShakeCurve extends Curve { @override double transform(double t) { return math.sin(t * math.PI * 2); } }AnimationControllerAnimationController是一个特殊的Animation对象，在屏幕刷新的每一帧，就会生成一个新的值。默认情况下，AnimationController在给定的时间段内会线性的生成从0.0到1.0的数字。 例如，下面代码创建一个Animation对象：final AnimationController controller = new AnimationController( duration: const Duration(milliseconds: 2000), vsync: this);AnimationController派生自Animation<double>，因此可以在需要Animation对象的任何地方使用。 但是，AnimationController具有控制动画的其他方法：forward()：启动动画；reverse({double from})：倒放动画；reset()：重置动画，将其设置到动画的开始位置；stop({ bool canceled = true })：停止动画；当创建一个AnimationController时，需要传递一个vsync参数，存在vsync时会防止屏幕外动画消耗不必要的资源，可以将stateful对象作为vsync的值。注意： 在某些情况下，值(position，值动画的当前值)可能会超出AnimationCon…

[Flutter]解决ERROR:D8: Cannot fit requested classes in a single dex file的问题
type: item
date: 2023-04-17T07:19:00+00:00
description: 最近在学习开发Flutter项目时遇到了一个问题,具体的异常信息如下：Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust this source! ERROR:D8: Cannot fit requested classes in a single dex file (# methods: 65853 > 65536) com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: The number of method references in a .dex file cannot exceed 64K. Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html at com.android.builder.dexing.D8DexArchiveMerger.getExceptionToRethrow(D8DexArchiveMerger.java:151) at com.android.builder.dexing.D8DexArchiveMerger.mergeDexArchives(D8DexArchiveMerger.java:138) at com.android.build.gradle.internal.tasks.DexMergingWorkAction.merge(DexMergingTask.kt:859) at com.android.build.gradle.internal.tasks.DexMergingWorkAction.run(DexMergingTask.kt:805) at com.android.build.gradle.internal.profile.ProfileAwareWorkAction.execute(ProfileAwareWorkAction.kt:74) at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org…