Flutter封装:极简弹窗/抽屉终极版

一、需求来源

一直在琢磨如何实现任意方向的弹窗,上周突然灵光一闪,实现了,分享给大家。 核心是通过传入参数 Alignment 来控制抽屉的进入和消失方向; 通过构造器构建弹窗内容,最终实现无限可能。代码看起来很简单,但是从0到1的过程还是构思了蛮久的,大家直接用就好。

效果如下:

二、使用示例

调用方法

less 复制代码
presentDrawer({Alignment alignment = Alignment.topCenter,}){
  NAlignmentDrawer(
    alignment: alignment,
    builder: (onHide) {

      return ClipRRect(
        borderRadius: BorderRadius.all(Radius.circular(12)),
        child: Container(
          width: 300,
          height: 400,
          child: Scaffold(
            appBar: AppBar(
                title: Text("NAlignmentDrawer"),
                automaticallyImplyLeading: false,
                actions: [
                  TextButton(
                    onPressed: onHide,
                    child: Text('取消',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ]
            ),
            body: buildBody(),
          ),
        ),
      );
    },
  ).toShowDialog(
    context: context,
    useSafeArea: false,
    barrierDismissible: false,
  );
}

三、组件源码 - NAlignmentDrawer

scala 复制代码
//
//  NAlignmentDrawer.dart
//  flutter_templet_project
//
//  Created by shang on 2024/3/4 23:27.
//  Copyright © 2024/3/4 shang. All rights reserved.
//


import 'package:flutter/material.dart';

/// 弹窗/抽屉/页面展示
class NAlignmentDrawer extends StatefulWidget {

  NAlignmentDrawer({
    super.key,
    this.duration = const Duration(milliseconds: 350),
    this.alignment = Alignment.topCenter,
    this.barrierColor,
    this.onBarrier,
    this.hasFade = true,
    this.builder,
  });

  /// 动画时间
  final Duration duration;
  /// 目标位置
  final Alignment alignment;

  final Color? barrierColor;
  final VoidCallback? onBarrier;

  /// 是否有 fade 动画
  final bool hasFade;

  final Widget Function(VoidCallback onHide)? builder;

  @override
  State<NAlignmentDrawer> createState() => _NAlignmentDrawerState();
}

class _NAlignmentDrawerState extends State<NAlignmentDrawer> with SingleTickerProviderStateMixin {
  final _scrollController = ScrollController();

  Tween<Offset> get tween {
    // debugPrint("widget.alignment:${widget.alignment} ${widget.alignment.y}");
    if ([-1, 1].contains(widget.alignment.y)) {
      return Tween<Offset>(
        begin: Offset(0, widget.alignment.y),
        end: const Offset(0, 0),
      );
    }

    if ([-1, 1].contains(widget.alignment.x)) {
      return Tween<Offset>(
        begin: Offset(widget.alignment.x, 0),
        end: const Offset(0, 0),
      );
    }

    return Tween<Offset>(
      begin: Offset(0, 0),
      end: const Offset(0, 0),
    );
  }

  late final controller = AnimationController(duration: widget.duration, vsync: this,);

  late final Animation<Offset> offsetAnimation = tween.animate(CurvedAnimation(
      parent: controller,
      curve: Curves.decelerate,
  ));

  late final Animation<double> fadeAnimation = CurvedAnimation(
    parent: controller,
    curve: Curves.easeIn,
  );


  @override
  void dispose() {
    _scrollController.dispose();
    controller.removeListener(onListener);
    controller.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    controller.addListener(onListener);
    controller.forward();
  }

  void onListener(){
    setState(() {});
  }

  Future<void> onHide() async {
    await controller.reverse();
    Navigator.of(context).pop();
  }


  @override
  Widget build(BuildContext context) {
    final defaultWidget = Scaffold(
      appBar: AppBar(
          title: Text("$widget"),
          automaticallyImplyLeading: false,
          actions: [
            TextButton(
              onPressed: onHide,
              child: Text('取消',
                style: TextStyle(color: Colors.white),
              ),
            ),
          ]
      ),
      body: buildBody(),
    );

    Widget child = widget.builder?.call(onHide) ?? defaultWidget;

    if (widget.hasFade) {
      child = FadeTransition(
        opacity: fadeAnimation,
        child: child,
      );
    }

    child = SlideTransition(
      position: offsetAnimation,
      child: child,
    );

    return InkWell(
      onTap: widget.onBarrier ?? onHide,
      child: Container(
        alignment: widget.alignment,
        color: widget.barrierColor ?? Theme.of(context).bottomSheetTheme.modalBarrierColor,
        child: child,
      )
    );
  }

  buildBody() {
    final indexs = List.generate(20, (index) => index);
    return Scrollbar(
      controller: _scrollController,
      child: SingleChildScrollView(
        controller: _scrollController,
        child: Column(
          children: indexs.map((e) {

            return ListTile(
              title: Text("选项$e"),
            );
          },).toList(),
        ),
      ),
    );
  }
  
}

四、总结

1、这是我从开发 flutter 以来发现的最简单,扩展性最好的实现方式,大家如果发现更好的,可以留言哈,共同进步。
2、如果需要进一步定制,大家在此基础上修改即可,无限扩展性是初衷,不想过度封装丧失某些可能性。
3、如果弹窗内容没有限制宽高,就会显示页面进入动画效果。

github

相关推荐
coder_pig3 小时前
📝小记:Ubuntu 部署 Jenkins 打包 Flutter APK
flutter·ubuntu·jenkins
捡芝麻丢西瓜5 小时前
flutter自学笔记5- dart 编码规范
flutter·dart
恋猫de小郭5 小时前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
sunly_1 天前
Flutter:导航,tab切换,顶部固定,列表分页滚动
开发语言·javascript·flutter
敲代码的小强1 天前
Flutter项目兼容鸿蒙Next系统
flutter·华为·harmonyos
Zh-jie2 天前
flutter 快速实现侧边栏
前端·javascript·flutter
truemi.732 天前
flutter --no-color pub get 超时解决方法
android·flutter
王家视频教程图书馆2 天前
flutter 使用dio 请求go语言后台数据接口展示瀑布流图片
flutter
迷雾漫步者2 天前
Flutter组件————AppBar
flutter·跨平台·dart
AiFlutter3 天前
Flutter 开关属性
flutter