Flutter - Expand bottomNavigationBar by swiping or

2020-06-17 06:14发布

问题:

I have a main widget called DashboardWidget. Inside it, I have a Scaffold with BottomNavigationBar and a FloatingActionButton:

Now, I want to make a widget that would be dragged from the bottom by:

  1. Swiping up with the finger.
  2. Pressing on FloatingActionButton.

In other words, I want to expand the BottomNavigationBar.

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

The problem is, I'm not sure where to start to implement that. I've thought about removing the BottomNavigationBar and create a custom widget that can be expanded, but I'm not sure if it's possible either.

回答1:

Output:


I used a different approach and did it without AnimationController, GlobalKey etc, the logic code is very short (_handleClick).

I only used 4 variables, simple and short!

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(() {});
    });
  }
}


回答2:

You can use the BottomSheet class. Here is a Medium-tutorial for using that, here is a youtube-tutorial using it and here is the documentation for the class.

The only difference from the tutorials is that you have to add an extra call method for showBottomSheet from your FloatingActionButton when it is touched.

Bonus: here is the Material Design page on how to use it.



回答3:

You can check this code, it is a complete example of how to start implementing this kind of UI, take it with a grain of salt.

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;
      });
    }
  }
}

Edit: I modified the code by using Material Widget instead of A container with shadow for better performance,If you have any issue, please let me know .



标签: dart flutter