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

相关推荐
雾沉川2 小时前
Flutter 入门开发环境完整搭建教程
学习·flutter
MemoriKu3 小时前
Flutter 本地 AI 相册工程收口:从屏幕常亮、标签体系到照片属性后台队列
大数据·人工智能·python·flutter·elasticsearch·搜索引擎·数据库架构
Prowler_92565 小时前
创新项目实训博客(十一):大模型智能标题生成与多级降维兜底策略
人工智能·flutter·aigc
不良使6 小时前
鸿蒙PC迁移_LocalSend 迁移到鸿蒙 PC:一次 Flutter + Rust + 三方库适配的完整记录
flutter·rust·harmonyos
恋猫de小郭7 小时前
由于 iOS 26 的键盘变化,Flutter 又要重构键盘区域逻辑
android·前端·flutter
风华圆舞1 天前
在 Flutter 鸿蒙项目里接入文本转语音的完整思路
flutter·华为·harmonyos
勤劳打代码1 天前
翻江倒海——滚动布局下拉视图管理
flutter·前端框架·开源
spmcor1 天前
Flutter 学习笔记 (6):路由与导航 —— 从基础 push/pop 到 go_router
flutter
风华圆舞2 天前
在 Flutter 鸿蒙项目里接入语音识别的完整思路
flutter·语音识别·harmonyos
风华圆舞2 天前
鸿蒙 + Flutter 下如何让 HarmonyOS 能力真正服务于 AI 体验
人工智能·flutter·harmonyos