TreeviewCopyright © aleen42 all right reserved, powered by aleen42

在 Android 中,如果 ScrollView 包含的一个 RecyclerView,则会造成滑动冲突,需要我们自行处理。

而在 Flutter 中,为我们提供了 NestedScrollView 来帮助我们解决多个滑动 Widget 嵌套的问题。

NestedScrollView 通过为外部 ScrollView 和内部 ScrollView 提供自定义 ScrollController 来解决此问题,将它们链接在一起,以便它们作为一个连贯的滚动视图显示给用户。

构造方法

  const NestedScrollView({
    Key key,
    this.controller,
    this.scrollDirection = Axis.vertical,
    this.reverse = false,
    this.physics,
    @required this.headerSliverBuilder,
    @required this.body,
    this.dragStartBehavior = DragStartBehavior.start,
  })
  • controller,控制器,控制 Widget 的滚动

  • scrollDirection,设置视图的滚动方向

  • reverse,是否按照阅读方向相反的方向滑动,设置水平滚动时

    设置水平滚动时

    • reverse: false 时,则滚动内容头部和左侧对其, 那么滑动方向就是从左向右
    • reverse: true 时,则滚动内容尾部和右侧对其, 那么滑动方向就是从右往左。

    其实此属性本质上是决定可滚动 widget 的初始滚动位置是在头还是尾,取 false 时,初始滚动位置在头,反之则在尾

  • physics,接受一个 ScrollPhysics 对象,它决定可滚动 Widget 如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。

    默认情况下,Flutter 会根据具体平台分别使用不同的 ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在 iOS 上会出现弹性效果,而在 Android 上会出现微光效果。

    如果你想在所有平台下使用同一种效果,可以显式指定,Flutter SDK 中包含了两个 ScrollPhysics 的子类可以直接使用:

    • ClampingScrollPhysics:安卓下微光效果。
    • BouncingScrollPhysicsiOS 下弹性效果。
  • headerSliverBuilder,通常是带有 TabBarSliverAppBar

  • body,主要部分,通常是一个 TabBarView,显示的主体部分

  • dragStartBehavior,确定处理拖动开始行为的方式。

示例

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Text Demo',
      theme: ThemeData(primarySwatch: Colors.green),
      home: MyHomePage(title: 'Text Demo'),
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {
  Choice _selectedChoice = choices[0]; // The app's "state".

  void _select(Choice choice) {
    setState(() {
      // Causes the app to rebuild with the new _selectedChoice.
      _selectedChoice = choice;
    });
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: choices.length,
      child: Scaffold(
        body: NestedScrollView(
          headerSliverBuilder: _headerSliverBuilder,
          body: TabBarView(
            children: choices.map((Choice choice) {
              return Padding(
                padding: const EdgeInsets.all(16.0),
                child: ChoiceCard(choice: choice),
              );
            }).toList(),
          ),
        ),
      ),
    );
  }

  List<Widget> _headerSliverBuilder(
      BuildContext context, bool innerBoxIsScrolled) {
    return <Widget>[
      SliverAppBar(
        //1.在标题左侧显示的一个控件,在首页通常显示应用的 logo;在其他界面通常显示为返回按钮
        leading: Icon(_selectedChoice.icon),

        //2. ? 控制是否应该尝试暗示前导小部件为null
        automaticallyImplyLeading: true,

        //3.当前界面的标题文字
        title: Text(_selectedChoice.title),

        //4.一个 Widget 列表,代表 Toolbar 中所显示的菜单,对于常用的菜单,通常使用 IconButton 来表示;
        //对于不常用的菜单通常使用 PopupMenuButton 来显示为三个点,点击后弹出二级菜单
        actions: <Widget>[
          IconButton(
            // action button
            icon: Icon(choices[0].icon),
            onPressed: () {
              _select(choices[0]);
            },
          ),
          IconButton(
            // action button
            icon: Icon(choices[1].icon),
            onPressed: () {
              _select(choices[1]);
            },
          ),
          PopupMenuButton<Choice>(
            // overflow menu
            onSelected: _select,
            itemBuilder: (BuildContext context) {
              return choices.skip(2).map((Choice choice) {
                return PopupMenuItem<Choice>(
                  value: choice,
                  child: Text(choice.title),
                );
              }).toList();
            },
          )
        ],

        //5.一个显示在 AppBar 下方的控件,高度和 AppBar 高度一样,
        // 可以实现一些特殊的效果,该属性通常在 SliverAppBar 中使用
        flexibleSpace: FlexibleSpaceBar(
          centerTitle: true,
          background: Image(
            image: AssetImage(
                "assets/images/girl.png"),
            fit: BoxFit.cover,
          ),
        ),

        //6.一个 AppBarBottomWidget 对象,通常是 TabBar。用来在 Toolbar 标题下面显示一个 Tab 导航栏
        bottom: TabBar(
          isScrollable: true,
          tabs: choices.map(
            (Choice choice) {
              return Tab(
                text: choice.title,
                icon: Icon(choice.icon),
              );
            },
          ).toList(),
        ),

        //7.? 材料设计中控件的 z 坐标顺序,默认值为 4,对于可滚动的 SliverAppBar,
        // 当 SliverAppBar 和内容同级的时候,该值为 0, 当内容滚动 SliverAppBar 变为 Toolbar 的时候,修改 elevation 的值
        elevation: 1,

        //APP bar 的颜色,默认值为 ThemeData.primaryColor。改值通常和下面的三个属性一起使用
        backgroundColor: Colors.red,

        //App bar 的亮度,有白色和黑色两种主题,默认值为 ThemeData.primaryColorBrightness
        brightness: Brightness.light,

        //App bar 上图标的颜色、透明度、和尺寸信息。默认值为 ThemeData().primaryIconTheme
        iconTheme: ThemeData().primaryIconTheme,

        //App bar 上的文字主题。默认值为 ThemeData().primaryTextTheme
        textTheme: ThemeData().accentTextTheme,

        //此应用栏是否显示在屏幕顶部
        primary: true,

        //标题是否居中显示,默认值根据不同的操作系统,显示方式不一样,true居中 false居左
        centerTitle: true,

        //横轴上标题内容 周围的间距
        titleSpacing: NavigationToolbar.kMiddleSpacing,

        //展开高度
        expandedHeight: 200,

        //是否随着滑动隐藏标题
        floating: true,

        //tab 是否固定在顶部
        pinned: true,

        //与floating结合使用
        snap: true,
      )
    ];
  }
}

class Choice {
  const Choice({this.title, this.icon});

  final String title;
  final IconData icon;
}

const List<Choice> choices = const <Choice>[
  const Choice(title: 'Car', icon: Icons.directions_car),
  const Choice(title: 'Bicycle', icon: Icons.directions_bike),
  const Choice(title: 'Boat', icon: Icons.directions_boat),
  const Choice(title: 'Bus', icon: Icons.directions_bus),
  const Choice(title: 'Train', icon: Icons.directions_railway),
  const Choice(title: 'Walk', icon: Icons.directions_walk),
];

class ChoiceCard extends StatelessWidget {
  const ChoiceCard({Key key, this.choice}) : super(key: key);

  final Choice choice;

  @override
  Widget build(BuildContext context) {
    final TextStyle textStyle = Theme.of(context).textTheme.display1;

    Widget _itemBuilder(BuildContext context, int index) {
      return ListTile(
        leading: Icon(choice.icon),
        title: Text("this is a " + choice.title),
      );
    }

    return Card(
      color: Colors.white,
      child: Center(
        child: ListView.builder(
          itemBuilder: _itemBuilder,
          itemCount: 30,
        ),
      ),
    );
  }
}

效果如下:

results matching ""

    No results matching ""