Flutter气泡引导pop

效果图
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

Future<T?> showCustomGuidePop<T>({
  required BuildContext context,
  required GlobalKey popKey,
  required String title,
  Offset? offset,
}) {
  return Navigator.push(
    context,
    _CustomGuidePopRoute<T>(
        popKey: popKey,
        title: title,
        offset: offset
    ),
  );
}

class _CustomGuidePopRoute<T> extends PopupRoute<T> {
  final GlobalKey popKey;
  final String title;
  final Offset? offset;

  _CustomGuidePopRoute({
    required this.popKey,
    required this.title,
    this.offset,
  });

  @override
  Color? get barrierColor => null;

  @override
  bool get barrierDismissible => true;

  @override
  String? get barrierLabel => null;

  @override
  Duration get transitionDuration => Duration(milliseconds: 100);

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    return CustomGuidePop<T>(
      popKey: popKey,
      title: title,
      offset: offset,
    );
  }

  @override
  Animation<double> createAnimation() {
    return CurvedAnimation(
      parent: super.createAnimation(),
      curve: Curves.linear,
      reverseCurve: const Interval(0.0, 1.5),
    );
  }
}

class CustomGuidePop<T> extends StatefulWidget {
  final GlobalKey popKey;
  final String title;
  final Offset offset;

  CustomGuidePop({
    Key? key,
    required this.popKey,
    required this.title,
    Offset? offset,
  }):this.offset = offset ?? Offset.zero, super(key: key);

  @override
  State<CustomGuidePop> createState() => _CustomGuidePopState();
}



class _CustomGuidePopState extends State<CustomGuidePop> {
  late GlobalKey globalKey = GlobalKey();
  late Rxn<RelativeRect> positionObs;
  late Timer timer;

  @override
  void initState() {
    super.initState();
    positionObs = Rxn();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      RenderBox? contentBox = globalKey.currentContext?.findRenderObject() as RenderBox?;
      RenderBox? targetBox = widget.popKey.currentContext?.findRenderObject() as RenderBox?;
      double left = 0;
      double top = 0;
      if(targetBox == null || contentBox == null) {
        return;
      }
      var contentSize = contentBox.size;
      var targetSize = targetBox.size;
      Offset localToGlobal = targetBox.localToGlobal(Offset.zero);
      // 目前仅支持在左边显示弹窗
      left = localToGlobal.dx - contentSize.width;
      top = localToGlobal.dy - (contentSize.height - targetSize.height) * 0.5;
      left = left + widget.offset.dx;
      top = top + widget.offset.dy;
      positionObs.value = RelativeRect.fromLTRB(left, top, 0, 0);
    });
    // 默认3秒后消失
    timer = Timer(Duration(seconds: 3), () {
      Navigator.of(context).pop();
    });
  }

  @override
  void dispose() {
    super.dispose();
    timer.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: GestureDetector(
        child: _buildWindow(context),
        onPanDown: (details) {
          Navigator.of(context).pop();
        },
      ),
    );
  }

  Widget _buildWindow(BuildContext context) {
    return Stack(
      children: [
        Container(
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.height,
          color: Colors.transparent,
        ),
        Obx(() {
          return Positioned(
            key: globalKey,
            left: positionObs.value?.left ?? 0,
            top: positionObs.value?.top ?? 0,
            child: Visibility(
              maintainAnimation: true,
              maintainSize: true,
              maintainState: true,
              visible: positionObs.value != null,
              child: Row(
                children: [
                  Container(
                    padding: EdgeInsets.all(16),
                    constraints: BoxConstraints(maxWidth: 375 - 160),
                    decoration: BoxDecoration(
                      color: Colors.grey,
                      borderRadius: BorderRadius.circular(10),
                    ),
                    child: Text(
                      widget.title,
                      style: const TextStyle(
                        color: Colors.white,
                      ),
                    ),
                  ),
                  Container(
                    margin: EdgeInsets.only(right: 5),
                    child: CustomPaint(
                      size: Size(8, 14),
                      painter: RowPainter(),
                    ),
                  )
                ],
              ),
            ),
          );
        }),
      ],
    );
  }
}

class RowPainter extends CustomPainter {
  void paint(Canvas canvas, Size size) {
    var paint = Paint();
    paint.color = Colors.grey;
    paint.style = PaintingStyle.fill;
    paint.strokeCap = StrokeCap.round;
    paint.strokeJoin = StrokeJoin.round;
    paint.isAntiAlias = true;
    paint.strokeWidth = 1;
    Path path = Path();
    path.moveTo(0, 0);
    path.lineTo(7.5, 6);
    path.lineTo(8, 7);
    path.lineTo(7.5, 8);
    path.lineTo(0, 14);
    path.close();
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。