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

1 回复

更多关于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),
        ),
      ),
    );
  }
}

在这个示例中,我们做了以下几件事:

  1. pubspec.yaml文件中添加了window_focus依赖。
  2. 创建了一个Flutter应用,并在主页面FocusListenerExample中使用了WindowFocus监听器。
  3. initState方法中,我们添加了WidgetsBindingObserver以便在应用生命周期内监听窗口焦点变化。
  4. 使用WindowFocus.addListener方法来添加一个监听器,当窗口焦点变化时,更新hasFocus状态。
  5. dispose方法中,我们移除了WidgetsBindingObserver并尝试移除WindowFocus监听器(尽管在这个例子中,我们没有传递特定的清理函数给removeListener,因为addListener的回调没有使用外部状态)。
  6. 在UI中显示当前窗口焦点状态。

这个示例展示了如何使用window_focus插件来监听窗口焦点事件,并根据焦点状态更新UI。如果你需要在应用中获得或失去焦点时执行特定操作,可以在监听器回调中添加相应的逻辑。

回到顶部