一直觉得自己写的不是技术,而是情怀,一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的,希望我的这条路能让你们少走弯路,希望我能帮你们抹去知识的蒙尘,希望我能帮你们理清知识的脉络,希望未来技术之巅上有你们也有我。

2025.11.24Flutter-Frame.zip 项目底层框架 代码下载

文章目录

全局导入

OC

在这里插入图片描述

在这里插入图片描述

Flutter

iOSPCH文件全局导入,Flutter 可以通过 集中导入+导出 来实现几乎相同的效果

方法一:集中 export(推荐)

新建一个 common_imports.dart 文件:

// common_imports.dart
export 'package:flutter/material.dart';
export 'package:flutter/services.dart';
export 'package:provider/provider.dart';
export 'utils/log.dart';
export 'utils/constants.dart';

以后你在任何地方只要:

import 'common_imports.dart';

就能同时使用上面所有的包和工具类,效果跟 iOS 的 PCH 很接近。

AZGradient 渐变效果

AZGradientText 渐变色的文字

源码

import 'package:flutter/material.dart';

class AZGradientText extends StatelessWidget {
  final String text;
  final TextStyle? style;
  final List<Color> colors;
  final AlignmentGeometry startPoint;
  final AlignmentGeometry endPoint;

  const AZGradientText(
      this.text, {
        Key? key,
        required this.colors,
        this.style,
        this.startPoint = Alignment.centerLeft,
        this.endPoint = Alignment.centerRight,
      }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ShaderMask(
      shaderCallback: (bounds) => LinearGradient(
        colors: colors,
        begin: startPoint,
        end: endPoint,
      ).createShader(Rect.fromLTWH(0, 0, bounds.width, bounds.height)),
      child: Text(
        text,
        style: style?.copyWith(color: Colors.white) ??
            const TextStyle(color: Colors.white),
      ),
    );
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/AZGradient/AZGradientText.dart';

class Category extends StatefulWidget {
  final String title;
  const Category({super.key, required this.title});

  @override
  State<Category> createState() => _Category();
}

class _Category extends State<Category> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Category'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            AZGradientText(
              widget.title, // ✅ 渐变文字内容
              colors: const [
                Color(0xFFFF1493),
                Color(0xFF0072FF),
              ],
              style: const TextStyle(
                fontSize: 36,
                fontWeight: FontWeight.bold,
              ),
              startPoint: Alignment.topLeft,
              endPoint: Alignment.bottomRight,
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

AZGradientView 渐变背景容器

代码

import 'package:flutter/material.dart';

class AZGradientView extends StatelessWidget {
  final List<Color> colors;
  final List<double>? locations;
  final AlignmentGeometry startPoint;
  final AlignmentGeometry endPoint;
  final Widget? child;

  const AZGradientView({
    Key? key,
    required this.colors,
    this.locations,
    this.startPoint = Alignment.centerLeft,
    this.endPoint = Alignment.centerRight,
    this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: colors,
          stops: locations,
          begin: startPoint,
          end: endPoint,
        ),
      ),
      child: child,
    );
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/AZGradient/AZGradientView.dart'; // ✅ 记得引入刚才的封装文件

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: const AZGradientView(
        colors: [
          Color(0xFF4FACFE), // 渐变起始色
          Color(0xFFFF1493), // 渐变结束色
        ],
        startPoint: Alignment.topLeft,
        endPoint: Alignment.bottomRight,
        child: Center(
          child: Text(
            'I am gradient text area',
            textAlign: TextAlign.center,
            style: TextStyle(
              backgroundColor: Colors.transparent, // 由外层负责背景
              color: Colors.white, // 渐变背景衬托白色字体
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

UUID 设备唯一标识符

KeyChainStore

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class KeyChainStore {
  static const FlutterSecureStorage _storage = FlutterSecureStorage();

  /// 保存数据
  static Future<void> save(String service, String data) async {
    await _storage.write(key: service, value: data);
  }

  /// 读取数据
  static Future<String?> load(String service) async {
    return await _storage.read(key: service);
  }

  /// 删除数据
  static Future<void> deleteKeyData(String service) async {
    await _storage.delete(key: service);
  }
}

UUID

import 'dart:async';
import 'package:uuid/uuid.dart';
import 'package:flutterdemols/Extension/UUID/KeyChainStore.dart';

class UUID {
  static const String _service = "com.company.app.usernamepassword";

  static Future<String> getUUID() async {
    // 尝试读取
    String? strUUID = await KeyChainStore.load(_service);

    if (strUUID == null || strUUID.isEmpty) {
      // 生成新的 UUID
      strUUID = const Uuid().v4();

      // 保存到 Keychain
      await KeyChainStore.save(_service, strUUID);
    }

    return strUUID;
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/UUID/UUID.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  String? _uuid; // 用于保存读取到的UUID

  @override
  void initState() {
    super.initState();
    _loadUUID();
  }

  /// 异步加载UUID
  Future<void> _loadUUID() async {
    String uuid = await UUID.getUUID(); // 从KeyChain中读取或生成新的UUID
    setState(() {
      _uuid = uuid;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: _uuid == null
            ? const CircularProgressIndicator() // 加载中显示loading
            : Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '设备唯一标识符 (UUID):',
              style: TextStyle(
                fontSize: 18,
                color: Colors.black87,
              ),
            ),
            const SizedBox(height: 10),
            Container(
              padding: const EdgeInsets.all(12),
              margin: const EdgeInsets.symmetric(horizontal: 20),
              decoration: BoxDecoration(
                color: Colors.amber[100],
                borderRadius: BorderRadius.circular(8),
              ),
              child: Text(
                _uuid!,
                textAlign: TextAlign.center,
                style: const TextStyle(
                  color: Colors.blue,
                  fontSize: 16,
                  fontWeight: FontWeight.w500,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

AnimationManager 轻量级通用动画调度器

源码

import 'package:flutter/material.dart';

enum AnimationDirection { up, down, left, right }

class AnimationManager {
  static void startAnimation({
    required BuildContext context,
    required Widget child,
    required AnimationDirection direction,
    required double offset,
    Duration duration = const Duration(seconds: 5),
    Duration delay = Duration.zero,
  }) {
    final overlay = Overlay.of(context);
    if (overlay == null) return;

    // 先声明,再赋值 —— 关键点
    OverlayEntry? entry;
    entry = OverlayEntry(builder: (_) {
      return _AnimatedView(
        child: child,
        direction: direction,
        offset: offset,
        duration: duration,
        delay: delay,
        onEnd: () {
          // 使用可空访问,避免未初始化或重复移除的问题
          entry?.remove();
        },
      );
    });

    overlay.insert(entry);
  }
}

class _AnimatedView extends StatefulWidget {
  final Widget child;
  final AnimationDirection direction;
  final double offset;
  final Duration duration;
  final Duration delay;
  final VoidCallback onEnd;

  const _AnimatedView({
    Key? key,
    required this.child,
    required this.direction,
    required this.offset,
    required this.duration,
    required this.delay,
    required this.onEnd,
  }) : super(key: key);

  @override
  State<_AnimatedView> createState() => _AnimatedViewState();
}

class _AnimatedViewState extends State<_AnimatedView>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<Offset> _position;
  late final Animation<double> _opacity;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(vsync: this, duration: widget.duration);

    Offset begin;
    switch (widget.direction) {
      case AnimationDirection.up:
        begin = Offset(0, -widget.offset);
        break;
      case AnimationDirection.down:
        begin = Offset(0, widget.offset);
        break;
      case AnimationDirection.left:
        begin = Offset(-widget.offset, 0);
        break;
      case AnimationDirection.right:
        begin = Offset(widget.offset, 0);
        break;
    }

    _position = Tween<Offset>(begin: begin, end: Offset.zero).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),
    );

    _opacity = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),
    );

    Future.delayed(widget.delay, () {
      if (!mounted) return;
      _controller.forward().whenComplete(() {
        widget.onEnd();
      });
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // 使用 Center + Transform.translate 保持和原来 center/alpha 行为一致
    return Center(
      child: AnimatedBuilder(
        animation: _controller,
        builder: (_, child) {
          return Transform.translate(
            offset: _position.value,
            child: Opacity(
              opacity: _opacity.value,
              child: child,
            ),
          );
        },
        child: widget.child,
      ),
    );
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/AnimationManager.dart'; 

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {

  void _showAnimatedText() {
    AnimationManager.startAnimation(
      context: context,
      child: _animatedText(),
      direction: AnimationDirection.up,
      offset: 1.0, // 偏移倍数,1.0 相当于屏幕一个单位的高度或宽度
      duration: const Duration(seconds: 2),
      delay: const Duration(milliseconds: 200),
    );
  }

  Widget _animatedText() {
    return const Text(
      'i am is text',
      textAlign: TextAlign.center,
      style: TextStyle(
        backgroundColor: Colors.amber,
        color: Colors.blue,
        fontSize: 20,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: _showAnimatedText,
          child: const Text('Show Animation'),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

AppColor 颜色工具库

源码

import 'dart:math';
import 'package:flutter/material.dart';

/// 颜色工具类
class ColorHex {
  /// 将 0x 开头的十六进制值转换为 Color
  /// [ColorHex.fromHex(0xFF00CD)]
  static Color fromHexInt(int hexColor, {double alpha = 1.0}) {
    assert(alpha >= 0 && alpha <= 1);
    return Color.fromRGBO(
      (hexColor >> 16) & 0xFF,
      (hexColor >> 8) & 0xFF,
      hexColor & 0xFF,
      alpha,
    );
  }

  /// 从字符串(# 或 0x 开头)创建颜色
  /// 支持格式:
  /// - "#333333"
  /// - "0X333333"
  /// - "333333"
  static Color fromHexString(String colorStr) {
    String c = colorStr.trim().toUpperCase();

    if (c.startsWith('0X')) {
      c = c.substring(2);
    } else if (c.startsWith('#')) {
      c = c.substring(1);
    }

    if (c.length != 6) return Colors.transparent;

    final r = int.parse(c.substring(0, 2), radix: 16);
    final g = int.parse(c.substring(2, 4), radix: 16);
    final b = int.parse(c.substring(4, 6), radix: 16);

    return Color.fromRGBO(r, g, b, 1.0);
  }
}

/// 应用颜色表(等价于 OC 的 Color 类)
class AppColor {
  /// 随机颜色
  static Color get random {
    final random = Random();
    return Color.fromRGBO(
      random.nextInt(256),
      random.nextInt(256),
      random.nextInt(256),
      1.0,
    );
  }

  /// 主题色
  static const Color theme = Color(0xFFCD001E);

  /// 亮主题色
  static const Color themeLight = Color(0xFFFF5000);

  /// 浅主题色
  static const Color themeShallow = Color(0xFFF08080);

  /// 弱主题色
  static const Color themeWeak = Color(0xFFFCF0F2);

  /// 文字主色
  static const Color textBlank = Color(0xFF333333);

  /// 二级文字
  static const Color textSub = Color(0xFF8C8C8C);

  /// 未激活按钮
  static const Color nonActivated = Color(0xFFBEBEBE);

  /// 文字描边
  static const Color textLine = Color(0xFFDDDDDD);

  /// 分割线
  static const Color line = Color(0xFFE5E5E5);

  /// 背景
  static const Color background = Color(0xFFF5F5F5);

  /// 辅助色
  static const Color assist = Color(0xFFFFAC03);

  /// 辅助深色
  static const Color assistDeep = Color(0xFFF4A400);

  /// 亮绿色
  static const Color greenLight = Color(0xFF82A542);

  /// 深亮绿
  static const Color greenDeepLight = Color(0xFF75953C);

  /// 深绿
  static const Color greenDeep = Color(0xFF6D7B52);

  /// 道奇蓝
  static const Color doderBlue = Color(0xFF00A4E3);

  /// 皇家蓝
  static const Color royalBlue = Color(0xFF4169E1);

  /// 乳脂色
  static const Color bisque = Color(0xFFFAEBD7);

  /// 春天绿
  static const Color limeGreen = Color(0xFF3CB371);

  /// 晒黑
  static const Color tan = Color(0xFFA58561);

  /// 标题颜色
  static const Color textTheme = Color(0xFF010101);

  /// 秒杀已抢光颜色
  static const Color robed = Color(0xFFE56A7E);
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/AppColor.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: AppColor.theme, // 使用主题色
      ),
      body: const Center(
        child: Text(
          'i am is text',
          textAlign: TextAlign.center,
          style: TextStyle(
            backgroundColor: AppColor.themeWeak, // 背景色 → 弱主题色
            color: AppColor.textBlank, // 字体颜色(主文本色)
            fontSize: 20,
          ),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

BinarySystem 进制的转换

源码

import 'dart:math';

/// BinarySystem 工具类:
/// 实现任意进制(2~36)之间的互相转换。
class BinarySystem {
  /// 将 10 进制整数 [decimal] 转换为 N 进制字符串
  /// 例:BinarySystem.tenToN(255, 16) => "ff"
  static String tenToN(int decimal, int radix) {
    assert(radix >= 2 && radix <= 36, '进制数必须在 2~36 之间');

    if (decimal == 0) return "0";
    const digits = '0123456789abcdefghijklmnopqrstuvwxyz';
    var result = StringBuffer();

    var value = decimal;
    while (value > 0) {
      int remainder = value % radix;
      value ~/= radix; // 整除
      result.write(digits[remainder]);
    }

    // 翻转结果,因为计算时是从低位到高位
    return result.toString().split('').reversed.join('');
  }

  /// 将 N 进制字符串 [str] 转换为 10 进制整数
  /// 例:BinarySystem.nToTen("ff", 16) => 255
  static int nToTen(String str, int radix) {
    assert(radix >= 2 && radix <= 36, '进制数必须在 2~36 之间');

    const digits = '0123456789abcdefghijklmnopqrstuvwxyz';
    var lower = str.toLowerCase();
    int result = 0;

    for (int i = 0; i < lower.length; i++) {
      int digitValue = digits.indexOf(lower[i]);
      if (digitValue == -1 || digitValue >= radix) {
        throw FormatException("非法字符 '${lower[i]}' 对于 $radix 进制");
      }
      result += digitValue * pow(radix, lower.length - i - 1).toInt();
    }

    return result;
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/BinarySystem.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  final int decimal = 255;

  @override
  Widget build(BuildContext context) {
    final hexStr = BinarySystem.tenToN(decimal, 16); // 255 -> ff
    final binaryStr = BinarySystem.tenToN(decimal, 2); // 255 -> 11111111

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Text(
          '十进制: $decimal\n\n'
              '十六进制: $hexStr\n\n'
              '二进制: $binaryStr',
          textAlign: TextAlign.center,
          style: const TextStyle(
            backgroundColor: Colors.amber,
            color: Colors.blue,
            fontSize: 20,
          ),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

ClearCacheManager 清空缓存

源码

import 'dart:io';
import 'package:path_provider/path_provider.dart';

class ClearCacheManager {
  // 单例
  ClearCacheManager._internal();
  static final ClearCacheManager _instance = ClearCacheManager._internal();
  factory ClearCacheManager() => _instance;

  /// 获取缓存目录路径
  Future<Directory> _getCacheDir() async {
    return await getTemporaryDirectory(); // 对应 iOS 的 NSCachesDirectory
  }

  /// 获取缓存大小(单位:MB)
  Future<double> getCacheSize() async {
    final dir = await _getCacheDir();
    int totalBytes = await _getDirectorySize(dir);
    return totalBytes / (1024 * 1024); // 转为 MB
  }

  /// 递归计算文件夹大小(字节数)
  Future<int> _getDirectorySize(FileSystemEntity entity) async {
    if (entity is File) {
      try {
        return await entity.length();
      } catch (_) {
        return 0;
      }
    }
    if (entity is Directory) {
      int total = 0;
      try {
        final children = entity.listSync();
        for (var child in children) {
          total += await _getDirectorySize(child);
        }
      } catch (_) {}
      return total;
    }
    return 0;
  }

  /// 清除缓存
  Future<void> removeCache() async {
    final dir = await _getCacheDir();
    if (await dir.exists()) {
      try {
        final children = dir.listSync();
        for (var child in children) {
          try {
            if (child is File) {
              await child.delete();
            } else if (child is Directory) {
              await child.delete(recursive: true);
            }
          } catch (_) {}
        }
      } catch (_) {}
    }
  }
}

使用

import 'package:flutter/material.dart';
import 'clear_cache_manager.dart'; // 替换为实际路径

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  double _cacheSize = 0.0;
  bool _loading = false;

  @override
  void initState() {
    super.initState();
    _loadCacheSize();
  }

  Future<void> _loadCacheSize() async {
    final size = await ClearCacheManager().getCacheSize();
    setState(() {
      _cacheSize = double.parse(size.toStringAsFixed(2)); // 保留两位小数
    });
  }

  Future<void> _clearCache() async {
    setState(() {
      _loading = true;
    });

    await ClearCacheManager().removeCache();
    await _loadCacheSize();

    setState(() {
      _loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Cache Manager'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: _loading
            ? const CircularProgressIndicator()
            : Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    '缓存大小:$_cacheSize MB',
                    style: const TextStyle(fontSize: 22),
                  ),
                  const SizedBox(height: 30),
                  ElevatedButton(
                    onPressed: _clearCache,
                    child: const Text('清除缓存'),
                  ),
                ],
              ),
      ),
    );
  }
}

效果

在这里插入图片描述

CountDownManager 倒计时

源码

import 'dart:async';

/// 倒计时与顺计时工具类 (等价于 OC 的 CountDownManager)
class CountDownManager {
  Timer? _timer;

  /// 主动销毁定时器
  void destroyTimer() {
    _timer?.cancel();
    _timer = null;
  }

  /// 倒计时 (秒级时间戳)
  /// [finishTimeStamp] 结束时间戳(10位,单位秒)
  /// [adjust] 校正时间(服务器与本地的时间差,单位秒)
  /// [onTick] 每秒回调,返回时间字符串与是否结束
  void countDownWithFinishTimeStamp(
      int finishTimeStamp, {
        double adjust = 0,
        required void Function(String timeStr, bool isFinish) onTick,
      }) {
    final now = DateTime.now().millisecondsSinceEpoch / 1000 + adjust;
    final remaining = finishTimeStamp - now;
    _countDownWithTimeout(remaining.toInt(), onTick);
  }

  /// 倒计时 (毫秒级时间戳)
  void countDownWithFinishTimeMillisecond(
      int finishTimeStamp, {
        double adjust = 0,
        required void Function(String timeStr, bool isFinish) onTick,
      }) {
    final now = DateTime.now().millisecondsSinceEpoch + adjust * 1000;
    final remaining = (finishTimeStamp - now) / 1000;
    _countDownWithTimeout(remaining.toInt(), onTick);
  }

  /// 顺计时 (秒级)
  void clockwiseHasPassTimeWithStartTimeStamp(
      int startTimeStamp, {
        double adjust = 0,
        required void Function(String dateStr) onTick,
      }) {
    destroyTimer();
    final now = DateTime.now().millisecondsSinceEpoch / 1000 + adjust;
    int elapsed = (now - startTimeStamp).toInt();
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      elapsed++;
      onTick(_formatTime(elapsed));
    });
  }

  /// 顺计时 (毫秒级)
  void clockwiseHasPassTimeWithStartTimeMillisecond(
      int startTimeStamp, {
        double adjust = 0,
        required void Function(String dateStr) onTick,
      }) {
    destroyTimer();
    final now = DateTime.now().millisecondsSinceEpoch + adjust * 1000;
    int elapsed = ((now - startTimeStamp) / 1000).toInt();
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      elapsed++;
      onTick(_formatTime(elapsed));
    });
  }

  /// 从开始时间到结束时间递增 (秒级)
  void countDownHasPassTimeWithStartAndFinishTimeStamp(
      int startTimeStamp,
      int finishTimeStamp, {
        double adjust = 0,
        required void Function(String dateStr, bool isFinish) onTick,
      }) {
    destroyTimer();
    final now = DateTime.now().millisecondsSinceEpoch / 1000 + adjust;
    int elapsed = (now - startTimeStamp).toInt();
    final total = finishTimeStamp - startTimeStamp;

    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      final isFinish = elapsed >= total;
      onTick(_formatTime(elapsed), isFinish);
      if (isFinish) {
        destroyTimer();
      } else {
        elapsed++;
      }
    });
  }

  /// 从开始时间到结束时间递增 (毫秒级)
  void countDownHasPassTimeWithStartAndFinishTimeMillisecond(
      int startTimeStamp,
      int finishTimeStamp, {
        double adjust = 0,
        required void Function(String dateStr, bool isFinish) onTick,
      }) {
    destroyTimer();
    final now = DateTime.now().millisecondsSinceEpoch + adjust * 1000;
    int elapsed = ((now - startTimeStamp) / 1000).toInt();
    final total = ((finishTimeStamp - startTimeStamp) / 1000).toInt();

    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      final isFinish = elapsed >= total;
      onTick(_formatTime(elapsed), isFinish);
      if (isFinish) {
        destroyTimer();
      } else {
        elapsed++;
      }
    });
  }

  /// 倒计时(直接输入秒数)
  void _countDownWithTimeout(
      int timeout,
      void Function(String timeStr, bool isFinish) onTick,
      ) {
    destroyTimer();
    int remaining = timeout;
    if (remaining <= 0) {
      onTick(_formatTime(0), true);
      return;
    }

    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      if (remaining <= 0) {
        destroyTimer();
        onTick(_formatTime(0), true);
      } else {
        onTick(_formatTime(remaining), false);
        remaining--;
      }
    });
  }

  /// 获取当前时间戳(秒级)
  static String getNowTimeTimestampSecond() {
    return (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
  }

  /// 获取当前时间戳(毫秒级)
  static String getNowTimeTimestampMillisecond() {
    return DateTime.now().millisecondsSinceEpoch.toString();
  }

  /// 内部工具:格式化输出 "dd:HH:mm:ss"
  String _formatTime(int seconds) {
    int days = seconds ~/ (3600 * 24);
    int hours = (seconds % (3600 * 24)) ~/ 3600;
    int minutes = (seconds % 3600) ~/ 60;
    int secs = seconds % 60;

    String twoDigits(int n) => n.toString().padLeft(2, '0');
    return "${twoDigits(days)}:${twoDigits(hours)}:${twoDigits(minutes)}:${twoDigits(secs)}";
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/CountDownManager.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  final CountDownManager _countDownManager = CountDownManager();
  String _timeStr = "00:00:00:10"; // 初始显示10秒
  bool _isFinish = false;

  @override
  void initState() {
    super.initState();
    _startCountDown();
  }

  void _startCountDown() {
    // 当前时间戳(秒级)
    final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
    final finish = now + 10; // 10 秒后结束

    _countDownManager.countDownWithFinishTimeStamp(
      finish,
      onTick: (timeStr, isFinish) {
        if (!mounted) return;
        setState(() {
          _timeStr = timeStr;
          _isFinish = isFinish;
        });
      },
    );
  }

  @override
  void dispose() {
    _countDownManager.destroyTimer();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('倒计时示例'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Text(
          _isFinish ? "倒计时结束" : _timeStr,
          textAlign: TextAlign.center,
          style: const TextStyle(
            backgroundColor: Colors.amber,
            color: Colors.blue,
            fontSize: 22,
          ),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

DeviceUtils 设备尺寸信息

源码

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';

class DeviceUtils {
  /// 屏幕宽度
  static double screenWidth(BuildContext context) =>
      MediaQuery.of(context).size.width;

  /// 屏幕高度
  static double screenHeight(BuildContext context) =>
      MediaQuery.of(context).size.height;

  /// 状态栏高度
  static double statusHeight(BuildContext context) =>
      MediaQuery.of(context).padding.top;

  /// 底部安全距离(适配 iPhone X 等刘海屏)
  static double bottomSafeHeight(BuildContext context) =>
      MediaQuery.of(context).padding.bottom;

  /// 导航栏高度 (Flutter 没有原生导航栏,通常自己定义 AppBar 高度)
  static double navigationHeight = kToolbarHeight;

  /// tabbar 高度(49),刘海屏加上底部安全区
  static double tabBarHeight(BuildContext context) =>
      49 + bottomSafeHeight(context);

  /// 顶部安全距离
  static double topSafeHeight(BuildContext context) =>
      statusHeight(context) + navigationHeight;

  /// 是否是刘海屏
  static bool isNotchScreen(BuildContext context) =>
      bottomSafeHeight(context) > 0;

  /// iOS/Android 系统版本
  static String osVersion() => Platform.operatingSystemVersion;

  /// App 版本号
  static Future<String?> appVersion() async {
    // 用 package_info_plus 获取
    // import 'package:package_info_plus/package_info_plus.dart';
    final info = await PackageInfo.fromPlatform();
    return info.version;
  }

  /// Debug 打印
  static void debugLog(String msg) {
    if (kDebugMode) {
      // kDebugMode 在 debug 模式下为 true,release 下自动去掉
      debugPrint('[DEBUG] $msg');
    }
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/DeviceUtils.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  String _appVersion = "";
  String _osVersion = "";

  @override
  void initState() {
    super.initState();
    _loadDeviceInfo();
  }

  Future<void> _loadDeviceInfo() async {
    final version = await DeviceUtils.appVersion();
    final osVer = DeviceUtils.osVersion();

    if (!mounted) return;
    setState(() {
      _appVersion = version ?? "";
      _osVersion = osVer;
    });
  }

  @override
  Widget build(BuildContext context) {
    final width = DeviceUtils.screenWidth(context);
    final height = DeviceUtils.screenHeight(context);
    final status = DeviceUtils.statusHeight(context);
    final bottom = DeviceUtils.bottomSafeHeight(context);
    final isNotch = DeviceUtils.isNotchScreen(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Text(
          '''
          屏幕宽: $width px
          屏幕高: $height px
          
          状态栏高度: $status px
          底部安全区: $bottom px
          是否刘海屏: $isNotch
          
          系统版本: $_osVersion
          App版本: $_appVersion
          ''',
          textAlign: TextAlign.center,
          style: const TextStyle(
            backgroundColor: Colors.amber,
            color: Colors.blue,
            fontSize: 20,
          ),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

Encryp 加密相关

源码

import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:pointycastle/export.dart';

class Encryp {
  static const String gKey = "EQwRjg3NEU5RDA3Q"; // 与OC中保持一致
  static const String gIv = "APs6\$^*(&(5sd1#^"; // 与OC中保持一致

  /// -------------------------------
  /// 🔹 MD5 加密(32位小写)
  /// -------------------------------
  static String md5Lower32(String input) {
    final bytes = utf8.encode(input);
    final digest = md5.convert(bytes);
    return digest.toString(); // 默认小写32位
  }

  /// -------------------------------
  /// 🔹 MD5 加密(32位大写)
  /// -------------------------------
  static String md5Upper32(String input) {
    return md5Lower32(input).toUpperCase();
  }

  /// -------------------------------
  /// 🔹 MD5 加密(16位小写)
  /// -------------------------------
  static String md5Lower16(String input) {
    final full = md5Lower32(input);
    return full.substring(8, 24);
  }

  /// -------------------------------
  /// 🔹 MD5 加密(16位大写)
  /// -------------------------------
  static String md5Upper16(String input) {
    return md5Lower16(input).toUpperCase();
  }

  /// -------------------------------
  /// 🔹 Base64 编码
  /// -------------------------------
  static String base64EncodeData(Uint8List data) {
    return base64.encode(data);
  }

  /// -------------------------------
  /// 🔹 Base64 解码
  /// -------------------------------
  static Uint8List base64DecodeData(String base64Str) {
    return base64.decode(base64Str);
  }

  /// -------------------------------
  /// 🔹 SHA1 加密
  /// -------------------------------
  static String sha1Encrypt(String input) {
    final bytes = utf8.encode(input);
    final digest = sha1.convert(bytes);
    return digest.toString();
  }

  /// -------------------------------
  /// 🔹 AES128 加密(补位)
  /// -------------------------------
  static String aes128Encrypt(String plainText) {
    final key = Uint8List.fromList(utf8.encode(gKey));
    final iv = Uint8List.fromList(utf8.encode(gIv));

    final blockCipher = CBCBlockCipher(AESFastEngine())
      ..init(
        true,
        ParametersWithIV(KeyParameter(key), iv),
      );

    final paddedData = _pkcs7Pad(Uint8List.fromList(utf8.encode(plainText)));

    final cipherText = _processBlocks(blockCipher, paddedData);
    return base64.encode(cipherText);
  }

  /// -------------------------------
  /// 🔹 AES128 解密
  /// -------------------------------
  static String aes128Decrypt(String base64Text) {
    final key = Uint8List.fromList(utf8.encode(gKey));
    final iv = Uint8List.fromList(utf8.encode(gIv));
    final cipherText = base64.decode(base64Text);

    final blockCipher = CBCBlockCipher(AESFastEngine())
      ..init(
        false,
        ParametersWithIV(KeyParameter(key), iv),
      );

    final decrypted = _processBlocks(blockCipher, cipherText);
    final unpadded = _pkcs7Unpad(decrypted);

    return utf8.decode(unpadded);
  }

  /// -------------------------------
  /// 🔹 内部方法:AES 分块处理
  /// -------------------------------
  static Uint8List _processBlocks(BlockCipher cipher, Uint8List input) {
    final output = Uint8List(input.length);
    var offset = 0;
    while (offset < input.length) {
      offset += cipher.processBlock(input, offset, output, offset);
    }
    return output;
  }

  /// -------------------------------
  /// 🔹 PKCS7 Padding
  /// -------------------------------
  static Uint8List _pkcs7Pad(Uint8List data) {
    final padLen = 16 - (data.length % 16);
    return Uint8List.fromList(data + List<int>.filled(padLen, padLen));
  }

  /// -------------------------------
  /// 🔹 PKCS7 Unpadding
  /// -------------------------------
  static Uint8List _pkcs7Unpad(Uint8List data) {
    final padLen = data.last;
    return data.sublist(0, data.length - padLen);
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/Encryp.dart';
import 'dart:typed_data';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  String result = "";

  @override
  void initState() {
    super.initState();
    _testEncrypt();
  }

  void _testEncrypt() {
    const source = "HelloFlutter";

    String md5_32_lower = Encryp.md5Lower32(source);
    String md5_32_upper = Encryp.md5Upper32(source);
    String md5_16_lower = Encryp.md5Lower16(source);
    String md5_16_upper = Encryp.md5Upper16(source);
    String sha1_text = Encryp.sha1Encrypt(source);

    String aesEncryptText = Encryp.aes128Encrypt(source);
    String aesDecryptText = Encryp.aes128Decrypt(aesEncryptText);

    String base64Text = Encryp.base64EncodeData(Uint8List.fromList(source.codeUnits));
    String base64DecodeText = String.fromCharCodes(
      Encryp.base64DecodeData(base64Text),
    );

    result = '''
原文: $source

MD5_32 小写: $md5_32_lower
MD5_32 大写: $md5_32_upper
MD5_16 小写: $md5_16_lower
MD5_16 大写: $md5_16_upper

SHA1: $sha1_text

AES 加密: $aesEncryptText
AES 解密: $aesDecryptText

Base64 编码: $base64Text
Base64 解码: $base64DecodeText
''';

    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('加密示例'),
        backgroundColor: Colors.blue[300],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Text(
          result,
          style: const TextStyle(fontSize: 15, color: Colors.black),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

FHXHelp

源码

import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/services.dart';
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';


class FHXHelp {
  /// 拨打电话
  static Future<void> makePhoneCall(String tel) async {
    final Uri url = Uri(scheme: 'tel', path: tel);
    if (await canLaunchUrl(url)) {
      await launchUrl(url);
    } else {
      throw '无法拨打电话: $tel';
    }
  }

  /// 判断手机号运营商类型
  static String judgePhoneNumType(String mobile) {
    final regCM = RegExp(r'^1(34[0-8]|(3[5-9]|5[017-9]|8[278])\d)\d{7}$');
    final regCU = RegExp(r'^1(3[0-2]|4[5]|5[256]|7[6]|8[56])\d{8}$');
    final regCT = RegExp(r'^1((33|53|8[09])[0-9]|349)\d{7}$');

    if (regCM.hasMatch(mobile)) return "中国移动";
    if (regCU.hasMatch(mobile)) return "中国联通";
    if (regCT.hasMatch(mobile)) return "中国电信";
    return "未知";
  }

  /// 时间字符串 -> 时间戳
  static String timeStringToTimestamp(String time) {
    final dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss");
    final date = dateFormat.parse(time);
    return (date.millisecondsSinceEpoch / 1000).toInt().toString();
  }

  /// 时间戳 -> 时间字符串
  static String timestampToTimeString(String timestamp) {
    final date = DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000);
    final dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss");
    return dateFormat.format(date);
  }

  /// 获取 [年,月,日]
  static List<String> getYearMonthDay(String time) {
    return [time.substring(0, 4), time.substring(5, 7), time.substring(8, 10)];
  }

  /// 今天和明天的日期
  static List<List<String>> recentDates() {
    final now = DateTime.now();
    final tomorrow = now.add(const Duration(days: 1));
    final fmt = DateFormat("yyyy-MM-dd HH:mm:ss");
    return [
      getYearMonthDay(fmt.format(now)),
      getYearMonthDay(fmt.format(tomorrow))
    ];
  }

  /// 当前界面截图(需要 BuildContext)
  static Future<Uint8List?> captureWidget(GlobalKey key) async {
    try {
      RenderRepaintBoundary boundary =
      key.currentContext!.findRenderObject() as RenderRepaintBoundary;
      // 注意:这里使用 ui.Image
      ui.Image image = await boundary.toImage(pixelRatio: 3.0);
      ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
      return byteData?.buffer.asUint8List();
    } catch (e) {
      debugPrint("截图失败: $e");
      return null;
    }
  }


  /// 去掉HTML标签
  static String removeHtmlTags(String html) {
    return html.replaceAll(RegExp(r'<[^>]*>|{[^}]*}'), '');
  }

  /// 随机数
  static int randomInt(int from, int to) {
    final rnd = Random();
    return from + rnd.nextInt(to - from + 1);
  }

  /// 添加边框
  static BoxDecoration border({
    bool top = false,
    bool left = false,
    bool bottom = false,
    bool right = false,
    Color color = Colors.black,
    double width = 1.0,
  }) {
    return BoxDecoration(
      border: Border(
        top: top ? BorderSide(color: color, width: width) : BorderSide.none,
        left: left ? BorderSide(color: color, width: width) : BorderSide.none,
        bottom: bottom ? BorderSide(color: color, width: width) : BorderSide.none,
        right: right ? BorderSide(color: color, width: width) : BorderSide.none,
      ),
    );
  }

  /// 数组去重
  static List<T> unique<T>(List<T> list) => list.toSet().toList();

  /// 图片缩放
  static Future<Image> scaleImage(Image image, Size size) async {
    return Image(
      image: image.image,
      width: size.width,
      height: size.height,
      fit: BoxFit.cover,
    );
  }

  /// 格式化千分位
  static String formatThousands(String number) {
    final n = double.tryParse(number) ?? 0.0;
    final formatter = NumberFormat("#,##0.00", "en_US");
    return formatter.format(n);
  }

  /// 不四舍五入保留小数位
  static String noRound(double value, int fractionDigits) {
    final factor = pow(10, fractionDigits);
    final truncated = (value * factor).truncate() / factor;
    return truncated.toStringAsFixed(fractionDigits);
  }

  /// 获取手机信息
  static Future<Map<String, dynamic>> getUserPhoneInfo() async {
    final deviceInfo = DeviceInfoPlugin();
    Map<String, dynamic> data = {};

    if (Platform.isIOS) {
      final ios = await deviceInfo.iosInfo;
      data = {
        "mobiletype": ios.utsname.machine,
        "sysversion": ios.systemVersion,
        "logintype": "IOS",
        "appversion": ios.systemName,
        "devicenumber": ios.identifierForVendor,
      };
    } else if (Platform.isAndroid) {
      final android = await deviceInfo.androidInfo;
      data = {
        "mobiletype": android.model,
        "sysversion": android.version.release,
        "logintype": "Android",
        "appversion": android.version.sdkInt.toString(),
        "devicenumber": android.id,
      };
    }

    return data;
  }

  /// 手机号格式化 138 8888 9999
  static String formatPhone(String phone) {
    if (phone.length != 11) return phone;
    return "${phone.substring(0, 3)} ${phone.substring(3, 7)} ${phone.substring(7)}";
  }

  /// 银行卡中间空格
  static String formatCard(String str) {
    final buffer = StringBuffer();
    for (int i = 0; i < str.length; i++) {
      buffer.write(str[i]);
      if ((i + 1) % 4 == 0 && i != str.length - 1) buffer.write(' ');
    }
    return buffer.toString();
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/FHXHelp.dart';

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  String randomNumber = '';
  String formattedPhone = '';
  String timestampNow = '';
  String timestampToStr = '';
  Map<String, dynamic>? deviceInfo;

  @override
  void initState() {
    super.initState();
    _testFHXHelp();
  }

  Future<void> _testFHXHelp() async {
    randomNumber = FHXHelp.randomInt(1000, 9999).toString();
    formattedPhone = FHXHelp.formatPhone("13888889999");
    timestampNow = DateTime.now().millisecondsSinceEpoch.toString();
    timestampToStr = FHXHelp.timestampToTimeString(timestampNow.substring(0,10));
    deviceInfo = await FHXHelp.getUserPhoneInfo();
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FHXHelp Demo'),
        backgroundColor: Colors.blue[300],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Text(
          '''
随机数: $randomNumber
手机号格式化: $formattedPhone
当前时间戳: $timestampNow
时间戳转时间: $timestampToStr
设备信息: ${deviceInfo ?? '加载中...'}
          ''',
          style: const TextStyle(
            backgroundColor: Colors.amber,
            color: Colors.blue,
            fontSize: 16,
          ),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

FHXImage 图片操作(颜色转图片)

源码

import 'dart:ui' as ui;
import 'package:flutter/material.dart';

class FHXImage {
  /// 根据颜色生成 1x1 图片
  static Future<ui.Image> createImageWithColor(Color color) async {
    final recorder = ui.PictureRecorder();
    final canvas = Canvas(recorder);
    final paint = Paint()..color = color;
    canvas.drawRect(Rect.fromLTWH(0, 0, 1, 1), paint);
    final picture = recorder.endRecording();
    // 注意:toImage 返回 Future<ui.Image>
    return await picture.toImage(1, 1);
  }

  /// 根据颜色和尺寸生成图片
  static Future<ui.Image> createImageWithColorAndRect(Color color, Size size) async {
    final recorder = ui.PictureRecorder();
    final canvas = Canvas(recorder);
    final paint = Paint()..color = color;
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
    final picture = recorder.endRecording();
    return await picture.toImage(size.width.toInt(), size.height.toInt());
  }

  /// 图片圆形化
  static Widget circleImage(ImageProvider imageProvider,
      {double? width, double? height}) {
    return ClipOval(
      child: Image(
        image: imageProvider,
        width: width,
        height: height,
        fit: BoxFit.cover,
      ),
    );
  }

  /// 图片拉伸
  static Widget stretchableImage(ImageProvider imageProvider,
      {double? width, double? height}) {
    return Image(
      image: imageProvider,
      width: width,
      height: height,
      fit: BoxFit.fill,
    );
  }

  /// 加载本地 GIF
  static Image loadGif(String assetName, {double? width, double? height}) {
    return Image.asset(
      assetName,
      width: width,
      height: height,
    );
  }

  /// 加载网络 GIF
  static Image loadNetworkGif(String url, {double? width, double? height}) {
    return Image.network(
      url,
      width: width,
      height: height,
    );
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/FHXImage.dart';
import 'dart:ui' as ui; 

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  ui.Image? colorImage;

  @override
  void initState() {
    super.initState();
    _generateColorImage();
  }

  Future<void> _generateColorImage() async {
    final img = await FHXImage.createImageWithColorAndRect(
        Colors.red, const Size(100, 100));
    setState(() {
      colorImage = img;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FHXImage Demo'),
        backgroundColor: Colors.blue[300],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            const Text(
              '原始文本示例',
              style: TextStyle(
                backgroundColor: Colors.amber,
                color: Colors.blue,
                fontSize: 20,
              ),
            ),
            const SizedBox(height: 20),

            // 纯色图片显示
            if (colorImage != null)
              SizedBox(
                width: 100,
                height: 100,
                child: RawImage(
                  image: colorImage,
                ),
              )
            else
              const CircularProgressIndicator(),
            const SizedBox(height: 20),

            // 圆形图片
            FHXImage.circleImage(
              const NetworkImage(
                  'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
              width: 100,
              height: 100,
            ),
            const SizedBox(height: 20),

            // 拉伸图片
            FHXImage.stretchableImage(
              const NetworkImage(
                  'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
              width: 150,
              height: 100,
            ),
            const SizedBox(height: 20),

            // 网络 GIF
            FHXImage.loadNetworkGif(
              'https://media.giphy.com/media/ICOgUNjpvO0PC/giphy.gif',
              width: 150,
              height: 150,
            ),
            const SizedBox(height: 20),

            // 本地 GIF(确保 assets 已添加到 pubspec.yaml)
            // FHXImage.loadGif('assets/local.gif', width: 150, height: 150),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

FHXObjectExtension 字符串是否为null

源码

extension FHXObjectExtension on Object? {
  /// 判断对象是否为空
  bool get isNull {
    final value = this;
    if (value == null) return true;

    if (value is String) {
      final str = value.trim();
      return str.isEmpty ||
          str.toLowerCase() == 'null' ||
          str == '(null)' ||
          str == '<null>';
    }

    if (value is Iterable) {
      return value.isEmpty;
    }

    if (value is Map) {
      return value.isEmpty;
    }

    return false;
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/FHXObjectExtension.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  String? testString;
  List<int>? testList;
  Map<String, dynamic>? testMap;

  @override
  void initState() {
    super.initState();
    testString = "  "; // 测试空字符串
    testList = [];
    testMap = {};
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'testString is null: ${testString.isNull}',
              style: const TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 10),
            Text(
              'testList is null: ${testList.isNull}',
              style: const TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 10),
            Text(
              'testMap is null: ${testMap.isNull}',
              style: const TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 20),
            const Text(
              'i am is text',
              textAlign: TextAlign.center,
              style: TextStyle(
                backgroundColor: Colors.amber,
                color: Colors.blue,
                fontSize: 20,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

(没效果)GetVC 获取当前控制器

源码

import 'package:flutter/material.dart';

class GetVC {
  /// 获取当前全局Context(需在MaterialApp中绑定navigatorKey)
  static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  /// 获取当前NavigatorState
  static NavigatorState? get navigator => navigatorKey.currentState;

  /// 获取当前Context
  static BuildContext? get context => navigatorKey.currentContext;

  /// 获取根路由(相当于RootViewController)
  static Widget? get rootWidget {
    final nav = navigator;
    if (nav == null) return null;
    return nav.widget;
  }

  /// 获取当前显示的Route
  static Route? get currentRoute {
    final nav = navigator;
    if (nav == null) return null;
    Route? current;
    nav.popUntil((route) {
      current = route;
      return true;
    });
    return current;
  }

  /// 获取当前页面Widget(相当于当前控制器)
  static Widget? get currentPage {
    final route = currentRoute;
    if (route is MaterialPageRoute) {
      return route.builder(navigatorKey.currentContext!);
    }
    return null;
  }

  /// 跳转到指定页面(等价于push)
  static Future<dynamic> push(Widget page) async {
    return navigator?.push(MaterialPageRoute(builder: (_) => page));
  }

  /// 返回上一个页面(等价于pop)
  static void pop<T extends Object?>([T? result]) {
    navigator?.pop(result);
  }

  /// 返回到根页面
  static void popToRoot() {
    navigator?.popUntil((route) => route.isFirst);
  }

  /// 通过GlobalKey获取Widget的State
  static T? getStateFromKey<T extends State>(GlobalKey<T> key) {
    return key.currentState;
  }

  /// 通过GlobalKey获取Widget
  static Widget? getWidgetFromKey(GlobalKey key) {
    return key.currentWidget;
  }

  /// 遍历Widget树(递归打印Widget结构)
  static void traverseWidgetTree(Element element, {int depth = 0}) {
    final indent = ' ' * depth;
    debugPrint('$indent${element.widget.runtimeType}');
    element.visitChildElements((child) {
      traverseWidgetTree(child, depth: depth + 2);
    });
  }

  /// 查找Widget树中指定类型的Widget
  static void findWidgetByType<T extends Widget>(Element element, List<T> result) {
    if (element.widget is T) {
      result.add(element.widget as T);
    }
    element.visitChildElements((child) {
      findWidgetByType(child, result);
    });
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/GetVC.dart';
import 'package:flutterdemols/NormalDetail.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'i am is text',
              textAlign: TextAlign.center,
              style: TextStyle(
                backgroundColor: Colors.amber,
                color: Colors.blue,
                fontSize: 20,
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // 使用 GetVC 跳转页面
                GetVC.push(NormalDetail());
              },
              child: const Text('跳转到 SecondPage'),
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                // 获取当前页面 Widget
                final current = GetVC.currentPage;
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('当前页面: ${current.runtimeType}')),
                );
              },
              child: const Text('获取当前页面 Widget'),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

GradientUtil view背景渐变

源码

import 'dart:ui';
import 'package:flutter/material.dart';

/// 渐变方向定义,对应 Objective-C 中的 ZQGradientChangeDirection
enum GradientDirection {
  level, // 水平渐变
  vertical, // 垂直渐变
  upwardDiagonal, // 主对角线方向渐变
  downwardDiagonal, // 副对角线方向渐变
}

/// Flutter 等价工具类
class GradientUtil {
  /// 生成线性渐变 Shader
  static Shader createGradientShader({
    required Size size,
    required GradientDirection direction,
    required Color startColor,
    required Color endColor,
  }) {
    Alignment begin = Alignment.topLeft;
    Alignment end = Alignment.bottomRight;

    switch (direction) {
      case GradientDirection.level:
        begin = Alignment.centerLeft;
        end = Alignment.centerRight;
        break;
      case GradientDirection.vertical:
        begin = Alignment.topCenter;
        end = Alignment.bottomCenter;
        break;
      case GradientDirection.upwardDiagonal:
        begin = Alignment.bottomLeft;
        end = Alignment.topRight;
        break;
      case GradientDirection.downwardDiagonal:
        begin = Alignment.topLeft;
        end = Alignment.bottomRight;
        break;
    }

    return LinearGradient(
      begin: begin,
      end: end,
      colors: [startColor, endColor],
    ).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
  }

  /// 生成一个可直接用作装饰背景的 BoxDecoration
  static BoxDecoration createGradientBox({
    required GradientDirection direction,
    required Color startColor,
    required Color endColor,
  }) {
    Alignment begin = Alignment.topLeft;
    Alignment end = Alignment.bottomRight;

    switch (direction) {
      case GradientDirection.level:
        begin = Alignment.centerLeft;
        end = Alignment.centerRight;
        break;
      case GradientDirection.vertical:
        begin = Alignment.topCenter;
        end = Alignment.bottomCenter;
        break;
      case GradientDirection.upwardDiagonal:
        begin = Alignment.bottomLeft;
        end = Alignment.topRight;
        break;
      case GradientDirection.downwardDiagonal:
        begin = Alignment.topLeft;
        end = Alignment.bottomRight;
        break;
    }

    return BoxDecoration(
      gradient: LinearGradient(
        begin: begin,
        end: end,
        colors: [startColor, endColor],
      ),
    );
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/GradientUtil.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {

  // 文字渐变改法
  Widget gradientText(String text, TextStyle style, GradientDirection direction, Color start, Color end) {
    Alignment begin = Alignment.topLeft;
    Alignment endAlign = Alignment.bottomRight;

    switch (direction) {
      case GradientDirection.level:
        begin = Alignment.centerLeft;
        endAlign = Alignment.centerRight;
        break;
      case GradientDirection.vertical:
        begin = Alignment.topCenter;
        endAlign = Alignment.bottomCenter;
        break;
      case GradientDirection.upwardDiagonal:
        begin = Alignment.bottomLeft;
        endAlign = Alignment.topRight;
        break;
      case GradientDirection.downwardDiagonal:
        begin = Alignment.topLeft;
        endAlign = Alignment.bottomRight;
        break;
    }

    return ShaderMask(
      shaderCallback: (bounds) {
        return LinearGradient(
          begin: begin,
          end: endAlign,
          colors: [start, end],
        ).createShader(Rect.fromLTWH(0, 0, bounds.width, bounds.height));
      },
      blendMode: BlendMode.srcIn,
      child: Text(text, style: style),
    );
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Gradient Demo'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 背景渐变容器
            Container(
              width: 250,
              height: 100,
              decoration: GradientUtil.createGradientBox(
                direction: GradientDirection.level,
                startColor: Colors.purple,
                endColor: Colors.orange,
              ),
              alignment: Alignment.center,
              child: const Text(
                '背景渐变',
                style: TextStyle(
                  fontSize: 20,
                  color: Colors.white,
                ),
              ),
            ),
            const SizedBox(height: 40),

            // 文字渐变
            gradientText(
              '文字渐变',
              const TextStyle(
                fontSize: 40,
                fontWeight: FontWeight.bold,
                color: Colors.white, // color 会被 ShaderMask 替换
              ),
              GradientDirection.downwardDiagonal,
              Colors.red,
              Colors.yellow,
            ),

          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

GradualChange 渐变文字

源码

import 'package:flutter/material.dart';

/// 渐变文字或控件封装类(等效于 GradualChange)
class GradualChange {
  /// 对应 Objective-C 中的
  /// +(void)TextGradientview:bgVIew:gradientColors:gradientStartPoint:endPoint:
  static Widget textGradientView({
    required Widget child,
    required List<Color> colors,
    AlignmentGeometry startPoint = Alignment.centerLeft,
    AlignmentGeometry endPoint = Alignment.centerRight,
  }) {
    return ShaderMask(
      shaderCallback: (bounds) {
        return LinearGradient(
          colors: colors,
          begin: startPoint,
          end: endPoint,
        ).createShader(Rect.fromLTWH(0, 0, bounds.width, bounds.height));
      },
      blendMode: BlendMode.srcIn, // 保留文字本身的透明度,只让颜色被渐变替换
      child: child,
    );
  }

  /// 对应 Objective-C 中的
  /// +(void)TextGradientControl:bgVIew:gradientColors:gradientStartPoint:endPoint:
  static Widget textGradientControl({
    required Widget child,
    required List<Color> colors,
    AlignmentGeometry startPoint = Alignment.centerLeft,
    AlignmentGeometry endPoint = Alignment.centerRight,
  }) {
    // 控件其实和文字处理方式一致,只是 child 可以是任意按钮类控件
    return ShaderMask(
      shaderCallback: (bounds) {
        return LinearGradient(
          colors: colors,
          begin: startPoint,
          end: endPoint,
        ).createShader(Rect.fromLTWH(0, 0, bounds.width, bounds.height));
      },
      blendMode: BlendMode.srcIn,
      child: child,
    );
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/GradualChange.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GradualChange Demo'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 背景渐变容器
            Container(
              width: 250,
              height: 100,
              decoration: const BoxDecoration(
                gradient: LinearGradient(
                  colors: [Colors.purple, Colors.orange],
                  begin: Alignment.centerLeft,
                  end: Alignment.centerRight,
                ),
              ),
              alignment: Alignment.center,
              child: const Text(
                '背景渐变',
                style: TextStyle(fontSize: 20, color: Colors.white),
              ),
            ),
            const SizedBox(height: 40),

            // 文字渐变
            GradualChange.textGradientView(
              colors: [Colors.red, Colors.yellow],
              startPoint: Alignment.topLeft,
              endPoint: Alignment.bottomRight,
              child: const Text(
                '文字渐变',
                style: TextStyle(
                  fontSize: 40,
                  fontWeight: FontWeight.bold,
                  color: Colors.white, // color 会被 ShaderMask 替换
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

ImageSize URL获取图片尺寸

源码

import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:async';
import 'package:http/http.dart' as http;

class ImageSize {
  static Future<({int width, int height})?> getImageSize(String url) async {
    final uri = Uri.tryParse(url);
    if (uri == null) return null;

    final extension = uri.path.split('.').last.toLowerCase();
    if (extension.contains('png')) {
      return await _getPngSize(uri);
    } else if (extension.contains('gif')) {
      return await _getGifSize(uri);
    } else if (extension.contains('jpg') || extension.contains('jpeg')) {
      return await _getJpgSize(uri);
    } else {
      return await _getImageSizeByFullDownload(uri);
    }
  }

  static Future<({int width, int height})?> _getPngSize(Uri uri) async {
    try {
      final response = await http.get(uri, headers: {'Range': 'bytes=16-23'});
      if (response.statusCode == 206 || response.statusCode == 200) {
        final data = response.bodyBytes;
        if (data.length == 8) {
          final width = _readUint32(data, 0);
          final height = _readUint32(data, 4);
          return (width: width, height: height);
        }
      }
    } catch (_) {}
    return null;
  }

  static Future<({int width, int height})?> _getGifSize(Uri uri) async {
    try {
      final response = await http.get(uri, headers: {'Range': 'bytes=6-9'});
      if (response.statusCode == 206 || response.statusCode == 200) {
        final data = response.bodyBytes;
        if (data.length == 4) {
          final width = _readUint16(data, 0);
          final height = _readUint16(data, 2);
          return (width: width, height: height);
        }
      }
    } catch (_) {}
    return null;
  }

  static Future<({int width, int height})?> _getJpgSize(Uri uri) async {
    try {
      final response = await http.get(uri, headers: {'Range': 'bytes=0-209'});
      if (response.statusCode == 206 || response.statusCode == 200) {
        final data = response.bodyBytes;
        if (data.length < 210) return null;

        int offset = 0;
        while (offset < data.length - 9) {
          if (data[offset] == 0xFF &&
              (data[offset + 1] >= 0xC0 && data[offset + 1] <= 0xC3)) {
            final height = (data[offset + 5] << 8) + data[offset + 6];
            final width = (data[offset + 7] << 8) + data[offset + 8];
            return (width: width, height: height);
          }
          offset++;
        }
      }
    } catch (_) {}
    return null;
  }

  /// ✅ 修正后的版本(Flutter 3.22+)
  static Future<({int width, int height})?> _getImageSizeByFullDownload(Uri uri) async {
    try {
      final response = await http.get(uri);
      if (response.statusCode == 200) {
        final bytes = response.bodyBytes;

        // 使用 Completer 等待回调完成
        final completer = Completer<({int width, int height})>();

        ui.decodeImageFromList(bytes, (ui.Image img) {
          completer.complete((width: img.width, height: img.height));
        });

        return completer.future;
      }
    } catch (_) {}
    return null;
  }

  static int _readUint32(Uint8List data, int offset) {
    return (data[offset] << 24) |
    (data[offset + 1] << 16) |
    (data[offset + 2] << 8) |
    (data[offset + 3]);
  }

  static int _readUint16(Uint8List data, int offset) {
    return data[offset] + (data[offset + 1] << 8);
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/ImageSize.dart';

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  String imageUrl =
      'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg';
  int? width;
  int? height;
  bool loading = false;

  Future<void> _fetchImageSize() async {
    setState(() {
      loading = true;
    });
    final size = await ImageSize.getImageSize(imageUrl);
    setState(() {
      width = size?.width;
      height = size?.height;
      loading = false;
    });
  }

  @override
  void initState() {
    super.initState();
    _fetchImageSize();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Image Size Demo'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: loading
            ? const CircularProgressIndicator()
            : Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '图片 URL:\n$imageUrl',
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 16),
            ),
            const SizedBox(height: 20),
            Text(
              width != null && height != null
                  ? '图片尺寸: ${width} x ${height}'
                  : '获取尺寸失败',
              style: const TextStyle(
                fontSize: 20,
                color: Colors.blue,
                backgroundColor: Colors.amber,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

ImageWaterMark 图片加水印

源码

import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'dart:typed_data';
import 'dart:async';

class ImageWaterMark {
  /// 图片加图片水印(指定位置和透明度)
  static Future<ui.Image> imageWaterMarkWithImage(
      ui.Image baseImage,
      ui.Image watermark,
      Offset point,
      double alpha,
      ) async {
    final recorder = ui.PictureRecorder();
    final canvas = Canvas(recorder);

    // 绘制原图
    canvas.drawImage(baseImage, Offset.zero, Paint());

    // 绘制水印
    final paint = Paint()..color = Colors.white.withOpacity(alpha);
    canvas.drawImage(watermark, point, paint);

    final picture = recorder.endRecording();
    return picture.toImage(baseImage.width, baseImage.height);
  }

  /// 图片加文字水印(指定位置和属性)
  static Future<ui.Image> imageWaterMarkWithString(
      ui.Image baseImage,
      String text,
      Offset point,
      TextStyle style,
      ) async {
    final recorder = ui.PictureRecorder();
    final canvas = Canvas(recorder);

    // 绘制原图
    canvas.drawImage(baseImage, Offset.zero, Paint());

    // 绘制文字
    final textPainter = TextPainter(
      text: TextSpan(text: text, style: style),
      textDirection: TextDirection.ltr,
    );
    textPainter.layout(); // 计算文字大小
    textPainter.paint(canvas, point);

    final picture = recorder.endRecording();
    return picture.toImage(baseImage.width, baseImage.height);
  }

  /// 图片加文字和图片水印(指定位置和透明度)
  static Future<ui.Image> imageWaterMarkWithStringAndImage(
      ui.Image baseImage,
      {String? text,
        Offset? textPoint,
        TextStyle? textStyle,
        ui.Image? watermark,
        Offset? imagePoint,
        double alpha = 1.0}) async {
    final recorder = ui.PictureRecorder();
    final canvas = Canvas(recorder);

    // 绘制原图
    canvas.drawImage(baseImage, Offset.zero, Paint());

    // 绘制水印图片
    if (watermark != null && imagePoint != null) {
      final paint = Paint()..color = Colors.white.withOpacity(alpha);
      canvas.drawImage(watermark, imagePoint, paint);
    }

    // 绘制文字
    if (text != null && textPoint != null && textStyle != null) {
      final textPainter = TextPainter(
        text: TextSpan(text: text, style: textStyle),
        textDirection: TextDirection.ltr,
      );
      textPainter.layout();
      textPainter.paint(canvas, textPoint);
    }

    final picture = recorder.endRecording();
    return picture.toImage(baseImage.width, baseImage.height);
  }

  /// 将ui.Image转成Uint8List(方便保存或展示)
  static Future<Uint8List> imageToBytes(ui.Image image) async {
    final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    return byteData!.buffer.asUint8List();
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/ImageWaterMark.dart';
import 'dart:ui' as ui;
import 'package:flutter/services.dart' show rootBundle;
import 'dart:async';

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  ui.Image? baseImage;
  ui.Image? watermarkedImage;
  bool loading = true;

  @override
  void initState() {
    super.initState();
    _loadImage();
  }

  /// 从 assets 加载图片
  Future<ui.Image> _loadUiImage(String assetPath) async {
    final data = await rootBundle.load(assetPath);
    final bytes = data.buffer.asUint8List();
    final completer = Completer<ui.Image>();
    ui.decodeImageFromList(bytes, (ui.Image img) {
      completer.complete(img);
    });
    return completer.future;
  }

  Future<void> _loadImage() async {
    // 加载 base 图片
    baseImage = await _loadUiImage('assets/images/image.png'); // 确保你的 assets 已添加到 pubspec.yaml

    // 添加文字水印
    watermarkedImage = await ImageWaterMark.imageWaterMarkWithString(
      baseImage!,
      'Hello Flutter',
      const Offset(20, 20),
      const TextStyle(
        color: Colors.red,
        fontSize: 30,
        fontWeight: FontWeight.bold,
      ),
    );

    setState(() {
      loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Watermark Demo'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: loading
            ? const CircularProgressIndicator()
            : SizedBox(
          width: watermarkedImage!.width.toDouble(),
          height: watermarkedImage!.height.toDouble(),
          child: RawImage(
            image: watermarkedImage,
            fit: BoxFit.contain,
          ),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

IOSDeviceCheck 判断iPhone机型

源码

import 'package:flutter/material.dart';

class IOSDeviceCheck {
  static Size _physicalSize(BuildContext context) {
    final mq = MediaQuery.of(context);
    return Size(
      mq.size.width * mq.devicePixelRatio,
      mq.size.height * mq.devicePixelRatio,
    );
  }

  static bool isIphone5(BuildContext context) =>
      _equalSize(context, const Size(640, 1136));

  static bool isIphone5S(BuildContext context) =>
      _equalSize(context, const Size(640, 1136));

  static bool isIphone5C(BuildContext context) =>
      _equalSize(context, const Size(640, 1136));

  static bool isIphoneSE(BuildContext context) =>
      _equalSize(context, const Size(640, 1136));

  static bool isIphoneSE2(BuildContext context) =>
      _equalSize(context, const Size(750, 1334));

  static bool isIphone6(BuildContext context) =>
      _equalSize(context, const Size(750, 1334));

  static bool isIphone6S(BuildContext context) =>
      _equalSize(context, const Size(750, 1334));

  static bool isIphone7(BuildContext context) =>
      _equalSize(context, const Size(750, 1334));

  static bool isIphone8(BuildContext context) =>
      _equalSize(context, const Size(750, 1334));

  static bool isIphone6Plus(BuildContext context) =>
      _equalSize(context, const Size(1242, 2208));

  static bool isIphone6SPlus(BuildContext context) =>
      _equalSize(context, const Size(1242, 2208));

  static bool isIphone7Plus(BuildContext context) =>
      _equalSize(context, const Size(1242, 2208));

  static bool isIphone8Plus(BuildContext context) =>
      _equalSize(context, const Size(1242, 2208));

  static bool isIphoneX(BuildContext context) =>
      _equalSize(context, const Size(1125, 2436));

  static bool isIphoneXR(BuildContext context) =>
      _equalSize(context, const Size(828, 1792));

  static bool isIphoneXS(BuildContext context) =>
      _equalSize(context, const Size(1125, 2436));

  static bool isIphoneXSMax(BuildContext context) =>
      _equalSize(context, const Size(1242, 2688));

  static bool isIphone11(BuildContext context) =>
      _equalSize(context, const Size(828, 1792));

  static bool isIphone11Pro(BuildContext context) =>
      _equalSize(context, const Size(1125, 2436));

  static bool isIphone11ProMax(BuildContext context) =>
      _equalSize(context, const Size(1242, 2688));

  static bool isIphone12(BuildContext context) =>
      _equalSize(context, const Size(1170, 2532));

  static bool isIphone12Pro(BuildContext context) =>
      _equalSize(context, const Size(1170, 2532));

  static bool isIphone12ProMax(BuildContext context) =>
      _equalSize(context, const Size(1284, 2778));

  static bool isIphone12Mini(BuildContext context) =>
      _equalSize(context, const Size(1080, 2340));

  static bool isIphone13(BuildContext context) =>
      _equalSize(context, const Size(1170, 2532));

  static bool isIphone13Pro(BuildContext context) =>
      _equalSize(context, const Size(1170, 2532));

  static bool isIphone13ProMax(BuildContext context) =>
      _equalSize(context, const Size(1284, 2778));

  static bool isIphone13Mini(BuildContext context) =>
      _equalSize(context, const Size(1080, 2340));

  /// 工具方法:对比物理分辨率
  static bool _equalSize(BuildContext context, Size target) {
    final actual = _physicalSize(context);
    return actual.width.round() == target.width.round() &&
        actual.height.round() == target.height.round();
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/IOSDeviceCheck.dart';

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  String deviceType = '';

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _checkDevice();
  }

  void _checkDevice() {
    if (IOSDeviceCheck.isIphone13(context)) {
      deviceType = 'iPhone 13';
    } else if (IOSDeviceCheck.isIphone13ProMax(context)) {
      deviceType = 'iPhone 13 Pro Max';
    } else if (IOSDeviceCheck.isIphone12(context)) {
      deviceType = 'iPhone 12';
    } else {
      deviceType = 'Other iPhone';
    }
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Device Check Demo'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Text(
          'Detected device: $deviceType',
          textAlign: TextAlign.center,
          style: const TextStyle(
            backgroundColor: Colors.amber,
            color: Colors.blue,
            fontSize: 20,
          ),
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

Jailbreak 检测手机是否越狱,模拟器

源码

import 'dart:io';
import 'package:url_launcher/url_launcher.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/services.dart';

class Jailbreak {
  static const _platform = MethodChannel('com.example.jailbreak'); // iOS原生通道

  /// 主检测入口
  static Future<bool> isJailBreak() async {
    if (await _isSimulator()) return true;
    if (await _isJailBreakWithFile()) return true;
    if (await _isJailBreakWithOpenCydia()) return true;
    if (await _isJailBreakWithPath()) return true;
    if (await _isJailBreakWithReadAppList()) return true;
    return false;
  }

  /// 检查模拟器
  static Future<bool> _isSimulator() async {
    final deviceInfo = DeviceInfoPlugin();
    final iosInfo = await deviceInfo.iosInfo;
    final name = iosInfo.name?.toLowerCase() ?? '';
    return name.contains('simulator') || !Platform.isIOS;
  }

  /// 检查常见越狱文件
  static Future<bool> _isJailBreakWithFile() async {
    final List<String> paths = [
      '/Applications/Cydia.app',
      '/Library/MobileSubstrate/MobileSubstrate.dylib',
      '/bin/bash',
      '/usr/sbin/sshd',
      '/etc/apt',
    ];

    for (final path in paths) {
      if (File(path).existsSync()) {
        return true;
      }
    }
    return false;
  }

  /// 检查能否打开 Cydia
  static Future<bool> _isJailBreakWithOpenCydia() async {
    final uri = Uri.parse('cydia://package/com.example.package');
    return await canLaunchUrl(uri);
  }

  /// 检查能否访问系统级目录
  static Future<bool> _isJailBreakWithReadAppList() async {
    try {
      final dir = Directory('/User/Applications/');
      return dir.existsSync();
    } catch (_) {
      return false;
    }
  }

  /// 检查环境变量(需要iOS原生支持)
  static Future<bool> _isJailBreakWithPath() async {
    try {
      final result = await _platform.invokeMethod('isJailBreakWithPath');
      return result == true;
    } catch (_) {
      return false;
    }
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/Jailbreak.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  bool? isJailBreak;

  @override
  void initState() {
    super.initState();
    _checkJailBreak();
  }

  Future<void> _checkJailBreak() async {
    final result = await Jailbreak.isJailBreak();
    setState(() {
      isJailBreak = result;
    });

    // 弹出提示
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(result
            ? '当前设备存在越狱风险'
            : '设备安全,未发现越狱迹象'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue,
      ),
      body: Center(
        child: Text(
          isJailBreak == null
              ? '正在检测设备安全...'
              : (isJailBreak! ? '⚠️ 已越狱设备' : '✅ 安全设备'),
          textAlign: TextAlign.center,
          style: const TextStyle(
            backgroundColor: Colors.amber,
            color: Colors.blue,
            fontSize: 20,
          ),
        ),
      ),
    );
  }
}

iOS端 – Swift

import Flutter
import UIKit

public class JailbreakPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "com.example.jailbreak", binaryMessenger: registrar.messenger())
    let instance = JailbreakPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    if call.method == "isJailBreakWithPath" {
      result(checkPath())
    } else {
      result(FlutterMethodNotImplemented)
    }
  }

  private func checkPath() -> Bool {
    let paths = ["/Applications/Cydia.app", "/usr/sbin/sshd"]
    return paths.contains { FileManager.default.fileExists(atPath: $0) }
  }
}

效果

在这里插入图片描述

LocationManager 定位

源码

import 'package:geolocator/geolocator.dart';

class LocationManager {
  // 单例模式,可选
  LocationManager._privateConstructor();
  static final LocationManager instance = LocationManager._privateConstructor();

  /// 判断是否打开定位服务,并且APP是否有权限
  static Future<bool> determineWhetherTheAPPOpensTheLocation() async {
    // 1. 检查设备定位服务是否开启
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return false; // 定位服务未开启
    }

    // 2. 检查APP权限
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      // 请求权限
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        return false; // 用户拒绝权限
      }
    }

    if (permission == LocationPermission.deniedForever) {
      // 用户永久拒绝权限
      return false;
    }

    // 3. 已授权定位
    return true;
  }
}

info.plist

    <key>NSLocationWhenInUseUsageDescription</key>
    <string>应用需要访问您的位置信息以提供定位服务</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>应用需要持续访问位置信息以提供相关功能</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>应用在后台运行时需要访问位置权限</string>

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/LocationManager.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  String _locationStatus = "未检测";

  @override
  void initState() {
    super.initState();
    _checkLocation();
  }

  Future<void> _checkLocation() async {
    bool isEnabled =
    await LocationManager.determineWhetherTheAPPOpensTheLocation();

    setState(() {
      _locationStatus = isEnabled ? "定位已开启 ✅" : "定位未授权 ❌";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              _locationStatus,
              style: const TextStyle(
                backgroundColor: Colors.amber,
                color: Colors.blue,
                fontSize: 20,
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async => _checkLocation(),
              child: const Text("重新检测定位权限"),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述
在这里插入图片描述

MDImageColor 计算图片主色

源码


import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as img;

/// 简单的 model,等同于你的 ImageColorModel
class ImageColorModel {
  final Color color;
  final int colorCount;
  final double alpha;

  ImageColorModel({
    required this.color,
    required this.colorCount,
    required this.alpha,
  });
}

class MDImageColor {
  /// 从图片字节(png/jpg bytes)计算主色(同步)
  /// 返回 null 表示无法解析图片或无有效像素
  static ImageColorModel? mostColorFromBytes(Uint8List bytes, {int thumbWidth = 40, int thumbHeight = 40}) {
    final src = img.decodeImage(bytes);
    if (src == null) return null;

    final thumb = img.copyResize(src, width: thumbWidth, height: thumbHeight);

    double maxScore = 0.0;
    List<int>? best; // [r,g,b,a]

    final int w = thumb.width;
    final int h = thumb.height;

    for (int y = 0; y < h; y++) {
      for (int x = 0; x < w; x++) {
        final pixel = thumb.getPixel(x, y); // 返回 Pixel 对象
        final int red = pixel.r.toInt();
        final int green = pixel.g.toInt();
        final int blue = pixel.b.toInt();
        final int alpha = pixel.a.toInt();

        if (alpha < 25) continue;

        final hsv = _rgbToHsv(red.toDouble(), green.toDouble(), blue.toDouble());
        final double s = hsv[1];

        int yTemp = ((red * 2104 + green * 4130 + blue * 802 + 4096 + 131072) >> 13);
        if (yTemp > 235) yTemp = 235;
        double yVal = (yTemp - 16) / (235 - 16);
        if (yVal > 0.9) continue;

        final double index = (y * w + x).toDouble();
        final double score = (s + 0.1) * index;

        if (score > maxScore) {
          maxScore = score;
          best = [red, green, blue, alpha];
        }
      }
    }


    if (best == null) return null;

    final Color color = Color.fromARGB(best[3], best[0], best[1], best[2]);
    final int count = best[0] + best[1] + best[2];
    final double alpha = best[3] / 255.0;

    return ImageColorModel(color: color, colorCount: count, alpha: alpha);
  }

  /// 如果你已经有 ui.Image(比如从 rootBundle.instantiateImageCodec 得到的 frame.image),
  /// 可以用这个方法(异步)把它转成 bytes 然后调用上面的方法
  static Future<ImageColorModel?> mostColorFromUiImage(ui.Image image, {int thumbWidth = 40, int thumbHeight = 40}) async {
    final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    if (byteData == null) return null;
    return mostColorFromBytes(byteData.buffer.asUint8List(), thumbWidth: thumbWidth, thumbHeight: thumbHeight);
  }

  // RGB -> HSV(r,g,b 输入 0..255)
  static List<double> _rgbToHsv(double r, double g, double b) {
    r /= 255.0;
    g /= 255.0;
    b /= 255.0;

    final double maxVal = max(r, max(g, b));
    final double minVal = min(r, min(g, b));
    double h = 0.0, s = 0.0;
    final double v = maxVal;
    final double delta = maxVal - minVal;

    if (maxVal != 0.0) {
      s = delta / maxVal;
    } else {
      return [-1.0, 0.0, v];
    }

    if (r == maxVal) {
      h = (g - b) / delta;
    } else if (g == maxVal) {
      h = 2.0 + (b - r) / delta;
    } else {
      h = 4.0 + (r - g) / delta;
    }

    h *= 60.0;
    if (h < 0) h += 360.0;

    return [h, s, v];
  }
}

使用

import 'package:flutter/material.dart';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutterdemols/Extension/MDImageColor.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  Color _mainColor = Colors.grey;
  bool _isLoading = true;
  String imagePath = 'assets/images/image.png'; // 统一使用同一张图片

  @override
  void initState() {
    super.initState();
    _loadImageMainColor();
  }

  Future<void> _loadImageMainColor() async {
    try {
      ByteData data = await rootBundle.load(imagePath);
      Uint8List bytes = data.buffer.asUint8List();

      final model = MDImageColor.mostColorFromBytes(bytes);
      if (model != null) {
        setState(() {
          _mainColor = model.color;
        });
      }
    } catch (e) {
      debugPrint('解析图片失败: $e');
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Image Main Color Demo'),
        backgroundColor: _mainColor,
      ),
      body: Center(
        child: _isLoading
            ? const CircularProgressIndicator()
            : Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ClipRRect(
              borderRadius: BorderRadius.circular(12),
              child: Image.asset(
                imagePath,
                width: 200,
                height: 200,
                fit: BoxFit.cover,
              ),
            ),
            const SizedBox(height: 20),
            Text(
              '主色: #${_mainColor.value.toRadixString(16).padLeft(8, '0')}',
              style: TextStyle(
                color: Colors.white,
                fontSize: 20,
                backgroundColor: _mainColor,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

NetworkManager 监听网络状态

源码

import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';

enum NetworkType {
  none,
  wifi,
  cellular,
}

class NetworkManager {
  NetworkManager._internal();
  static final NetworkManager _instance = NetworkManager._internal();
  static NetworkManager get shared => _instance;

  final Connectivity _connectivity = Connectivity();

  // 订阅使用 dynamic,以兼容 ConnectivityResult 或 List<ConnectivityResult>
  StreamSubscription<dynamic>? _subscription;

  NetworkType _lastNetworkType = NetworkType.none;
  bool _isFirst = true;

  // 用 stream 模拟 NotificationCenter(广播)
  final StreamController<Map<String, dynamic>> _networkChangedController =
  StreamController<Map<String, dynamic>>.broadcast();

  Stream<Map<String, dynamic>> get onNetworkChanged =>
      _networkChangedController.stream;

  final StreamController<Map<String, dynamic>> _networkTrendController =
  StreamController<Map<String, dynamic>>.broadcast();

  Stream<Map<String, dynamic>> get onNetworkTrend =>
      _networkTrendController.stream;

  /// 启动监听
  void startMonitoring() {
    _subscription?.cancel();

    _subscription = _connectivity.onConnectivityChanged.listen((dynamic event) async {
      // 先规范化为 ConnectivityResult
      ConnectivityResult result;
      if (event is List && event.isNotEmpty) {
        // event 可能是 List<ConnectivityResult>
        final first = event.first;
        if (first is ConnectivityResult) {
          result = first;
        } else {
          // 万一 first 不是枚举,降级处理
          result = ConnectivityResult.none;
        }
      } else if (event is ConnectivityResult) {
        result = event;
      } else if (event is String) {
        // 有些平台/版本可能返回字符串,做个防御性解析
        result = _parseConnectivityResultFromString(event);
      } else {
        result = ConnectivityResult.none;
      }

      // 以下为你原来的逻辑(将 result 映射为 NetworkType 等)
      final currentType = await _mapConnectivityResult(result);
      _notifyNetworkChanged(currentType);

      if (_isFirst) {
        _isFirst = false;
        _lastNetworkType = currentType;
        return;
      }

      if (currentType != _lastNetworkType) {
        int trendType = 0;
        if (currentType == NetworkType.none && _lastNetworkType == NetworkType.wifi) {
          trendType = 1;
        } else if (currentType == NetworkType.wifi && _lastNetworkType == NetworkType.cellular) {
          trendType = 2;
        } else if (currentType == NetworkType.cellular && _lastNetworkType == NetworkType.wifi) {
          trendType = 3;
        }

        if (trendType != 0) _notifyNetworkTrend(trendType);
        _lastNetworkType = currentType;
      }
    });
  }

  ConnectivityResult _parseConnectivityResultFromString(String s) {
    final lower = s.toLowerCase();
    if (lower.contains('wifi')) return ConnectivityResult.wifi;
    if (lower.contains('mobile') || lower.contains('cell')) return ConnectivityResult.mobile;
    if (lower.contains('ethernet')) return ConnectivityResult.ethernet;
    if (lower.contains('none') || lower.contains('disconnected')) return ConnectivityResult.none;
    return ConnectivityResult.none;
  }

  /// 停止监听(保留 stream 控制器,可继续订阅)
  void stopMonitoring() {
    _subscription?.cancel();
    _subscription = null;
  }

  /// 如果需要彻底释放资源(可选)
  void dispose() {
    stopMonitoring();
    _networkChangedController.close();
    _networkTrendController.close();
  }

  /// 获取当前网络类型(兼容 checkConnectivity 返回 list 的情况)
  Future<NetworkType> currentNetworkType() async {
    final dynamic res = await _connectivity.checkConnectivity();
    ConnectivityResult result;
    if (res is List && res.isNotEmpty) {
      result = res.first as ConnectivityResult;
    } else if (res is ConnectivityResult) {
      result = res;
    } else {
      result = ConnectivityResult.none;
    }
    return _mapConnectivityResult(result);
  }

  /// 是否能连通互联网(真实可访问)
  Future<bool> isNetworkReachable() async {
    return await InternetConnectionChecker().hasConnection;
  }

  Future<NetworkType> _mapConnectivityResult(ConnectivityResult result) async {
    switch (result) {
      case ConnectivityResult.mobile:
        return NetworkType.cellular;
      case ConnectivityResult.wifi:
      case ConnectivityResult.ethernet:
      // ethernet 也把它当作有线/WiFi 类网络来处理
        return NetworkType.wifi;
      case ConnectivityResult.bluetooth:
      case ConnectivityResult.vpn:
      case ConnectivityResult.other:
      // 这些视情况而定,这里视作有网络(归类为 wifi),你也可以单独处理
        return NetworkType.wifi;
      case ConnectivityResult.none:
      default:
        return NetworkType.none;
    }
  }

  void _notifyNetworkChanged(NetworkType type) {
    _networkChangedController.add({"type": type.index});
  }

  void _notifyNetworkTrend(int trendType) {
    _networkTrendController.add({"type": trendType});
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/NetworkManager.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  String _netStatus = "检测中...";
  String _trendText = "无趋势变化";

  @override
  void initState() {
    super.initState();

    // 启动监听
    NetworkManager.shared.startMonitoring();

    // 网络状态变化监听
    NetworkManager.shared.onNetworkChanged.listen((data) {
      final type = data["type"] as int;
      final status = _mapNetworkType(type);

      setState(() {
        _netStatus = status;
      });
    });

    // 网络趋势变化监听
    NetworkManager.shared.onNetworkTrend.listen((data) {
      final trend = data["type"] as int;
      setState(() {
        _trendText = _mapNetworkTrend(trend);
      });
    });

    _checkNow();
  }

  Future<void> _checkNow() async {
    final type = await NetworkManager.shared.currentNetworkType();
    final reachable = await NetworkManager.shared.isNetworkReachable();

    setState(() {
      _netStatus = "${_mapNetworkType(type.index)} | 实际可访问: ${reachable ? "✅" : "❌"}";
    });
  }

  @override
  void dispose() {
    NetworkManager.shared.stopMonitoring();
    super.dispose();
  }

  String _mapNetworkType(int typeIndex) {
    switch (typeIndex) {
      case 0:
        return "无网络 ❌";
      case 1:
        return "WiFi ✅";
      case 2:
        return "移动网络 📶";
      default:
        return "未知网络";
    }
  }

  String _mapNetworkTrend(int type) {
    switch (type) {
      case 1:
        return "WiFi → 无网络 ❌";
      case 2:
        return "移动网络 → WiFi ✅";
      case 3:
        return "WiFi → 移动网络 📶";
      default:
        return "无趋势变化";
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('网络状态监控 Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              _netStatus,
              style: const TextStyle(fontSize: 20, color: Colors.blue),
            ),
            const SizedBox(height: 10),
            Text(
              _trendText,
              style: const TextStyle(fontSize: 18, color: Colors.deepOrange),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => _checkNow(),
              child: const Text("手动检测网络"),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

QRCodeUtil 扫一扫

源码

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:qr_code_tools/qr_code_tools.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';

class QRCodeUtil {
  /// 生成二维码 Widget
  /// [qrString] 内容
  /// [size] 图片大小
  /// [color] 填充颜色
  static Widget createQRImageString(
      String qrString, double size, Color color) {
    return QrImageView(
      data: qrString,
      version: QrVersions.auto,
      size: size,
      foregroundColor: color,
      backgroundColor: Colors.white,
    );
  }

  /// 将二维码图片转成 Uint8List(类似 UIImage)
  static Future<Uint8List?> createQRImageBytes(
      String qrString, double size, Color color) async {
    final painter = QrPainter(
      data: qrString,
      version: QrVersions.auto,
      gapless: true,
      color: color,
      emptyColor: Colors.white,
    );
    final picData = await painter.toImageData(size);
    return picData?.buffer.asUint8List();
  }

  /// 从图片文件路径中读取二维码信息
  static Future<String?> readQRCodeFromFile(String filePath) async {
    try {
      String? result = await QrCodeToolsPlugin.decodeFrom(filePath);
      return result;
    } catch (e) {
      debugPrint("二维码解析失败: $e");
      return null;
    }
  }

  /// 从 Uint8List(内存中的图片数据)读取二维码信息
  static Future<String?> readQRCodeFromBytes(Uint8List data) async {
    try {
      // 1. 获取临时目录
      final tempDir = await getTemporaryDirectory();
      final file = File('${tempDir.path}/temp_qr.png');

      // 2. 写入临时文件
      await file.writeAsBytes(data);

      // 3. 调用 decodeFrom() 解析
      String? result = await QrCodeToolsPlugin.decodeFrom(file.path);
      return result;
    } catch (e) {
      debugPrint("二维码解析失败: $e");
      return null;
    }
  }
}

使用

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/QRCodeUtil.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  String _qrString = "https://www.baidu.com";
  String _decodedText = "尚未解析";
  Uint8List? _qrBytes;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('QR Code Demo'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '二维码展示',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),

            /// 显示二维码,由 QRCodeUtil 生成
            QRCodeUtil.createQRImageString(_qrString, 150, Colors.black),

            const SizedBox(height: 25),

            ElevatedButton(
              onPressed: _generateBytesAndDecode,
              child: const Text("生成二维码Bytes并解析"),
            ),

            const SizedBox(height: 20),

            Text(
              "解析结果:$_decodedText",
              style: const TextStyle(fontSize: 16),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }

  /// 生成二维码 Bytes 并解析
  Future<void> _generateBytesAndDecode() async {
    /// 生成 Uint8List 数据
    _qrBytes = await QRCodeUtil.createQRImageBytes(
      _qrString,
      300,
      Colors.black,
    );

    if (_qrBytes == null) {
      setState(() {
        _decodedText = "生成二维码失败";
      });
      return;
    }

    /// 尝试解析二维码内容
    final decoded = await QRCodeUtil.readQRCodeFromBytes(_qrBytes!);

    setState(() {
      _decodedText = decoded ?? "解析失败";
    });
  }
}

效果

在这里插入图片描述

Regular 正则

源码

import 'package:flutter/foundation.dart';

class Regular {
  /// 纳税号(15-20位,必须同时包含字母和数字)
  static bool validateTaxNumber(String? code) {
    if (code == null || code.isEmpty) return false;
    final pattern = RegExp(r'^(?![0-9]+$)(?![a-zA-Z]+$)[a-zA-Z0-9]{15,20}$');
    return pattern.hasMatch(code);
  }

  /// 银行卡账号(9-30位纯数字)
  static bool validateBankCode(String? code) {
    if (code == null || code.length <= 8 || code.length > 30) return false;
    final pattern = RegExp(r'^[0-9]+$');
    return pattern.hasMatch(code);
  }

  /// 手机号简单判断:1开头、11位、全数字
  static bool validateMobileNH(String? mobile) {
    if (mobile == null || mobile.isEmpty || mobile.length != 11) return false;
    final pattern = RegExp(r'^[0-9]+$');
    if (!pattern.hasMatch(mobile)) return false;
    return mobile.startsWith('1');
  }

  /// 手机号精确判断(移动/联通/电信/虚拟号段)
  static bool validateMobile(String? mobile) {
    if (mobile == null || mobile.isEmpty) return false;
    final pattern = RegExp(r'^1(3[0-9]|4[579]|5[0-35-9]|7[01356]|8[0-9])\d{8}$');
    return pattern.hasMatch(mobile);
  }

  /// 身份证号判断(15位或18位,最后一位可能是X/x)
  static bool validateIdentityCard(String? idCard) {
    if (idCard == null || idCard.isEmpty) return false;
    final pattern = RegExp(r'^(\d{14}|\d{17})(\d|[xX])$');
    return pattern.hasMatch(idCard);
  }

  /// 邮箱判断
  static bool validateEmail(String? email) {
    if (email == null || email.isEmpty) return false;
    final pattern = RegExp(r'^[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$');
    return pattern.hasMatch(email);
  }

  /// 7-12位纯数字
  static bool validateNum(String? number) {
    if (number == null || number.length < 7 || number.length > 12) return false;
    final pattern = RegExp(r'^[0-9]+$');
    return pattern.hasMatch(number);
  }
}

使用

import ‘package:flutter/material.dart’;
import ‘package:flutterdemols/Extension/Regular.dart’;

class Home extends StatefulWidget {
@override
State createState() => _HomeState();
}

class _HomeState extends State {
final TextEditingController _inputController = TextEditingController();
String _result = ‘’;

void _validateInput() {
final input = _inputController.text;

// 示例: 校验是否为手机号
final isMobile = Regular.validateMobile(input);

setState(() {
  _result = isMobile ? '✅ 手机号格式正确' : '❌ 手机号格式不正确';
});

}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(‘Home’),
backgroundColor: Colors.blue[300],
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _inputController,
decoration: const InputDecoration(
labelText: ‘请输入手机号码’,
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _validateInput,
child: const Text(‘验证’),
),
const SizedBox(height: 20),
Text(
_result,
style: TextStyle(
fontSize: 18,
color: _result.startsWith(‘✅’) ? Colors.green : Colors.red,
),
),
],
),
),
);
}
}

效果

在这里插入图片描述

RSAEncryptor RSA加密(无法使用)

在这里插入图片描述

在这里插入图片描述

源码

import 'dart:convert';
import 'package:encrypt/encrypt.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:flutter/services.dart' show rootBundle;

class RSAEncryptor {
  /// -----------------------
  /// 🔹 公钥字符串加密
  /// -----------------------
  static String encryptWithPublicKey(String plainText, String publicKeyPem) {
    final publicKey = RSAKeyParser().parse(publicKeyPem) as RSAPublicKey;
    final encrypter = Encrypter(RSA(publicKey: publicKey, encoding: RSAEncoding.PKCS1));
    final encrypted = encrypter.encrypt(plainText);
    return encrypted.base64;
  }

  /// -----------------------
  /// 🔹 私钥字符串解密
  /// -----------------------
  static String decryptWithPrivateKey(String base64Cipher, String privateKeyPem) {
    final privateKey = RSAKeyParser().parse(privateKeyPem) as RSAPrivateKey;
    final encrypter = Encrypter(RSA(privateKey: privateKey, encoding: RSAEncoding.PKCS1));
    final decrypted = encrypter.decrypt(Encrypted.fromBase64(base64Cipher));
    return decrypted;
  }

  /// -----------------------
  /// 🔹 从 `.der` 文件加载公钥并加密
  /// -----------------------
  static Future<String> encryptWithDerFile(String plainText) async {
    // 从 assets 加载 DER 文件
    final derData = await rootBundle.load('assets/keys/public_key.der');
    final bytes = derData.buffer.asUint8List();

    // DER 转 PEM
    final publicKeyPem = _convertDerToPem(bytes, 'PUBLIC KEY');

    // 使用公钥加密
    return encryptWithPublicKey(plainText, publicKeyPem);
  }

  /// -----------------------
  /// 🔹 从 `.p12` 文件加载私钥并解密
  /// -----------------------
  static Future<String> decryptWithP12File(
      String base64Cipher, String p12Path, String password) async {
    // ⚠️ 注意:Flutter/Dart 没有原生解析 .p12 功能,
    // 可在后端或原生层提取 PEM 格式私钥后传入此函数。
    throw UnimplementedError(
        'Dart 暂不支持直接解析 .p12 文件,请在服务器或原生层转换为 PEM 格式。');
  }

  /// -----------------------
  /// 工具方法:DER → PEM
  /// -----------------------
  static String _convertDerToPem(List<int> bytes, String type) {
    final base64Str = base64.encode(bytes);
    final chunks = <String>[];
    for (var i = 0; i < base64Str.length; i += 64) {
      chunks.add(base64Str.substring(i, i + 64 > base64Str.length ? base64Str.length : i + 64));
    }
    return '-----BEGIN $type-----\n${chunks.join('\n')}\n-----END $type-----';
  }
}

Sandbox 沙盒路径

源码

import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'dart:developer';

class Sandbox {
  /// 获取 Documents 路径
  static Future<String> sandboxGetDocumentsPathWithName([String? name]) async {
    final directory = await getApplicationDocumentsDirectory();
    String path = directory.path;
    if (name != null) {
      path = "${directory.path}${Platform.pathSeparator}$name";
    }
    log("filePath: $path");
    return path;
  }

  /// 获取 Caches 路径
  static Future<String> sandboxGetCachesPathWithName([String? name]) async {
    final directory = await getTemporaryDirectory(); // 注意: iOS/Android 上 caches 是临时目录
    String path = directory.path;
    if (name != null) {
      path = "${directory.path}${Platform.pathSeparator}$name";
    }
    log("filePath: $path");
    return path;
  }

  /// 获取 Tmp 路径
  static Future<String> sandboxGetTmpPathWithName([String? name]) async {
    final tmpPath = await getTemporaryDirectory();
    String path = tmpPath.path;
    if (name != null) {
      path = "${tmpPath.path}${Platform.pathSeparator}$name";
    }
    log("filePath: $path");
    return path;
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/Sandbox.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  String _path = '尚未获取路径';

  Future<void> _getDocumentsPath() async {
    final path = await Sandbox.sandboxGetDocumentsPathWithName('myFile.txt');
    setState(() {
      _path = path;
    });
  }

  Future<void> _getCachesPath() async {
    final path = await Sandbox.sandboxGetCachesPathWithName('cache.log');
    setState(() {
      _path = path;
    });
  }

  Future<void> _getTmpPath() async {
    final path = await Sandbox.sandboxGetTmpPathWithName('tmp.data');
    setState(() {
      _path = path;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              _path,
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 16),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _getDocumentsPath,
              child: const Text('获取 Documents 路径'),
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: _getCachesPath,
              child: const Text('获取 Caches 路径'),
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: _getTmpPath,
              child: const Text('获取 Tmp 路径'),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

ScreenUtil 判断iPhone是否刘海

源码

import 'package:flutter/widgets.dart';

class ScreenUtil {
  /// 判断是否为刘海屏(true 表示刘海屏,false 表示非刘海屏)
  static bool isNotchScreen(BuildContext context) {
    final padding = MediaQuery.of(context).padding;
    // 通常顶部安全区大于 20,就说明是刘海屏
    return padding.top > 20;
  }

  /// 如果你希望和原 OC 逻辑保持一致(YES = 非刘海屏)
  static bool isNotNotchScreen(BuildContext context) {
    return !isNotchScreen(context);
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/ScreenUtil.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    final isNotch = ScreenUtil.isNotchScreen(context);
    final isNotNotch = ScreenUtil.isNotNotchScreen(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              isNotch ? '该设备为刘海屏' : '该设备为非刘海屏',
              style: const TextStyle(
                fontSize: 22,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            Text(
              'isNotchScreen: $isNotch\n'
                  'isNotNotchScreen: $isNotNotch',
              textAlign: TextAlign.center,
              style: const TextStyle(
                backgroundColor: Colors.amber,
                color: Colors.blue,
                fontSize: 18,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

StringExtension 字符串扩展

源码

import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutterdemols/Home.dart';
import 'package:flutterdemols/Other.dart';

/// 字符串扩展工具类
class StringExtension {
  /// 字符串倒序
  static String reverse(String oldStr) {
    return oldStr.split('').reversed.join();
  }

  /// 字符串转 JSON 安全字符串
  /// 类似 OC 版本中的转义字符处理
  static String stringToJSONString(String input) {
    return input
        .replaceAll('"', '\\"')
        .replaceAll('/', '\\/')
        .replaceAll('\n', '\\n')
        .replaceAll('\b', '\\b')
        .replaceAll('\f', '\\f')
        .replaceAll('\r', '\\r')
        .replaceAll('\t', '\\t');
  }

  /// JSON 字符串转 Map
  static Map<String, dynamic>? convertToDictionary(String jsonString) {
    try {
      return json.decode(jsonString);
    } catch (e) {
      debugPrint('JSON decode error: $e');
      return null;
    }
  }

  /// 字典(Map) 转 JSON 字符串
  static String convertToJsonData(Map<String, dynamic> dict) {
    try {
      // pretty print -> false 去掉空格和换行
      return json.encode(dict);
    } catch (e) {
      debugPrint('JSON encode error: $e');
      return '';
    }
  }

  /// 返回文字的 size(用于 Text 渲染前计算)
  static Size textSize(String text, TextStyle style, {Size? maxSize}) {
    final TextPainter textPainter = TextPainter(
      text: TextSpan(text: text, style: style),
      maxLines: null,
      textDirection: TextDirection.ltr,
    )..layout(maxWidth: maxSize?.width ?? double.infinity);

    return textPainter.size;
  }


    final builder = widgetRegistry[className];
    return builder != null ? builder() : null;
  }

  /// 生成随机字符串
  static String randomString(int len) {
    const letters =
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    final rand = Random.secure();
    return List.generate(len, (_) => letters[rand.nextInt(letters.length)])
        .join();
  }

  /// 指定字符集随机生成字符串
  static String randomStringWithLetters(int len, String letters) {
    final rand = Random.secure();
    return List.generate(len, (_) => letters[rand.nextInt(letters.length)])
        .join();
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/StringExtension.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    final originalText = "Hello123";
    final reversed = StringExtension.reverse(originalText);

    final map = {"name": "HanXu", "age": 28};
    final jsonStr = StringExtension.convertToJsonData(map);
    final mapFromJson = StringExtension.convertToDictionary(jsonStr);

    final escaped = StringExtension.stringToJSONString('Text "quotes" / line\nT');

    final random = StringExtension.randomString(8);

    /// 1. 返回文字 size 示例
    const String sampleText = "Flutter ABC";
    Size textSize = StringExtension.textSize(
      sampleText,
      const TextStyle(fontSize: 16),
      maxSize: const Size(200, double.infinity),
    );

    /// 2. 指定字符集随机字符串示例
    String randomNumStr = StringExtension.randomStringWithLetters(
      6,
      "0123456789",
    );

    /// 3. JSON 转义字符串示例
    String jsonSafe = StringExtension.stringToJSONString(
        'Hello "World" / line\nText'
    );

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Padding(
        padding: const EdgeInsets.all(18.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _item("原始字符串:", originalText),
            const Divider(),
            _item("倒序结果:", reversed),
            const Divider(),

            _item("Map 转 JSON:", jsonStr),
            const Divider(),
            _item("JSON 转 Map:", mapFromJson.toString()),
            const Divider(),

            _item("转义字符串:", escaped),
            const Divider(),

            _item("随机字符串:", random),
            const Divider(),
            Text("文本内容: $sampleText"),
            Text("预估尺寸 width: ${textSize.width.toStringAsFixed(2)}, "
                "height: ${textSize.height.toStringAsFixed(2)}"),
            const Divider(),

            Text("随机数字字符串: $randomNumStr"),
            const Divider(),

            const Text("原始字符串: Hello \"World\" / line\\nText"),
            Text("转义结果: $jsonSafe"),
          ],
        ),
      ),
    );
  }

  Widget _item(String title, String value) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: Text(
        "$title $value",
        style: const TextStyle(fontSize: 18),
      ),
    );
  }
}

效果

在这里插入图片描述

Time 时间戳

源码

import 'package:intl/intl.dart';

/// 时间工具类
class Time {
  /// 获取当前时间字符串
  /// [type] 格式:如 "yyyy-MM-dd HH:mm:ss SSS"、"yyyy-MM-dd"、"MM-dd" 等
  static String getCurrentTimesWithType(String type) {
    final now = DateTime.now();
    final formatter = DateFormat(type);
    return formatter.format(now);
  }

  /// 获取当前时间戳(秒)——方法A
  static String getNowTimeTimestampSecondA() {
    return (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
  }

  /// 获取当前时间戳(秒)——方法B
  static String getNowTimeTimestampSecondB() {
    final seconds = DateTime.now().millisecondsSinceEpoch / 1000;
    return seconds.toStringAsFixed(0);
  }

  /// 获取当前时间戳(毫秒)
  static String getNowTimeTimestampMillisecond() {
    return DateTime.now().millisecondsSinceEpoch.toString();
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/Time.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    // 调用 Time 工具类方法示例
    String nowFormatFull =
    Time.getCurrentTimesWithType("yyyy-MM-dd HH:mm:ss SSS");
    String nowFormatDate = Time.getCurrentTimesWithType("yyyy-MM-dd");
    String timestampSecondA = Time.getNowTimeTimestampSecondA();
    String timestampSecondB = Time.getNowTimeTimestampSecondB();
    String timestampMilli = Time.getNowTimeTimestampMillisecond();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text("当前时间(精确毫秒): $nowFormatFull"),
            Text("当前日期(yyyy-MM-dd): $nowFormatDate"),
            const Divider(),

            Text("时间戳A(秒): $timestampSecondA"),
            Text("时间戳B(秒): $timestampSecondB"),
            Text("时间戳(毫秒): $timestampMilli"),
          ],
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

TimeDisplayHelper 聊天时间显示规则

源码

import 'package:intl/intl.dart';

class TimeDisplayHelper {
  static String wechatStyleTimeStringFromDate(DateTime? date, {String locale = 'zh_CN'}) {
    if (date == null) return '';

    final now = DateTime.now();
    final diff = now.difference(date);

    // 今天
    if (_isSameDay(now, date)) {
      if (diff.inSeconds < 60) return '刚刚';
      if (diff.inMinutes < 60) return '${diff.inMinutes}分钟前';
      return DateFormat('HH:mm', locale).format(date);
    }

    // 昨天
    final yesterday = DateTime(now.year, now.month, now.day).subtract(const Duration(days: 1));
    if (_isSameDay(yesterday, date)) {
      return '昨天 ${DateFormat('HH:mm', locale).format(date)}';
    }

    // 本周内(周一为第一天)
    if (_isSameWeek(now, date)) {
      final weekdaySymbols = ['周一','周二','周三','周四','周五','周六','周日'];
      final weekdayStr = weekdaySymbols[date.weekday - 1]; // DateTime.weekday: Mon=1..Sun=7
      return '$weekdayStr ${DateFormat('HH:mm', locale).format(date)}';
    }

    // 今年内
    if (now.year == date.year) {
      return DateFormat('MM-dd HH:mm', locale).format(date);
    }

    // 更早
    return DateFormat('yyyy-MM-dd HH:mm', locale).format(date);
  }

  static bool _isSameDay(DateTime a, DateTime b) =>
      a.year == b.year && a.month == b.month && a.day == b.day;

  static bool _isSameWeek(DateTime a, DateTime b) {
    final aStart = _startOfWeek(a);
    final bStart = _startOfWeek(b);
    return aStart.year == bStart.year && aStart.month == bStart.month && aStart.day == bStart.day;
  }

  // 以周一为每周的第一天
  static DateTime _startOfWeek(DateTime d) {
    final date = DateTime(d.year, d.month, d.day);
    return date.subtract(Duration(days: date.weekday - 1));
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/TimeDisplayHelper.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    final now = DateTime.now();

    // 示例时间
    final times = [
      now.subtract(const Duration(seconds: 30)),  // 刚刚
      now.subtract(const Duration(minutes: 5)),   // 5分钟前
      now.subtract(const Duration(hours: 3)),     // 今天 HH:mm
      now.subtract(const Duration(days: 1)),      // 昨天
      now.subtract(const Duration(days: 3)),      // 本周
      now.subtract(const Duration(days: 15)),     // 本月/今年
      now.subtract(const Duration(days: 400)),    // 去年
    ];

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: ListView.builder(
          itemCount: times.length,
          itemBuilder: (context, index) {
            final date = times[index];
            final display = TimeDisplayHelper.wechatStyleTimeStringFromDate(date);
            return Padding(
              padding: const EdgeInsets.symmetric(vertical: 6.0),
              child: Text(
                "原始: $date\n显示: $display",
                style: const TextStyle(fontSize: 18),
              ),
            );
          },
        ),
      ),
    );
  }
}

效果

在这里插入图片描述

YSCountDown 活动cell倒计时

源码

import 'dart:async';
import 'package:flutter/foundation.dart';

typedef YSCountDownCallback = void Function(int index);

class YSCountDown {
  final YSCountDownCallback? onFinish;
  Timer? _timer;
  int _less = 0;
  bool isPlusTime = false;

  List<int>? _dataList; // 结束时间戳
  List<String>? _canReloadList; // “可以” / “不可以”
  ValueNotifier<int> _tick = ValueNotifier(0);
  ValueNotifier<int> get tick => _tick; // 公有 getter

  YSCountDown({this.onFinish});

  void destroyTimer() {
    _timer?.cancel();
    _timer = null;
  }

  /// 初始化
  void init({
    required List<int> dataList,
    required List<String> canReloadList,
  }) {
    _dataList = dataList;
    _canReloadList = canReloadList;
    _setupLess();
  }

  void _setupLess() async {
    // TODO: 可扩展:请求服务器时间差
    _less = 0;
    _startTimer();
  }

  void _startTimer() {
    destroyTimer();
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      _tick.value++; // 通知监听者
      _updateCountdown();
    });
  }

  void _updateCountdown() {
    if (_dataList == null || _canReloadList == null) return;

    final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
    for (int i = 0; i < _dataList!.length; i++) {
      final end = _dataList![i] + _less;
      final text = getNowTimeWithString(end, now);
      if (text == "倒计时结束" && _canReloadList![i] == "可以") {
        _canReloadList![i] = "不可以";
        onFinish?.call(i);
      }
    }
  }

  /// cell 滑动过快时调用
  String getCountdownText(int index) {
    final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
    final endTime = _dataList![index] + _less;
    return getNowTimeWithString(endTime, now);
  }

  /// 时间计算逻辑
  String getNowTimeWithString(int endTime, int currentTime) {
    int interval = endTime - currentTime;

    int days = interval ~/ (3600 * 24);
    int hours = (interval - days * 24 * 3600) ~/ 3600;
    int minutes = (interval - days * 24 * 3600 - hours * 3600) ~/ 60;
    int seconds =
        interval - days * 24 * 3600 - hours * 3600 - minutes * 60;

    if (isPlusTime) {
      if (hours >= 0 && minutes >= 0 && seconds >= 0) {
        return "正在倒计时";
      }
      hours = -hours;
      minutes = -minutes;
      seconds = -seconds;
    } else {
      if (hours <= 0 && minutes <= 0 && seconds <= 0) {
        return "倒计时结束";
      }
    }

    if (days > 0) {
      return "${days.toString().padLeft(2, '0')} : ${hours.toString().padLeft(2, '0')} : ${minutes.toString().padLeft(2, '0')} : ${seconds.toString().padLeft(2, '0')}";
    } else {
      return "${hours.toString().padLeft(2, '0')} : ${minutes.toString().padLeft(2, '0')} : ${seconds.toString().padLeft(2, '0')}";
    }
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutterdemols/Extension/YSCountDown.dart';

class Home extends StatefulWidget {
  @override
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  late YSCountDown countdown;
  List<int> endTimestamps = []; // 存放每个倒计时结束时间戳(秒)
  List<String> canReloadList = []; // 对应是否可刷新

  @override
  void initState() {
    super.initState();

    // 示例:创建倒计时对象
    countdown = YSCountDown(onFinish: (index) {
      print("倒计时 $index 完成");
    });

    // 初始化数据(这里示例3个倒计时,分别5秒、10秒、15秒后结束)
    final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
    endTimestamps = [now + 5, now + 10, now + 15];
    canReloadList = ["可以", "可以", "可以"];

    countdown.init(dataList: endTimestamps, canReloadList: canReloadList);
  }

  @override
  void dispose() {
    countdown.destroyTimer();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        backgroundColor: Colors.blue[300],
      ),
      body: ValueListenableBuilder<int>(
        valueListenable: countdown.tick, // 监听倒计时 tick
        builder: (context, value, child) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: List.generate(endTimestamps.length, (index) {
                return Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Text(
                    countdown.getCountdownText(index),
                    style: const TextStyle(
                      fontSize: 20,
                      color: Colors.blue,
                      backgroundColor: Colors.amber,
                    ),
                  ),
                );
              }),
            ),
          );
        },
      ),
    );
  }
}

效果

在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐