如何在 Flutter 上移动脚手架的 body 和 bottomSheet?

How to move Scaffold's body along with bottomSheet on Flutter?

我试图在显示 BottomSheet 时为我的脚手架实现特定行为。我希望脚手架的 body 与底部 sheet 一起移动。也就是说,当Bottomheet出来的时候,Scaffold的body应该会跟着一起上去。就像右图一样。我不确定我的方法是否正确。也许还有其他更好的选择可以使这种行为成为可能。

我目前使用的代码在这里:

 Scaffold(
  backgroundColor: Colors.purple[100],
  resizeToAvoidBottomInset: true,
  body: SingleChildScrollView(
    scrollDirection: Axis.vertical,
    child: Container(
      height: 900,
      child: Builder(
        builder: (context) => Container(
          child: GestureDetector(
            behavior: HitTestBehavior.translucent,
            onTap: () {
              FocusScope.of(context).requestFocus(_focusNode);
              if (bottomSheetIsOpen) {
                bottomSheetIsOpen = false;
                Navigator.of(context).pop();
              }
            },
            child: Container(
              width: double.infinity,
              height: double.infinity,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  SizedBox(height: 50),
                  Container(
                    decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(10),
                    ),
                    width: 300,
                    child: TextField(
                      cursorWidth: 3,
                      cursorColor: Colors.purple,
                      onTap: () {
                        bottomSheetIsOpen = true;
                        showBottomSheet(
                          clipBehavior: Clip.hardEdge,
                          context: context,
                          builder: (context) => Container(
                            child: Container(
                              height: 200,
                              color: Colors.red,
                            ),
                          ),
                        );
                      },
                      controller: _controller,
                      decoration: InputDecoration(
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(10),
                        ),
                      ),
                      style: TextStyle(fontSize: 24),
                      showCursor: true,
                      readOnly: _readOnly,
                    ),
                  ),
                  Container(
                    height: 300,
                    width: 300,
                    color: Colors.yellow,
                  ),
                  Container(
                    height: 250,
                    width: 300,
                    color: Colors.orange,
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    ),
  ),
);

您可以将新小部件添加到列

,而不是显示底部 sheet

reserve:true是导航到底部的关键参数

喜欢:

return Scaffold(
  body: SingleChildScrollView(
    reserve: true,
    child: Column(
      children: [
        YourWidget(),
        if (isOpenBottomSheet)
          YourBottomSheet()
      ],
    ),
  ),
);


完整示例:


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  bool isOpenBottomSheet = false;
  final _controller = ScrollController();

  void _incrementCounter() {
    setState(() {
      isOpenBottomSheet = !isOpenBottomSheet;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        controller: _controller,
        reverse: true,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // your widget
            Container(
                height: MediaQuery.of(context).size.height,
                color: Colors.black),
            // your bottom sheet
            if (isOpenBottomSheet) Container(height: 400, color: Colors.yellow),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

您可以使用 sliding_up_panel 视差效果:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text("SlidingUpPanelExample"),
    ),
    body: SlidingUpPanel(
      parallaxEnabled: true,
      parallaxOffset: 0.4
      panel: Center(
        child: Text("This is the sliding Widget"),
      ),
      body: Center(
        child: Text("This is the Widget behind the sliding panel"),
      ),
    ),
  );
}

您可以使用一个 Stack 和两个 AnimatedPositioned 小部件实现此目的:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Bottomsheet Demo',
      debugShowCheckedModeBanner: false,
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final _isOpenBottomSheet = useState(false);
    return Scaffold(
      appBar: AppBar(title: Text('Bottomsheet Demo')),
      body: LayoutWithBottomSheet(
        children: List.generate(
          10,
          (index) => Container(
            height: 100,
            color: Colors.red.withGreen(index * 25),
            child: Center(
              child: Text(
                index.toString(),
                style: TextStyle(fontSize: 24.0),
              ),
            ),
          ),
        ).toList(),
        bottomSheetChild: Container(color: Colors.yellow),
        bottomSheetHeight: 400,
        animationSpeed: Duration(milliseconds: 300),
        animationCurve: Curves.easeInOutQuad,
        isOpenBottomSheet: _isOpenBottomSheet.value,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _isOpenBottomSheet.value = !_isOpenBottomSheet.value;
        },
        child: Icon(_isOpenBottomSheet.value
            ? Icons.arrow_downward
            : Icons.arrow_upward),
      ),
    );
  }
}

class LayoutWithBottomSheet extends HookWidget {
  final List<Widget> children;
  final Widget bottomSheetChild;
  final Duration animationSpeed;
  final Curve animationCurve;
  final double bottomSheetHeight;
  final bool isOpenBottomSheet;

  const LayoutWithBottomSheet({
    Key key,
    this.children,
    this.bottomSheetChild,
    this.animationSpeed,
    this.animationCurve,
    this.bottomSheetHeight,
    this.isOpenBottomSheet,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final _scrollController = useScrollController();
    final childrenBottom = useState<double>();
    final bottomSheetBottom = useState<double>();
    useEffect(() {
      if (isOpenBottomSheet) {
        childrenBottom.value = bottomSheetHeight;
        bottomSheetBottom.value = 0;
        if (_scrollController.hasClients) {
          Future.microtask(
            () => _scrollController.animateTo(
              _scrollController.offset + bottomSheetHeight,
              duration: animationSpeed,
              curve: animationCurve,
            ),
          );
        }
      } else {
        childrenBottom.value = 0;
        bottomSheetBottom.value = -bottomSheetHeight;
        if (_scrollController.hasClients) {
          _scrollController.animateTo(
            _scrollController.offset - bottomSheetHeight,
            duration: animationSpeed,
            curve: animationCurve,
          );
        }
      }
      return;
    }, [isOpenBottomSheet]);
    return Stack(
      children: [
        AnimatedPositioned(
          duration: animationSpeed,
          curve: animationCurve,
          left: 0,
          right: 0,
          top: 0,
          bottom: childrenBottom.value,
          child: ListView(
            controller: _scrollController,
            children: children,
          ),
        ),
        AnimatedPositioned(
          duration: animationSpeed,
          curve: animationCurve,
          left: 0,
          right: 0,
          bottom: bottomSheetBottom.value,
          height: bottomSheetHeight,
          child: bottomSheetChild,
        ),
      ],
    );
  }
}