Flutter-滑动或按下floatActionButton来展开bottomNavigationBar

时间:2019-05-08 13:49:48

标签: dart flutter

我有一个名为DashboardWidget的主窗口小部件。在其中,我有一个ScaffoldBottomNavigationBar和一个FloatingActionButton

enter image description here

现在,我想制作一个可通过以下方式从底部拖动的小部件:

  1. 用手指轻扫。
  2. 按下FloatingActionButton

换句话说,我想扩展BottomNavigationBar

Here's a design concept in case I was unclear.

问题是,我不确定从哪里开始实施。我曾考虑过删除BottomNavigationBar并创建一个可以扩展的自定义窗口小部件,但是我不确定是否可行。

3 个答案:

答案 0 :(得分:6)

输出:

enter image description here


我使用了另一种方法,并且没有使用AnimationControllerGlobalKey等,因此逻辑代码非常短(_handleClick)。

我只使用了4个变量,简单而又简短!

void main() => runApp(MaterialApp(home: HomePage()));

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  static double _minHeight = 80, _maxHeight = 600;
  Offset _offset = Offset(0, _minHeight);
  bool _isOpen = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFFF6F6F6),
      appBar: AppBar(backgroundColor: Color(0xFFF6F6F6), elevation: 0),
      body: Stack(
        alignment: Alignment.bottomCenter,
        children: <Widget>[
          Align(
            alignment: Alignment.topLeft,
            child: FlatButton(
              onPressed: _handleClick,
              splashColor: Colors.transparent,
              textColor: Colors.grey,
              child: Text(_isOpen ? "Back" : ""),
            ),
          ),
          Align(child: FlutterLogo(size: 300)),
          GestureDetector(
            onPanUpdate: (details) {
              _offset = Offset(0, _offset.dy - details.delta.dy);
              if (_offset.dy < _HomePageState._minHeight) {
                _offset = Offset(0, _HomePageState._minHeight);
                _isOpen = false;
              } else if (_offset.dy > _HomePageState._maxHeight) {
                _offset = Offset(0, _HomePageState._maxHeight);
                _isOpen = true;
              }
              setState(() {});
            },
            child: AnimatedContainer(
              duration: Duration.zero,
              curve: Curves.easeOut,
              height: _offset.dy,
              alignment: Alignment.center,
              decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(30),
                    topRight: Radius.circular(30),
                  ),
                  boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 10)]),
              child: Text("This is my Bottom sheet"),
            ),
          ),
          Positioned(
            bottom: 2 * _HomePageState._minHeight - _offset.dy - 28, // 56 is the height of FAB so we use here half of it.
            child: FloatingActionButton(
              child: Icon(_isOpen ? Icons.keyboard_arrow_down : Icons.add),
              onPressed: _handleClick,
            ),
          ),
        ],
      ),
    );
  }

  // first it opens the sheet and when called again it closes.
  void _handleClick() {
    _isOpen = !_isOpen;
    Timer.periodic(Duration(milliseconds: 5), (timer) {
      if (_isOpen) {
        double value = _offset.dy + 10; // we increment the height of the Container by 10 every 5ms 
        _offset = Offset(0, value);
        if (_offset.dy > _maxHeight) {
          _offset = Offset(0, _maxHeight); // makes sure it does't go above maxHeight
          timer.cancel();
        }
      } else {
        double value = _offset.dy - 10; // we decrement the height by 10 here
        _offset = Offset(0, value);
        if (_offset.dy < _minHeight) {
          _offset = Offset(0, _minHeight); // makes sure it doesn't go beyond minHeight
          timer.cancel();
        }
      }
      setState(() {});
    });
  }
}

答案 1 :(得分:2)

您可以使用BottomSheet类。 这是一个使用Medium-tutorial的地方,这里是一个使用youtube-tutorial的地方,这是该课程的documentation

与本教程的唯一区别是,当您触摸showBottomSheet时,必须为FloatingActionButton添加一个额外的调用方法。

奖金:这是Material Design页面,介绍如何使用它。

答案 2 :(得分:2)

您可以检查此代码,它是如何开始实现这种UI的完整示例,请花点精力。

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rxdart/rxdart.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Orination Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  bool _isOpen;
  double _dragStart;
  double _hieght;
  double _maxHight;
  double _currentPosition;
  GlobalKey _cardKey;
  AnimationController _controller;
  Animation<double> _cardAnimation;

  @override
  void initState() {
    _isOpen = false;
    _hieght = 50.0;
    _cardKey = GlobalKey();
    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 700));
    _cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
        CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
    );

    _controller.addListener(() {
      setState(() {
        _hieght = _cardAnimation.value;
      });
    });

    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(

      appBar: AppBar(
        elevation: 0.0,
        backgroundColor: Colors.transparent,
        titleSpacing: 0.0,
        title: _isOpen
            ? MaterialButton(
          child: Text(
            "Back",
            style: TextStyle(color: Colors.red),
          ),
          onPressed: () {
            _isOpen = false;
            _cardAnimation = Tween(begin: _hieght, end: 50.0).animate(
                CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
            );
            _controller.forward(from: 0.0);
          },
        )
            : Text(""),
      ),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.keyboard_arrow_up),
          onPressed: () {
            final RenderBox renderBoxCard = _cardKey.currentContext
                .findRenderObject();
            _maxHight = renderBoxCard.size.height;
            _cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
                CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
            );
            _controller.forward(from: 0.0);
            _isOpen = true;
          }),
      body: Stack(
        key: _cardKey,
        alignment: Alignment.bottomCenter,
        children: <Widget>[
          Container(
            width: double.infinity,
            height: double.infinity,
            color: Colors.black12,
          ),
          GestureDetector(
            onPanStart: _onPanStart,
            onPanUpdate: _onPanUpdate,
            onPanEnd: _onPanEnd,
            child:Material(
              borderRadius: BorderRadius.only(
                    topRight: Radius.circular(16.0),
                    topLeft: Radius.circular(16.0),
              ),
              elevation: 60.0,
              color: Colors.white,
             // shadowColor: Colors.,
              child: Container(
                height: _hieght,

                child: Center(
                  child: Text("Hello, You can drag up"),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _onPanStart(DragStartDetails details) {
    _dragStart = details.globalPosition.dy;

    _currentPosition = _hieght;
  }

  void _onPanUpdate(DragUpdateDetails details) {
    final RenderBox renderBoxCard = _cardKey.currentContext.findRenderObject();
    _maxHight = renderBoxCard.size.height;
    final hieght = _currentPosition - details.globalPosition.dy + _dragStart;
    print(
        "_currentPosition = $_currentPosition _hieght = $_hieght hieght = $hieght");
    if (hieght <= _maxHight && hieght >= 50.0) {
      setState(() {
        _hieght = _currentPosition - details.globalPosition.dy + _dragStart;
      });
    }
  }

  void _onPanEnd(DragEndDetails details) {
    _currentPosition = _hieght;
    if (_hieght <= 60.0) {
      setState(() {
        _isOpen = false;
      });
    } else {
      setState(() {
        _isOpen = true;
      });
    }
  }
}

编辑:我使用Material Widget而不是带有阴影的容器修改了代码,以提高性能,如果有任何问题,请告诉我。