flutter 写一个BottomSheet

2021-11-26  本文已影响0人  卢融霜

效果图

底部弹出.gif

网页版(第一次打开网址加载会很慢)
网页版

布局结构

  StatefulWidget
        //控制动画
        AnimatedBuilder
                //控制位置
                Positioned         
                        //手势监听
                        GestureDetector
                                //内部布局
                                Container

各个widget用到的方法

AnimatedBuilder

  //控制器
_controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 600),
);

//动画状态
_controller.status     返回: AnimationStatus(dismissed:关闭,forward:向前,reverse:向后,completed:完成)

//velocity  传递正数 执行到 upperBound 值  负数 执行到 lowerBound 值
_controller.fling(velocity: isOpen ? -1 : 1);

//动画执行的数值 默认 0.0 - 1.0
_controller.value

GestureDetector

 //监听垂直方向的滑动
onVerticalDragUpdate
 //监听垂直方向,停止滑动时的移动速度
onVerticalDragEnd

其他

// 根据 _controller.value 的0 - 1的值,返回 minHeight - maxHeight 相对于的值,完成高度设置
lerpDouble(num? a, num? b, double t)

核心逻辑代码

  /// 滑动核心代码, 根据 _controller.value 的0 - 1的值,返回 minHeight - maxHeight 相对于的值,完成高度设置
  double lerp(double minHeight, double maxHeight) {
    var height = lerpDouble(minHeight, maxHeight, _controller.value);
    return height;
  }
  ///开关切换
  void _toggle() {
    //判断打开状态 进行切换
    bool isOpen = _controller.status == AnimationStatus.completed;
    //velocity  传递正数 执行到 upperBound 值  负数 执行到 lowerBound 值
    _controller.fling(velocity: isOpen ? -1 : 1);
  }
  ///滑动关键代码,监听纵向滑动阀值,设置响应高度
  void _dragUpdate(DragUpdateDetails details) {
    _controller.value -= details.primaryDelta / widget.maxHeight;
  }

  /// 开关核心代码: 根据滑动速度,滑动高度,来判断 开关
  void _handleDragEnd(DragEndDetails details) {

    //排除 动画过程中,和完成状态。
    if (_controller.isAnimating ||
        _controller.status == AnimationStatus.completed) return;

    //获取滑动速度
    final double flingVelocity =
        details.velocity.pixelsPerSecond.dy / widget.maxHeight;

    //设置滑动执行方向
    if (flingVelocity < 0.0) {
      _controller.fling(velocity: max(1.0, -flingVelocity));
    } else if (flingVelocity > 0.0) {
      _controller.fling(velocity: min(-1.0, -flingVelocity));
    } else {
      _controller.fling(velocity: _controller.value < 0.5 ? -1.0 : 1.0);
    }
  }

整体代码

使用

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

import 'base/base_routes_widget.dart';
import 'bottom_sheet.dart';

// @description 作用:
// @date: 2021/11/25
// @author: 卢融霜

class BottomSheetRoutes extends StatefulWidget {
  const BottomSheetRoutes({Key key}) : super(key: key);

  @override
  _BottomSheetRoutesState createState() => _BottomSheetRoutesState();
}

class _BottomSheetRoutesState extends State<BottomSheetRoutes> {
  @override
  Widget build(BuildContext context) {
    return BaseRoutesWidget(
      title: "底部弹出",
      child: SizedBox(
        width: double.infinity,
        child: Stack(
          children: [
            Column(
              children: const [Text("底部弹出")],
            ),
            MyBottomSheet(100.r, MediaQuery.of(context).size.height - 90.r)
          ],
        ),
      ),
    );
  }
}

组件

import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

// @description 作用:底部弹出
// @date: 2021/11/25
// @author: 卢融霜

class MyBottomSheet extends StatefulWidget {
  double minHeight;
  double maxHeight;

  MyBottomSheet(this.minHeight, this.maxHeight, {Key key}) : super(key: key);

  @override
  _BottomSheetState createState() => _BottomSheetState();
}

class _BottomSheetState extends State<MyBottomSheet>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 600),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Stack(
            children: [
              Positioned(
                  height: lerp(widget.minHeight, widget.maxHeight),
                  left: 0,
                  right: 0,
                  bottom: 0,
                  child: GestureDetector(
                    onVerticalDragUpdate: _dragUpdate,
                    onVerticalDragEnd: _handleDragEnd,
                    child: Container(
                        decoration: const BoxDecoration(
                          color: Colors.blueAccent,
                          borderRadius:
                              BorderRadius.vertical(top: Radius.circular(32)),
                        ),
                        padding: const EdgeInsets.symmetric(horizontal: 32),
                        child: Container(
                          alignment: Alignment.topRight,
                          padding: EdgeInsets.only(top: 20.r),
                          child: InkWell(
                            onTap: _toggle,
                            child: Text(
                              "切换",
                              style: TextStyle(
                                  fontSize: 14.sp, color: Colors.white),
                            ),
                          ),
                        )),
                  ))
            ],
          );
        });
  }

  /// 滑动核心代码, 根据 _controller.value 的0 - 1的值,返回 minHeight - maxHeight 相对于的值,完成高度设置
  double lerp(double minHeight, double maxHeight) {
    var height = lerpDouble(minHeight, maxHeight, _controller.value);
    return height;
  }

  ///开关切换
  void _toggle() {
    //判断打开状态 进行切换
    bool isOpen = _controller.status == AnimationStatus.completed;
    //velocity  传递正数 执行到 upperBound 值  负数 执行到 lowerBound 值
    _controller.fling(velocity: isOpen ? -1 : 1);
  }

  ///滑动关键代码,监听纵向滑动阀值,设置响应高度
  void _dragUpdate(DragUpdateDetails details) {
    _controller.value -= details.primaryDelta / widget.maxHeight;
  }

  /// 开关核心代码: 根据滑动速度,滑动高度,来判断 开关
  void _handleDragEnd(DragEndDetails details) {

    //排除 动画过程中,和完成状态。
    if (_controller.isAnimating ||
        _controller.status == AnimationStatus.completed) return;

    //获取滑动速度
    final double flingVelocity =
        details.velocity.pixelsPerSecond.dy / widget.maxHeight;


    //设置滑动执行方向
    if (flingVelocity < 0.0) {
      _controller.fling(velocity: max(1.0, -flingVelocity));
    } else if (flingVelocity > 0.0) {
      _controller.fling(velocity: min(-1.0, -flingVelocity));
    } else {
      _controller.fling(velocity: _controller.value < 0.5 ? -1.0 : 1.0);
    }
  }
}

上一篇下一篇

猜你喜欢

热点阅读