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

相关推荐
kirk_wang20 分钟前
Flutter艺术探索-PlatformView嵌入:在Flutter中显示原生View
flutter·移动开发·flutter教程·移动开发教程
ujainu21 分钟前
Flutter for OpenHarmony 悬浮操作按钮:FloatingActionButton 与扩展菜单的深度优化实践
android·flutter·组件
向哆哆23 分钟前
构建环保之旅:基于 Flutter × OpenHarmony 的垃圾回收个人成就应用
flutter·开源·鸿蒙·openharmony·开源鸿蒙
一起养小猫25 分钟前
Flutter for OpenHarmony 实战 文件存储与数据库操作完全指南
开发语言·jvm·数据库·spring·flutter·harmonyos
向哆哆1 小时前
Flutter × OpenHarmony 跨端实战:垃圾分类应用页面架构与数据结构设计详解
数据结构·flutter·开源·鸿蒙·openharmony
Mr__proto__1 小时前
Flutter 中使用 Autocomplete 实现智能模糊搜索加历史记录提示
flutter
lbb 小魔仙2 小时前
【Harmonyos】开源鸿蒙跨平台训练营DAY8:Flutter鸿蒙开发指南获取轮播图数据
flutter·华为·harmonyos
2501_944396192 小时前
Flutter for OpenHarmony 视力保护提醒App实战 - 性能优化技巧
android·flutter·性能优化
子春一2 小时前
Flutter for OpenHarmony:构建一个专业级 Flutter 节拍器,深入解析定时器、状态同步与音乐节奏交互设计
javascript·flutter·交互
kirk_wang2 小时前
Flutter艺术探索-Flutter插件开发:自定义Plugin实战指南
flutter·移动开发·flutter教程·移动开发教程