Flutter窗口焦点管理插件window_focus的使用
Flutter窗口焦点管理插件window_focus的使用
简介
Window Focus
是一个用于 Flutter 的插件,允许你跟踪用户活动并识别活动窗口。该插件支持 Windows 和 macOS 平台,并提供了以下功能:
- 检测用户不活动时间
- 获取活动应用程序名称或窗口标题
- 启用调试模式以增强日志记录
关键特性
用户不活动跟踪
你可以检测到用户在 Flutter 应用程序中的不活动时间段。你可以自定义不活动阈值并根据需要处理不活动事件。
活动窗口标题检索
能够获取操作系统活动窗口的标题。对于 macOS,这是应用程序名称;对于 Windows,则是窗口标题。
调试模式
启用详细日志记录以便于开发期间调试。
设置自定义空闲阈值
定义用户被认定为不活动的时间间隔。
插件安装
Windows
无需任何操作。
macOS
你需要将以下代码添加到 Info.plist 文件中:
<key>NSApplicationSupportsSecureRestorableState</key>
<true/>
插件使用
导入插件
import 'package:window_focus/window_focus.dart';
示例代码
以下是一个完整的示例代码,展示了如何使用 window_focus
插件来监听窗口焦点变化和用户活动状态。
import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:window_focus/window_focus.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
[@override](/user/override)
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String activeWindowTitle = 'Unknown';
bool userIdle = false;
final _windowFocusPlugin = WindowFocus(debug: true, duration: const Duration(seconds: 5));
final _messangerKey = GlobalKey<ScaffoldMessengerState>();
DateTime? lastUpdateTime;
final textController = TextEditingController();
final idleTimeOutInSeconds = 1;
List<TimeAppDto> items = [];
Duration allTime = const Duration();
Duration idleTime = const Duration();
late Timer _timer;
[@override](/user/override)
void initState() {
super.initState();
_windowFocusPlugin.addFocusChangeListener((p0) {
_handleFocusChange(p0.appName);
});
_windowFocusPlugin.addUserActiveListener((p0) {
print('>>>' + p0.toString());
setState(() {
userIdle = p0;
});
});
_startTimer();
}
void _startTimer() {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_updateActiveAppTime();
setState(() {
allTime += const Duration(seconds: 1);
if (!userIdle) {
idleTime += const Duration(seconds: 1);
}
});
});
}
void _updateActiveAppTime({bool forceUpdate = false}) {
if (!userIdle) return;
if (lastUpdateTime == null) return;
final now = DateTime.now();
final elapsed = now.difference(lastUpdateTime!);
if (elapsed < const Duration(seconds: 1) && !forceUpdate) return;
final existingIndex = items.indexWhere((item) => item.appName == activeWindowTitle);
if (existingIndex != -1) {
final existingItem = items[existingIndex];
items[existingIndex] = existingItem.copyWith(
timeUse: existingItem.timeUse + elapsed,
);
setState(() {});
} else {
items.add(TimeAppDto(appName: activeWindowTitle, timeUse: elapsed));
}
lastUpdateTime = now;
setState(() {});
}
void _handleFocusChange(String newAppName) {
final now = DateTime.now();
if (activeWindowTitle != newAppName) {
_updateActiveAppTime(forceUpdate: true);
}
activeWindowTitle = newAppName;
lastUpdateTime = now;
setState(() {});
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: _messangerKey,
home: Scaffold(
appBar: AppBar(
title: const Text('窗口焦点插件示例应用'),
),
body: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('当前活动窗口标题: $activeWindowTitle\n'),
Text('用户是否空闲: ${!userIdle}\n'),
Text('总时间: ${_formatDuration(allTime)}\n'),
const SizedBox(height: 10),
Text('空闲时间: ${_formatDuration(idleTime)}\n'),
const SizedBox(height: 10),
Text('活动时间: ${_formatDuration(allTime - idleTime)}\n'),
Form(
child: Row(
children: [
Expanded(
child: TextFormField(
decoration: const InputDecoration(labelText: '输入秒数'),
),
),
ElevatedButton(
onPressed: () async {
Duration duration = await _windowFocusPlugin.idleThreshold;
print(duration);
},
child: const Text('设置空闲超时'),
)
],
),
)
],
),
),
),
Expanded(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.only(right: 8),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(24)),
),
child: ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].appName),
trailing: Text(formatDurationToHHMM(items[index].timeUse)),
);
},
itemCount: items.length,
),
),
),
],
),
bottomNavigationBar: TextButton(
child: Text('订阅我的电报频道 @kotelnikoff_dev'),
onPressed: () async {
if (await canLaunchUrl(Uri.parse('https://telegram.me/kotelnikoff_dev'))) {
await launchUrl(Uri.parse('https://telegram.me/kotelnikoff_dev'));
} else {
_messangerKey.currentState!.showSnackBar(const SnackBar(
content: Text('哦!我的电报频道 @kotelnikoff_dev!'),
));
}
},
),
),
);
}
void addInactivityThreshold(BuildContext context) async {
try {
Duration duration = await _windowFocusPlugin.idleThreshold;
duration += const Duration(seconds: 5);
_windowFocusPlugin.setIdleThreshold(duration: duration);
_messangerKey.currentState!.showSnackBar(SnackBar(
content: Text('设置成功!新的空闲超时为 ${duration.inSeconds} 秒'),
));
} catch (e, s) {
print(e);
print(s);
}
}
String formatDurationToHHMM(Duration duration) {
final hours = duration.inHours;
final minutes = duration.inMinutes.remainder(60);
final seconds = duration.inSeconds.remainder(60);
if (hours > 0) {
return '${hours.toString().padLeft(2, '0')}:'
'${minutes.toString().padLeft(2, '0')}:'
'${seconds.toString().padLeft(2, '0')}';
} else {
return '${minutes.toString().padLeft(2, '0')}:'
'${seconds.toString().padLeft(2, '0')}';
}
}
String _formatDuration(Duration duration) {
return duration.toString().split('.').first.padLeft(8, "0");
}
[@override](/user/override)
void dispose() {
_timer.cancel();
_windowFocusPlugin.dispose();
super.dispose();
}
}
class TimeAppDto {
final String appName;
final Duration timeUse;
TimeAppDto({required this.appName, required this.timeUse});
TimeAppDto copyWith({
String? appName,
Duration? timeUse,
}) {
return TimeAppDto(
appName: appName ?? this.appName,
timeUse: timeUse ?? this.timeUse,
);
}
[@override](/user/override)
int get hashCode {
return timeUse.hashCode ^ appName.hashCode;
}
[@override](/user/override)
bool operator ==(Object other) {
return other is TimeAppDto && other.timeUse == timeUse && other.appName == appName;
}
}
更多关于Flutter窗口焦点管理插件window_focus的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter窗口焦点管理插件window_focus的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter中使用window_focus
插件来管理窗口焦点的代码示例。这个插件允许你监听窗口获得或失去焦点的事件,这在需要知道用户是否正在与应用交互时非常有用。
首先,确保你已经在pubspec.yaml
文件中添加了window_focus
依赖:
dependencies:
flutter:
sdk: flutter
window_focus: ^0.5.0 # 请检查最新版本号
然后,运行flutter pub get
来安装依赖。
接下来,在你的Dart文件中,你可以使用WindowFocus
类来监听窗口焦点事件。以下是一个完整的示例:
import 'package:flutter/material.dart';
import 'package:window_focus/window_focus.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FocusListenerExample(),
);
}
}
class FocusListenerExample extends StatefulWidget {
@override
_FocusListenerExampleState createState() => _FocusListenerExampleState();
}
class _FocusListenerExampleState extends State<FocusListenerExample> with WidgetsBindingObserver {
bool hasFocus = true; // 假设应用启动时具有焦点
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
// 监听窗口焦点变化
WindowFocus.addListener(() {
bool newFocusStatus = WindowFocus.hasFocus;
if (newFocusStatus != hasFocus) {
setState(() {
hasFocus = newFocusStatus;
});
}
});
}
@override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
WindowFocus.removeListener(() {}); // 移除监听器,尽管在这个例子中我们没有特定的清理函数
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Window Focus Listener Example'),
),
body: Center(
child: Text(
hasFocus ? 'Window has focus' : 'Window lost focus',
style: TextStyle(fontSize: 24),
),
),
);
}
}
在这个示例中,我们做了以下几件事:
- 在
pubspec.yaml
文件中添加了window_focus
依赖。 - 创建了一个Flutter应用,并在主页面
FocusListenerExample
中使用了WindowFocus
监听器。 - 在
initState
方法中,我们添加了WidgetsBindingObserver
以便在应用生命周期内监听窗口焦点变化。 - 使用
WindowFocus.addListener
方法来添加一个监听器,当窗口焦点变化时,更新hasFocus
状态。 - 在
dispose
方法中,我们移除了WidgetsBindingObserver
并尝试移除WindowFocus
监听器(尽管在这个例子中,我们没有传递特定的清理函数给removeListener
,因为addListener
的回调没有使用外部状态)。 - 在UI中显示当前窗口焦点状态。
这个示例展示了如何使用window_focus
插件来监听窗口焦点事件,并根据焦点状态更新UI。如果你需要在应用中获得或失去焦点时执行特定操作,可以在监听器回调中添加相应的逻辑。