首页
苏兮影视
随笔记
壁纸
更多
直播
时光轴
友联
关于
统计
Search
1
v2ray节点搭建
1,007 阅读
2
软件添加id功能按钮
919 阅读
3
QQ扫码无法登录的解决方案
820 阅读
4
网易云音乐歌单ID获取教程
686 阅读
5
typecho非常有特色的模块
659 阅读
谈天说地
建站源码
经验教程
资源分享
动漫美图
登录
Search
标签搜索
java
rust
flutter
esp32c3
springboot
安卓
linux
vue
dart
设计模式
docker
joe
快捷键
git
fish shell
maven
redis
netty
groovy
js
尽意
累计撰写
112
篇文章
累计收到
39
条评论
首页
栏目
谈天说地
建站源码
经验教程
资源分享
动漫美图
页面
苏兮影视
随笔记
壁纸
直播
时光轴
友联
关于
统计
搜索到
11
篇与
的结果
Flutter模板方法模式在多Tab列表中的应用
项目背景与需求在Flutter开发中,我们经常遇到需要实现多Tab栏列表的场景,每个Tab具有相似的结构(下拉刷新、上拉加载、列表展示),但数据类型和UI展示各不相同。本文将介绍如何使用模板方法模式来优雅地解决这个问题。模板方法模式简介模板方法模式是一种行为设计模式,它在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写特定步骤。在我们的场景中,每个Tab的加载流程(初始化→刷新→加载更多)是相同的,但具体的数据加载和UI展示是不同的。代码实现解析1. 抽象基类定义const int defaultPageSize = 10; abstract class TabData<T> { final String name; // tab名称 int total; // 数据数量 bool isSelected; // 选中状态 bool hasMore; // 是否有更多数据 List<T> items; // 数据列表 bool _isInited = false; // 是否初始化完成 final RefreshController controller; final Widget Function(BuildContext context,T item) itemBuilder; TabData({ required this.name, required this.itemBuilder, this.total = 0, this.isSelected = false, this.hasMore = true, this.items = const [] }) : controller = RefreshController(); int get pageSize => defaultPageSize; Future<List<T>> loadData(int page); void updateItems(List<T> newItems, bool isRefresh); Future<bool> init() async{ if (_isInited) { return true; } _isInited = true; await onRefresh(); return false; } Future<void> onRefresh() async { try { final data = await loadData(1); updateItems(data, true); hasMore = data.length >= pageSize; controller.refreshCompleted(); } catch (e) { controller.refreshFailed(); rethrow; } } Future<void> onLoadMore() async { if (!hasMore) { controller.loadNoData(); return; } try { final currentPage = (items.length / pageSize).ceil() + 1; final data = await loadData(currentPage); updateItems(data, false); hasMore = data.length >= pageSize; controller.loadComplete(); } catch (e) { controller.refreshFailed(); rethrow; } } void dispose() { controller.dispose(); } }设计要点:定义了完整的生命周期方法(init、onRefresh、onLoadMore)使用泛型T支持不同的数据类型封装了刷新控制器和分页逻辑提供itemBuilder回调实现UI定制化2. 具体子类实现点赞Tab实现:class LikeTabData extends TabData<LikeTabDataEntity>{ LikeTabData():super(name: "点赞",itemBuilder: (context,item){ return Text(item.name); }); @override Future<List<LikeTabDataEntity>> loadData(int page) async{ await Future.delayed(const Duration(seconds: 1)); return List.generate(pageSize, (index)=> LikeTabDataEntity(name: "$name 数据 ${page * pageSize + index}")); } @override void updateItems(List<LikeTabDataEntity> newItems, bool isRefresh) { items = isRefresh ? newItems : [...items, ...newItems]; total = items.length; } }评论Tab实现:class CommentTabData extends TabData<CommentTabDataEntity>{ CommentTabData():super(name: "评论",itemBuilder: (context,item) { Widget text () => Text(item.text,style: const TextStyle(color: Colors.white)); return Container( margin: const EdgeInsets.all(8), padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.blueAccent, borderRadius: BorderRadius.circular(8), ), child: text(), ); }); @override Future<List<CommentTabDataEntity>> loadData(int page) async{ await Future.delayed(const Duration(seconds: 1)); return List.generate(pageSize, (index)=> CommentTabDataEntity(text: "$name 数据 ${page * pageSize + index}")); } @override void updateItems(List<CommentTabDataEntity> newItems, bool isRefresh) { items = isRefresh ? newItems : [...items, ...newItems]; total = items.length; } }子类只需实现:loadData:具体的数据加载逻辑updateItems:数据合并策略itemBuilder:列表项UI构建3. 状态管理class DialogState extends BaseViewState { List<TabData> tabDataList = [ LikeTabData(), CommentTabData(), LikeTabData(), CommentTabData(), ]; }4. 逻辑控制器class DialogLogic extends BaseGetXLifeCycleController{ late final PageController tabController; final state = DialogState(); @override void onInit() { super.onInit(); tabController = PageController(); state.viewState = TYPE_LOAD; update(); state.tabDataList.first.init().then((_)=> update()); Future.delayed(const Duration(seconds: 1),(){ state.tabDataList.first.isSelected = true; state.viewState = TYPE_NORMAL; update(); }); } @override void onClose() { tabController.dispose(); for(final item in state.tabDataList) { item.dispose(); } super.onClose(); } void onTabChange(int index) async{ for(int i = 0; i < state.tabDataList.length; i++) { state.tabDataList[i].isSelected = index == i; } update(); final isInited = await state.tabDataList[index].init(); if(!isInited) update(); } void changeTab(int index,BuildContext context){ final screenWidth = MediaQuery.of(context).size.width; tabController.animateTo(index * screenWidth,duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); } void onRefresh(Future<void> Function() refresh) async{ await refresh(); update(); } void onLoadMore(Future<void> Function() loadMore) async{ await loadMore(); update(); } }5. UI界面实现class Dialog extends BaseTitleBottomSheet with GetXControllerMixin<DialogLogic>{ Dialog({super.key}); final logic = Get.put(DialogLogic()); final state = Get.find<DialogLogic>().state; @override String getTitle() => "Dialog"; @override Widget buildContent() { return buildPartBaseView( state.viewState,context, _buildContentWidget() ); } Widget _buildContentWidget() { return Expanded( child: Column( children: [ SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ for(int i = 0;i<state.tabDataList.length;i++) GestureDetector(onTap: ()=> logic.changeTab(i,context),child: _buildTabWidget(state.tabDataList[i])) ], ), ), Expanded( child: PageView.builder( controller: logic.tabController, onPageChanged: logic.onTabChange, itemCount: state.tabDataList.length, itemBuilder: (_,index){ final data = state.tabDataList[index]; if(data.items.isEmpty){ return const Center(child: Text("无数据"),); } return _KeepAliveList( data, keepAlive: true, onLoading: ()=> logic.onLoadMore(data.onLoadMore), onRefresh: ()=> logic.onRefresh(data.onRefresh), controller: data.controller ); }, ), ), ], ), ); } Widget _buildTabWidget(TabData entity){ final noSelectedStyle = TextStyle(color: Colors.black,fontSize: 23.w); final selectedStyle = TextStyle(color: Colors.blueAccent,fontSize: 23.w); final style = entity.isSelected ? selectedStyle : noSelectedStyle; return Row( mainAxisSize: MainAxisSize.min, children: [ Text(entity.name,style: style), Text("(${entity.total})",style: style), ], ); } } class _KeepAliveList extends StatefulWidget { final TabData tabData; final bool keepAlive; final VoidCallback? onRefresh; final VoidCallback? onLoading; final RefreshController controller; const _KeepAliveList(this.tabData,{super.key,this.keepAlive = true,required this.controller,this.onRefresh,this.onLoading}); @override State<_KeepAliveList> createState() => _KeepAliveListState(); } class _KeepAliveListState extends State<_KeepAliveList> with AutomaticKeepAliveClientMixin{ @override Widget build(BuildContext context) { super.build(context); return SmartRefresher( controller: widget.controller, enablePullDown: true, enablePullUp: true, header: const ClassicHeader(), onRefresh: widget.onRefresh, onLoading: widget.onLoading, child: ListView.builder( itemCount: widget.tabData.items.length, itemBuilder: (context,index){ // 新版本dart可以使用switch匹配模式,可以穷举完全部密封类的子类 // 为了兼容只能使用if了 final tabData = widget.tabData; if(tabData is LikeTabData){ return tabData.itemBuilder(context,tabData.items[index]); }else if(tabData is CommentTabData){ return tabData.itemBuilder(context,tabData.items[index]); } throw "未知的TabData类型"; }, ), ); } @override bool get wantKeepAlive => widget.keepAlive; }设计优势1. 代码复用性公共的刷新、加载、分页逻辑在基类中实现新增Tab只需继承TabData并实现三个抽象方法2. 可维护性算法框架稳定,修改基类即可影响所有子类业务逻辑与UI展示分离3. 扩展性支持任意类型的列表数据可轻松添加新的Tab类型4. 一致性所有Tab保持相同的交互逻辑统一的错误处理和加载状态使用建议添加新Tab:只需创建新的TabData子类,实现loadData、updateItems和itemBuilder修改公共逻辑:在TabData基类中修改,所有子类自动生效自定义分页大小:在子类中重写pageSize getter特殊处理:子类可重写onRefresh或onLoadMore方法进行个性化处理总结通过模板方法模式,我们成功地将多Tab列表中的公共逻辑提取到基类中,同时保留了子类的灵活性。这种设计模式特别适用于具有相似流程但不同实现的场景,能够显著减少代码重复,提高项目的可维护性和扩展性。
2026年01月16日
6 阅读
0 评论
1 点赞
Flutter 实现流式消息接收
我们通过 Dio 发起一个 ResponseType.stream 请求,消费后端逐字符返回的 HTTP 流。核心代码如下:void sendMessage() => Dio().get( "http://192.168.149.178:8080/stream/covert?url=${state.text}", options: Options(responseType: ResponseType.stream), cancelToken: CancelToken(), ).then((response) { final data = response.data; if (data is! ResponseBody) return; state.data = ""; update(); data.stream.listen((chunk) { final res = utf8.decoder.convert(chunk); state.data += res; update(); }); });UI 部分使用 Expanded + SingleChildScrollView 展示实时追加的文本:Column( children: [ TextField(controller: logic.text), TextButton(onPressed: logic.sendMessage, child: Text("发送消息")), Expanded( child: SelectionArea( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [Text(state.data)], ), ), ), ), ], )后端使用 Spring WebFlux 将目标网页内容逐字符延迟返回:@GetMapping("/covert") public Flux<String> covert(@RequestParam String url) { return Mono.fromCallable(() -> { // 同步抓取网页内容(在非 Netty 线程执行) var request = new Request.Builder().url(url).build(); try (var resp = client.newCall(request).execute()) { return resp.body().string(); } }) .flux() .flatMap(text -> Flux.fromStream(text.chars().mapToObj(c -> String.valueOf((char) c))) .delayElements(Duration.ofMillis(10)) // 每字符间隔 10ms ); }该方案无需 SSE 或 WebSocket,仅依赖标准 HTTP 分块传输,即可实现“打字机式”流式响应,适用于 AI 对话、实时日志等场景。
2026年01月07日
7 阅读
0 评论
1 点赞
Flutter LayoutBuilder 原理深度解析
引言在 Flutter 响应式布局体系中,LayoutBuilder 是一个强大而特殊的组件。与大多数在构建阶段(build phase)确定其子树的组件不同,LayoutBuilder 将子组件的构建推迟到布局阶段(layout phase),从而能够根据父级传递的实际布局约束动态构建 UI。本文将深入源码层面,全面剖析 LayoutBuilder 的工作原理。核心架构概览类继承层次Widget ├── RenderObjectWidget │ └── ConstrainedLayoutBuilder<BoxConstraints> │ └── LayoutBuilder (具体实现)主要角色分工ConstrainedLayoutBuilder<T> - 抽象基类,定义约束构建器的通用协议LayoutBuilder - 面向 BoxConstraints 的具体实现_LayoutBuilderElement - 连接 Widget 与 RenderObject 的桥梁_RenderLayoutBuilder - 实际执行布局的 RenderObject源码深度解析1. Element 的初始化与绑定// _LayoutBuilderElement.mount 方法 @override void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); // 1. 创建对应的 RenderObject renderObject.updateCallback(_rebuildWithConstraints); // 2. 绑定重建回调 }关键点分析:super.mount() 会创建 _RenderLayoutBuilder 实例updateCallback 将 _rebuildWithConstraints 方法注册到 RenderObject此时回调并未立即执行,只是建立了调用关系2. 布局阶段的核心调用链// _RenderLayoutBuilder.performLayout 方法 @override void performLayout() { final BoxConstraints constraints = this.constraints; // 1. 获取父级约束 rebuildIfNecessary(); // 2. 触发约束重建 if (child != null) { child!.layout(constraints, parentUsesSize: true); // 3. 布局子节点 size = constraints.constrain(child!.size); // 4. 确定自身大小 } else { size = constraints.biggest; } }2.1 约束的来源追踪约束的传递遵循 Flutter 渲染管线的标准流程:// RenderObject 层级传递 void layout(Constraints constraints, { bool parentUsesSize = false }) { this.constraints = constraints; // 父组件传递约束 // ... 后续布局计算 }this.constraints 在 performLayout 中通过 this.constraints 访问,这是 RenderObject 的标准属性。2.2 重建触发机制// RenderConstrainedLayoutBuilder 混入类 void rebuildIfNecessary() { assert(_callback != null); invokeLayoutCallback(_callback!); // 调用注册的回调 } // RenderObject 中的回调执行 void invokeLayoutCallback<T extends Constraints>(LayoutCallback<T> callback) { owner!._enableMutationsToDirtySubtrees(() { callback(constraints as T); // 关键:传递当前约束 }); }3. 安全的回调执行环境_enableMutationsToDirtySubtrees 是保证布局阶段安全修改组件树的关键:void _enableMutationsToDirtySubtrees(VoidCallback callback) { assert(_debugDoingLayout); bool? oldState; assert(() { oldState = _debugAllowMutationsToDirtySubtrees; _debugAllowMutationsToDirtySubtrees = true; // 临时允许修改 return true; }()); try { callback(); // 执行 LayoutBuilder 的 builder } finally { _shouldMergeDirtyNodes = true; assert(() { _debugAllowMutationsToDirtySubtrees = oldState!; // 恢复原状态 return true; }()); } }安全机制解析:断言保护:仅在布局阶段允许此操作状态切换:临时开启脏子树修改权限异常安全:使用 try-finally 确保状态恢复操作合并:设置 _shouldMergeDirtyNodes 以优化更新4. 约束重建的实际执行void _rebuildWithConstraints(ConstraintType constraints) { // 构建子组件的闭包 void updateChildCallback() { Widget built = widget.builder(this, constraints); // 调用用户定义的builder _child = updateChild(_child, built, null); // 更新子Element _needsBuild = false; _previousConstraints = constraints; // 缓存约束 } // 条件性执行重建 final VoidCallback? callback = _needsBuild || (constraints != _previousConstraints) ? updateChildCallback : null; owner!.buildScope(this, callback); }智能优化机制:_previousConstraints 缓存上次的约束仅当约束变化或标记需要重建时才执行避免不必要的重建,提升性能5. 更新与重建调度5.1 组件更新处理@override void update(ConstrainedLayoutBuilder<ConstraintType> newWidget) { super.update(newWidget); renderObject.updateCallback(_rebuildWithConstraints); // 重新绑定 if (newWidget.updateShouldRebuild(oldWidget)) { _needsBuild = true; // 标记需要重建 renderObject.markNeedsLayout(); // 触发重新布局 } }5.2 智能调度策略void _scheduleRebuild() { // 根据调度阶段决定立即标记还是延迟 final bool deferMarkNeedsLayout = switch (SchedulerBinding.instance.schedulerPhase) { SchedulerPhase.idle || SchedulerPhase.postFrameCallbacks => true, _ => false, }; if (!deferMarkNeedsLayout) { renderObject.markNeedsLayout(); } else { // 在稳定阶段延迟调度 SchedulerBinding.instance.scheduleFrameCallback(_frameCallback); } }调度策略优势:立即响应:在布局/绘制阶段立即触发更新延迟优化:在空闲阶段延迟到下一帧,避免打断当前操作设计模式分析1. 延迟决策模式LayoutBuilder 实现了典型的延迟决策模式:构建阶段:只建立结构和回调关系布局阶段:根据实际约束做出具体构建决策优势:适应动态布局需求,支持响应式设计2. 回调注册模式通过 updateCallback 机制实现松耦合:Element 向 RenderObject 注册回调RenderObject 在适当时机触发回调分离了决策逻辑与执行时机3. 安全沙箱模式_enableMutationsToDirtySubtrees 提供安全执行环境:临时放宽框架限制执行用户代码后立即恢复平衡灵活性与安全性实战应用示例1. 经典响应式布局Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { // 根据宽度决定布局方式 if (constraints.maxWidth > 600) { return Row(children: [Sidebar(), ContentArea()]); } else { return Column(children: [AppBar(), ContentArea()]); } }, ); }2. 动态尺寸计算Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final itemWidth = (constraints.maxWidth - 32) / 3; return GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, childAspectRatio: itemWidth / 100, ), itemBuilder: (context, index) => ItemWidget(), ); }, ); }源码设计亮点1. 性能优化约束缓存:通过 _previousConstraints 避免重复构建条件更新:仅在实际变化时触发重建智能调度:根据框架状态选择最佳更新时机2. 错误边界void _rebuildWithConstraints(ConstraintType constraints) { try { built = widget.builder(this, constraints); } catch (e, stack) { built = ErrorWidget.builder(...); // 优雅降级 } // ... 错误处理继续 }3. 调试支持详细的断言检查调试信息收集异常报告机制总结与启示LayoutBuilder 的设计体现了 Flutter 框架的几个核心理念:关注点分离:将构建决策延迟到拥有完整布局信息时性能优先:通过多种优化避免不必要的计算安全第一:在灵活性与框架稳定性间取得平衡声明式表达:通过简单的回调 API 提供强大功能通过深入分析 LayoutBuilder 的源码,我们不仅理解了其工作原理,更能学习到如何设计既灵活又稳定的框架级组件。这种在布局阶段获取约束并动态构建的能力,为 Flutter 的响应式设计提供了坚实的基础设施支持。源码文件:packages/flutter/lib/src/widgets/layout_builder.dart 设计模式:延迟决策、回调注册、安全沙箱 适用场景:响应式布局、动态 UI、约束依赖型组件
2025年12月25日
11 阅读
0 评论
1 点赞
Flutter 状态管理之 Provider
在 Flutter 开发中,状态管理是一个非常重要的话题。而 Provider 作为 Flutter 社区推荐的状态管理解决方案之一,因其轻量、简单、灵活而备受欢迎。本文将系统地解析 Provider 的基本用法和高级用法,包括 read、watch、consume 和 selector,并通过单个 Model 和多个 Model 的管理案例详细说明。什么是 Provider?Provider 是一个基于 InheritedWidget 的封装,用于高效地管理 Flutter 应用中的状态。它的主要优势包括:简单易用,符合 Dart 语言的惯用写法。支持多层 Widget 树的状态共享。高性能,只会更新需要重建的 Widget。通过 Provider,我们可以方便地实现依赖注入和响应式编程。Provider 的基本用法在使用 Provider 之前,需要先在 pubspec.yaml 文件中添加依赖:dependencies: provider: ^lates以下通过一个简单的计数器示例说明 Provider 的基本用法。单个 Model 的使用创建 Model 类import 'package:flutter/foundation.dart'; class CounterModel with ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } }在应用中注册 Providerimport 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'counter_model.dart'; void main() { runApp( ChangeNotifierProvider( create: (_) => CounterModel(), child: MyApp(), ), ); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: CounterScreen(), ); } }获取状态和更新 UIclass CounterScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Provider 示例')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('当前计数:'), Text( context.watch<CounterModel>().count.toString(), style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => context.read<CounterModel>().increment(), child: Icon(Icons.add), ), ); } }多个 Model 的管理在大型应用中,可能需要管理多个 Model。在这种情况下,可以使用 MultiProvider。定义多个 Modelclass CounterModel with ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } } class ThemeModel with ChangeNotifier { bool _isDark = false; bool get isDark => _isDark; void toggleTheme() { _isDark = !_isDark; notifyListeners(); } }注册多个 Providervoid main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => CounterModel()), ChangeNotifierProvider(create: (_) => ThemeModel()), ], child: MyApp(), ), ); }在不同的 Widget 中使用 Modelclass CounterScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Provider 示例'), actions: [ IconButton( icon: Icon(Icons.brightness_6), onPressed: () => context.read<ThemeModel>().toggleTheme(), ), ], ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('当前计数:'), Text( context.watch<CounterModel>().count.toString(), style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => context.read<CounterModel>().increment(), child: Icon(Icons.add), ), ); } }Provider 中的核心方法1. readread 方法用于访问状态,但不会订阅状态的变化。这意味着调用 read 的 Widget 在状态更新时不会重建。适用场景:需要触发一次性的操作,例如调用方法或事件处理器。FloatingActionButton( onPressed: () => context.read<CounterModel>().increment(), child: Icon(Icons.add), )2. watchwatch 方法会订阅状态的变化。当状态发生变化时,使用 watch 的 Widget 会重新构建。适用场景:需要根据状态动态更新 UI。Text( context.watch<CounterModel>().count.toString(), style: Theme.of(context).textTheme.headline4, )3. consumeConsumer 是 Provider 提供的一个 Widget,能够订阅状态并重建自身及其子树。适用场景:需要精确控制重建的范围,避免不必要的 Widget 重建。Consumer<CounterModel>( builder: (context, counter, child) { return Text( counter.count.toString(), style: Theme.of(context).textTheme.headline4, ); }, )4. selectorSelector 提供了更高效的订阅方式,可以从状态中选择特定字段进行订阅,避免因其他字段变化导致的无效重建。适用场景:状态对象较大且只需要监听其中部分字段时。Selector<CounterModel, int>( selector: (_, counter) => counter.count, builder: (context, count, child) { return Text( count.toString(), style: Theme.of(context).textTheme.headline4, ); }, )性能优化建议使用 Consumer 或 Selector 控制重建范围:避免不必要的父级 Widget 重建。分离逻辑和 UI:将业务逻辑放在模型中,UI 层只负责展示。尽量减少依赖注入的深度:如果可能,使用多层 Provider。
2024年12月06日
171 阅读
0 评论
1 点赞
flutter开发桌面应用实现窗体透明
2024年09月21日
262 阅读
0 评论
1 点赞
2024-09-21
1.使用bitsdojo_window插件:使用命令添加dart pub add bitsdojo_window2.在 windows/runner/main.cpp 中进行窗口透明设置:#include <flutter/dart_project.h> #include <flutter/flutter_view_controller.h> #include <windows.h> #include "flutter_window.h" #include "utils.h" // 添加的代码 #include<bitsdojo_window_windows/bitsdojo_window_plugin.h> auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP); void EnableTransparency(HWND hwnd) { SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED); COLORREF colorKey = RGB(255, 255, 255); BYTE alpha = 128; // 设置透明度(0-256) SetLayeredWindowAttributes(hwnd, colorKey, alpha, LWA_ALPHA); } // 到这里结束 int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. //...省略中间的代码 // 添加的代码 EnableTransparency(window.GetHandle()); // 到这里结束 window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } 3.设置页面背景为透明:Scaffold( backgroundColor: Colors.transparent, body: Container() )4.实现的效果:
1
2
3