123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- // ignore_for_file: must_be_immutable
- import 'dart:async';
- import 'package:flutter/material.dart';
- import 'dart:ui' as ui;
- import 'package:vnoteapp/components/floating_window/click_notification.dart';
- /// [FloatingItem]一个单独功能完善的列表项类
- class FloatingItem extends StatefulWidget {
- FloatingItem({
- super.key,
- required this.top,
- required this.isLeft,
- required this.title,
- required this.imageProvider,
- required this.index,
- required this.left,
- required this.isEntering,
- this.width,
- });
- /// [index] 列表项的索引值
- int index;
- /// [top]列表项的y坐标值
- double top;
- /// [left]列表项的x坐标值
- double left;
- ///[isLeft] 列表项是否在左侧,否则是右侧
- bool isLeft;
- /// [title] 列表项的文字说明
- String title;
- ///[imageProvider] 列表项Logo的imageProvider
- ImageProvider imageProvider;
- ///[width] 屏幕宽度的 1 / 2
- double? width;
- ///[isEntering] 列表项是否触发进场动画
- bool isEntering;
- @override
- _FloatingItemState createState() => _FloatingItemState();
- /// 全部列表项执行退场动画
- static void reverse() {
- for (int i = 0; i < _FloatingItemState.animationControllers.length; ++i) {
- if (!_FloatingItemState.animationControllers[i]
- .toString()
- .contains('DISPOSED')) {
- _FloatingItemState.animationControllers[i].reverse();
- }
- }
- }
- /// 全部列表项执行进场动画
- static void forward() {
- for (int i = 0; i < _FloatingItemState.animationControllers.length; ++i) {
- if (!_FloatingItemState.animationControllers[i]
- .toString()
- .contains('DISPOSED')) {
- _FloatingItemState.animationControllers[i].forward();
- }
- }
- }
- /// 每次更新时释放所有动画资源,清空动画控制器列表
- static void resetList() {
- for (int i = 0; i < _FloatingItemState.animationControllers.length; ++i) {
- if (!_FloatingItemState.animationControllers[i]
- .toString()
- .contains('DISPOSED')) {
- _FloatingItemState.animationControllers[i].dispose();
- }
- }
- _FloatingItemState.animationControllers.clear();
- _FloatingItemState.animationControllers = [];
- }
- }
- class _FloatingItemState extends State<FloatingItem>
- with TickerProviderStateMixin {
- /// [isPress] 列表项是否被按下
- bool isPress = false;
- ///[image] 列表项Logo的[ui.Image]对象,用于绘制Logo
- ui.Image? image;
- /// [animationController] 列表关闭动画的控制器
- AnimationController? animationController;
- /// [animationController] 所有列表项的动画控制器列表
- static List<AnimationController> animationControllers = [];
- /// [animation] 列表项的关闭动画
- Animation? animation;
- @override
- void initState() {
- // TODO: implement initState
- isPress = false;
- /// 获取Logo的ui.Image对象
- loadImageByProvider(widget.imageProvider).then((value) {
- setState(() {
- image = value;
- });
- });
- super.initState();
- }
- @override
- Widget build(BuildContext context) {
- return Positioned(
- left: widget.left,
- top: widget.top,
- child: GestureDetector(
- /// 监听按下事件,在点击区域内则将[isPress]设为true,若在关闭区域内则不做任何操作
- onPanDown: (details) {
- if (widget.isLeft) {
- /// 点击区域内
- if (details.globalPosition.dx < widget.width!) {
- setState(() {
- isPress = true;
- });
- }
- } else {
- /// 点击区域内
- if (details.globalPosition.dx < widget.width! * 2 - 50) {
- setState(() {
- isPress = true;
- });
- }
- }
- },
- /// 监听抬起事件
- onTapUp: (details) async {
- /// 通过左右列表项来决定关闭的区域,以及选中区域,触发相应的关闭或选中事件
- if (widget.isLeft) {
- /// 位于关闭区域
- if (details.globalPosition.dx >= widget.width! && !isPress) {
- /// 等待关闭动画执行完毕
- await animationController?.reverse();
- /// 通知父级触发关闭事件
- ClickNotification(deletedIndex: widget.index).dispatch(context);
- } else {
- /// 通知父级触发相应的点击事件
- ClickNotification(clickIndex: widget.index).dispatch(context);
- }
- } else {
- /// 位于关闭区域
- if (details.globalPosition.dx >= widget.width! * 2 - 50.0 &&
- !isPress) {
- /// 设置从中间返回至边缘的关闭动画
- await animationController?.reverse();
- /// 通知父级触发关闭事件
- ClickNotification(deletedIndex: widget.index).dispatch(context);
- } else {
- /// 通知父级触发选中事件
- ClickNotification(clickIndex: widget.index).dispatch(context);
- }
- }
- /// 抬起后取消选中
- setState(() {
- isPress = false;
- });
- },
- onTapCancel: () {
- /// 超出范围取消选中
- setState(() {
- isPress = false;
- });
- },
- child: Container(
- color: Colors.red,
- ),
- ),
- );
- // CustomPaint(
- // size: Size(widget.width! + 50.0, 50.0),
- // painter: FloatingItemPainter(
- // title: widget.title,
- // isLeft: widget.isLeft,
- // isPress: isPress,
- // image: image,
- // ))));
- }
- /// 通过ImageProvider获取ui.image
- Future<ui.Image> loadImageByProvider(
- ImageProvider provider, {
- ImageConfiguration config = ImageConfiguration.empty,
- }) async {
- Completer<ui.Image> completer = Completer<ui.Image>(); //完成的回调
- ImageStreamListener listener;
- ImageStream stream = provider.resolve(config); //获取图片流
- listener = ImageStreamListener((ImageInfo frame, bool sync) {
- //监听
- final ui.Image image = frame.image;
- completer.complete(image); //完成
- // stream.removeListener(listener); //移除监听
- });
- stream.addListener(listener); //添加监听
- return completer.future; //返回
- }
- @override
- void didUpdateWidget(FloatingItem oldWidget) {
- // TODO: implement didUpdateWidget
- animationController = AnimationController(
- vsync: this, duration: const Duration(milliseconds: 100));
- /// 初始化进场动画
- if (widget.isLeft) {
- animation = Tween<double>(begin: -(widget.width! + 50.0), end: 0.0)
- .animate(animationController!)
- ..addListener(() {
- setState(() {
- widget.left = animation!.value;
- });
- });
- } else {
- animation =
- Tween<double>(begin: widget.width! * 2, end: widget.width! - 50.0)
- .animate(animationController!)
- ..addListener(() {
- setState(() {
- widget.left = animation!.value;
- });
- });
- }
- animationControllers.add(animationController!);
- /// 执行进场动画
- if (animationController!.status == AnimationStatus.dismissed &&
- widget.isEntering) {
- animationController!.forward();
- }
- /// 无需执行进场动画,将列表项置于动画末尾
- else {
- animationController!.forward(from: 100.0);
- }
- super.didUpdateWidget(oldWidget);
- }
- @override
- void dispose() {
- // TODO: implement dispose
- /// 释放动画资源,避免内存泄漏
- if (!animationController.toString().toString().contains('DISPOSED')) {
- animationController!.dispose();
- }
- super.dispose();
- }
- }
|