一、需求来源
一直在琢磨如何实现任意方向的弹窗,上周突然灵光一闪,实现了,分享给大家。 核心是通过传入参数 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(),
        ),
      ),
    );
  }
  
}