Flutter:使用 Sliver 小部件时如何在滚动时隐藏 BottomAppBar?

Posted

技术标签:

【中文标题】Flutter:使用 Sliver 小部件时如何在滚动时隐藏 BottomAppBar?【英文标题】:Flutter: How to hide BottomAppBar on scroll when using Sliver widgets? 【发布时间】:2019-07-28 16:54:26 【问题描述】:

当用户向下滚动列表时,我试图隐藏底部的应用栏,就像它在行为部分的材料设计文档中显示的那样:

https://material.io/design/components/app-bars-bottom.html#behavior

我正在寻找的效果如下所示: https://storage.googleapis.com/spec-host-backup/mio-design%2Fassets%2F1gaSrddolFHd8BwOioeYz0ODiyEhCevtn%2Fbehavior-scroll.mp4

到目前为止,我在 Scaffold 中添加了底部应用栏,但它没有滚动行为。

在 *** 上回答了一个类似的问题,但它显示了一种解决方法,并且已经在不久前得到了回答。

在 Flutter 中是否有合法的方式来实现这种效果?

我的代码:

import 'package:fitness_fatality_flutter/data/entities/exercise.dart';
import 'package:fitness_fatality_flutter/data/entities/workout.dart';
import 'package:flutter/material.dart';

class WorkoutDetailsPage extends StatelessWidget 
  Workout _workout = Workout();

  final List<Exercise> exercises = [
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
  ];

  WorkoutDetailsPage(this._workout);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling,
      floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.blue,
        elevation: 12,
        onPressed: () ,
      ),
      bottomNavigationBar: BottomAppBar(
        elevation: 8,
        shape: CircularNotchedRectangle(),
        color: Colors.blue,
        child: Container(
          height: 50,
          child: Row(
            children: <Widget>[Text("data")],
          ),
        ),
      ),
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            bottom:BottomAppBar(), //<-- This would throw an error
            expandedHeight: 150,
            pinned: true,
            floating: true,
            snap: true,
            flexibleSpace: FlexibleSpaceBar(
              title: Text(_workout.name),
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(buildSliverListItem,
                childCount: exercises.length),
          ),
        ],
      ),
    );
  

  Widget buildSliverListItem(BuildContext context, int index) 
    return Center(
      child: ListTile(
        title: Text(exercises[index].name),
      ),
    );
  

【问题讨论】:

【参考方案1】:

BottomAppBarScaffold 小部件的一部分,而不是 Sliver。有一种方法可以实现您所描述的,但它有点复杂,需要StatefulWigetScrollControllerAnimatedContainer。请参阅下面的完整示例代码:

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

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

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: WorkoutDetailsPage(Workout()),
    );
  


class Exercise 
  String name;
  Exercise(@required name) 
    this.name = name;
  


class Workout 
  String name = "my name";


class WorkoutDetailsPage extends StatefulWidget 
  Workout _workout = Workout();

  WorkoutDetailsPage(this._workout);

  @override
  _WorkoutDetailsPageState createState() => _WorkoutDetailsPageState();


class _WorkoutDetailsPageState extends State<WorkoutDetailsPage> 
  final List<Exercise> exercises = [
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
    Exercise(name: "Push Ups"),
    Exercise(name: "Bench press"),
    Exercise(name: "Pull ups"),
    Exercise(name: "Press ups"),
    Exercise(name: "Crunches"),
    Exercise(name: "Sit ups"),
    Exercise(name: "BIceps curl"),
    Exercise(name: "Something else"),
  ];

  ScrollController _hideButtonController;

  bool _isVisible = true;
  @override
  void initState() 
    super.initState();
    _isVisible = true;
    _hideButtonController = new ScrollController();
    _hideButtonController.addListener(() 
      print("listener");
      if (_hideButtonController.position.userScrollDirection ==
          ScrollDirection.reverse) 
        setState(() 
          _isVisible = false;
          print("**** $_isVisible up");
        );
      
      if (_hideButtonController.position.userScrollDirection ==
          ScrollDirection.forward) 
        setState(() 
          _isVisible = true;
          print("**** $_isVisible down");
        );
      
    );
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling,
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
      floatingActionButton: _isVisible
          ? FloatingActionButton(
              backgroundColor: Colors.blue,
              elevation: 12,
              onPressed: () ,
            )
          : null,
      bottomNavigationBar: AnimatedContainer(
        duration: Duration(milliseconds: 200),
        height: _isVisible ? 60 : 0.0,
        child: BottomAppBar(
          elevation: 8,
          shape: CircularNotchedRectangle(),
          color: Colors.blue,
          child: Container(
            height: 60,
            child: Row(
              children: <Widget>[Text("data")],
            ),
          ),
        ),
      ),
      body: CustomScrollView(
        controller: _hideButtonController,
        slivers: <Widget>[
          SliverAppBar(
            expandedHeight: 150,
            pinned: true,
            floating: true,
            snap: true,
            flexibleSpace: FlexibleSpaceBar(
              title: Text(widget._workout.name),
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(buildSliverListItem,
                childCount: exercises.length),
          ),
        ],
      ),
    );
  

  Widget buildSliverListItem(BuildContext context, int index) 
    return Center(
      child: ListTile(
        title: Text(exercises[index].name),
      ),
    );
  


【讨论】:

以上是关于Flutter:使用 Sliver 小部件时如何在滚动时隐藏 BottomAppBar?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter - 如何在图像下方使用带有图像和文本/图标的容器小部件

Flutter-AppBar

如何在 Flutter 小部件测试中执行缩放(捏缩放)多指手势?

如何在 Flutter 小部件中测试回调函数

Flutter:如何在构造函数中传递值,以便我可以重用我的小部件?

Flutter 如何将小部件扩展为具有更多内容的更大小部件?