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

相关推荐
一头小火烧1 小时前
flutter打包签名问题
flutter
sunly_1 小时前
Flutter:异步多线程结合
flutter
AiFlutter1 小时前
Flutter网络通信-封装Dio
flutter
B.-1 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio
有趣的杰克1 小时前
Flutter【04】高性能表单架构设计
android·flutter·dart
sunly_13 小时前
Flutter:父组件,向子组件传值,子组件向二级页面传值
flutter
爱学习的绿叶16 小时前
flutter TabBarView 动态添加删除页面
flutter
趴菜小玩家18 小时前
使用 Gradle 插件优化 Flutter Android 插件开发中的 Flutter 依赖缺失问题
android·flutter·gradle
jhonjson2 天前
Flutter开发之flutter_local_notifications
flutter·macos·cocoa
iFlyCai2 天前
23种设计模式的Flutter实现第一篇创建型模式(一)
flutter·设计模式·dart