Flutter 滚动组件

Flutter 滚动控件

SingleChildScrollView

滚动组件,类似于Android中的ScrollView,只能接收一个子元素。

scrollDirection:滚动方向。

BouncingScrollPhysics:滚动到边界效果。

  • ClampingScrollPhysics:类似安卓效果,会有微光显示。
  • BouncingScrollPhysics:类似ios效果,会有回弹。

在这里插入图片描述

Scrollbar(
    child: Container(
        width: double.infinity,
        child: SingleChildScrollView(
            physics: BouncingScrollPhysics(),
            padding: EdgeInsets.all(10),
            child: Column(
                children:
                str.split("").map((e) => Text(e, textScaleFactor: 2)).toList(),
            ),
        ),
    ),
)

ListView

itemExtent:子组件长度,指定itemExtent后有利于提高性能,避免每次构建子组件时再次计算。

prototypeItem:列表项原型,指定子元素的Widget。有利于提供ListView性能,与itemExtent互斥。

shrinkWrap:是否根据子组件的总长度设置ListView的长度,默认为false。如果为false时,ListView会在主轴方向尽可能多占空间。

默认构造函数

适合数据量比较小。

ListView(
    shrinkWrap: true,
    padding: EdgeInsets.all(20),
    children: [
        Text("A"),
        Text("B"),
        Text("C"),
        Text("D"),
    ],
)

ListView.builder

适合数据量比较多或不确定的情况。

itemCount:子元素数量。如果为null,表示无限列表。

itemBuilder:设置子元素的Widget。

ListView.builder(
    itemCount: null,
    itemExtent: 50,
    itemBuilder: (BuildContext context, int index) {
        return ListTile(title: Text("$index"));
    }
)

ListView.separated

可以给ListView添加一个分割线。

Widget divider1 = Divider(color: Colors.red);
Widget divider2 = Divider(color: Colors.blue);

ListView.separated(
    itemCount: 100,
    itemBuilder: (BuildContext context, int index) {
        return ListTile(title: Text("$index"));
    },
    separatorBuilder: (BuildContext context, int index) {
        return index % 2 == 0 ? divider1 : divider2;
    },
)

ScrollController

监听滚动组件。

offset:滚动距离。

animateTo & jumpTo:滚动到指定位置,前者滚动时会执行动画,后者则不会。

addListener:监听滚动组件。

在这里插入图片描述

在这里插入图片描述


class ListViewPage extends StatefulWidget {
    @override
    State<StatefulWidget> createState() {
        return _ListViewPageState();
    }
}

class _ListViewPageState extends State<ListViewPage> {
    final ScrollController _controller = ScrollController();
    bool showTopBtn = false;

    @override
    void initState() {
        super.initState();
        _controller.addListener(() {
            print("滚动距离:${_controller.offset}");
            if (_controller.offset < 1000) {
                if (showTopBtn) {
                    showTopBtn = false;
                    setState(() {});
                }
            } else {
                if (!showTopBtn) {
                    showTopBtn = true;
                    setState(() {});
                }
            }
        });
    }

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

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text("ListView"),
                actions: [],
            ),
            body: ListView.builder(
                itemCount: null,
                itemExtent: 50,
                physics: BouncingScrollPhysics(),
                controller: _controller,
                itemBuilder: (BuildContext context, int index) {
                    return ListTile(title: Text("$index"));
                }),
            floatingActionButton: !showTopBtn
            ? null
            : FloatingActionButton(
                onPressed: () {
                    _controller.animateTo(
                        0,
                        duration: Duration(microseconds: 800),
                        curve: Curves.ease,
                    );
                },
                child: Icon(Icons.arrow_upward),
            ),
        );
    }
}

NotificationListener

可以监听ListView、NestedScrollView、GridView的滚动监听。

NotificationListener与ScrollController对比

  • NotificationListener可以在任意位置监听,并且携带的信息更多。
  • ScrollController只能和具体的滚动组件关联后才能监听,只能获取当前滚动位置。

ScrollNotification类里包含一个metrics属性,其类型是ScrollMetrics,包含一些信息:

var extentBefore = notification.metrics.extentBefore; //已滑出ViewPort顶部的长度,已滚动距离
var extentInside = notification.metrics.extentInside; //ViewPort内部长度,表示屏幕显示的列表部分的长度
var extentAfter = notification.metrics.extentAfter; //未划入ViewPort部分的长度
var pixels = notification.metrics.pixels; //当前滚动距离
var maxScrollExtent = notification.metrics.maxScrollExtent; //最大可滚动距离

在这里插入图片描述

class NotificationListenerPage extends StatefulWidget {
    @override
    State<StatefulWidget> createState() {
        return _NotificationListenerPageState();
    }
}

class _NotificationListenerPageState extends State<NotificationListenerPage> {
    String _progress = "0%";

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text("滚动监听")),
            body: Scrollbar(
                child: NotificationListener<ScrollNotification>(
                    onNotification: (ScrollNotification notification) {
                        double progress = notification.metrics.pixels /
                            notification.metrics.maxScrollExtent;
                        setState(() => _progress = "${(progress * 100).toInt()}%");
                        return false;
                    },
                    child: Stack(
                        alignment: Alignment.center,
                        children: [
                            ListView.builder(
                                itemCount: 100,
                                itemExtent: 50,
                                itemBuilder: (context, index) => ListTile(
                                    title: Text("$index"),
                                ),
                            ),
                            CircleAvatar(
                                radius: 30,
                                child: Text(_progress),
                                backgroundColor: Colors.transparent.withAlpha(200),
                            ),
                        ],
                    ),
                ),
            ),
        );
    }
}

AnimatedList

在这里插入图片描述

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

class AnimatedListPage extends StatefulWidget {
    @override
    State<StatefulWidget> createState() {
        return _AnimatedListPageState();
    }
}

class _AnimatedListPageState extends State<AnimatedListPage> {
    final _listKey = GlobalKey<AnimatedListState>();
    var data = <String>[];
    int counter = 5;

    @override
    void initState() {
        super.initState();
        for (int i = 0; i < counter; i++) {
            data.add("${i + 1}");
        }
    }

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: const Text("AnimatedList"),
                actions: [
                    IconButton(
                        icon: const Icon(Icons.add),
                        onPressed: () {
                            addItem();
                        },
                    ),
                ],
            ),
            body: AnimatedList(
                key: _listKey,
                initialItemCount: data.length,
                itemBuilder: (context, index, animation) {
                    return buildItem(context, index);
                },
            ),
        );
    }

    Widget buildItem(BuildContext context, int index) {
        String c = data[index];
        return ListTile(
            key: ValueKey(c),
            title: Text(c),
            trailing: IconButton(
                icon: const Icon(Icons.delete),
                onPressed: () {
                    deleteItem(context, index);
                },
            ),
        );
    }

    void addItem() {
        data.add("${++counter}");
        _listKey.currentState!.insertItem(data.length - 1);
    }

    void deleteItem(BuildContext context, int index) {
        _listKey.currentState!.removeItem(
            index,
            (BuildContext context, Animation<double> animation) {
                var item = buildItem(context, index);
                data.removeAt(index);
                return item;
            },
        );
    }
}

GridView

二维网格列表。

SliverGridDelegateWithFixedCrossAxisCount

横轴为固定数量的子元素布局。

crossAxisCount:横轴的子元素的数量。

mainAxisSpacing:主轴方向的间距。

crossAxisSpacing:横轴方向的间距。

childAspectRatio:子元素的宽高比。

在这里插入图片描述

GridView(
    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        childAspectRatio: 1,
    ),
    children: const [
        Icon(Icons.home),
        Icon(Icons.add),
        Icon(Icons.delete),
        Icon(Icons.update),
        Icon(Icons.close),
        Icon(Icons.lock),
        Icon(Icons.home),
        Icon(Icons.add),
        Icon(Icons.delete),
        Icon(Icons.update),
        Icon(Icons.close),
        Icon(Icons.lock),
        Icon(Icons.home),
        Icon(Icons.add),
        Icon(Icons.delete),
        Icon(Icons.update),
        Icon(Icons.close),
        Icon(Icons.lock),
    ],
)

GridView.count

用于快速创建GridView,内部仍然使用SliverGridDelegateWithFixedCrossAxisCount等价于上面的代码。

GridView.count(
    crossAxisCount: 3,
    childAspectRatio: 1,
    children: [
        Icon(Icons.home),
        Icon(Icons.add),
        Icon(Icons.delete),
        Icon(Icons.update),
        Icon(Icons.close),
        Icon(Icons.lock),
        Icon(Icons.home),
    ],
)

SliverGridDelegateWithMaxCrossAxisExtent

横轴的子元素最大长度的布局。

maxCrossAxisExtent:横轴子元素最大长度。

GridView(
    gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 120,
        childAspectRatio: 1,
    ),
    children: const [
        Icon(Icons.home),
        Icon(Icons.add),
        Icon(Icons.delete),
        Icon(Icons.update),
        Icon(Icons.close),
        Icon(Icons.lock),
        Icon(Icons.home),
        Icon(Icons.add),
        Icon(Icons.delete),
        Icon(Icons.update),
        Icon(Icons.close),
        Icon(Icons.lock),
        Icon(Icons.home),
        Icon(Icons.add),
        Icon(Icons.delete),
        Icon(Icons.update),
        Icon(Icons.close),
        Icon(Icons.lock),
    ],
)

GridView.extent

快速创建GridView,本质是调用SliverGridDelegateWithMaxCrossAxisExtent。

GridView.extent(
  maxCrossAxisExtent: 120,
  children: const [
    Icon(Icons.delete),
    Icon(Icons.update),
    Icon(Icons.close),
    Icon(Icons.lock),
    Icon(Icons.home),
    Icon(Icons.add),
  ],
)

GridView.builder

当子元素数量较多时,推荐使用GridView.builder方式创建。

class MyGridView extends StatefulWidget {
    @override
    State<StatefulWidget> createState() {
        return _MyGridViewState();
    }
}

class _MyGridViewState extends State<MyGridView> {
    List<Icon> icons = [];

    @override
    void initState() {
        super.initState();
        getData();
    }

    @override
    Widget build(BuildContext context) {
        return GridView.builder(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                childAspectRatio: 1,
            ),
            itemCount: icons.length,
            itemBuilder: (BuildContext context, int index) {
                if (index == icons.length - 1 && icons.length < 200) {
                    getData();
                }
                return icons[index];
            },
        );
    }

    void getData() {
        Future.delayed(Duration(seconds: 2)).then((value) {
            setState(() {
                icons.addAll([
                    Icon(Icons.delete),
                    Icon(Icons.update),
                    Icon(Icons.close),
                    Icon(Icons.lock),
                    Icon(Icons.home),
                    Icon(Icons.add),
                ]);
            });
        });
    }
}

PageView

类似于Android中的ViewPager控件。PageView组件可以实现页面切换和Tab布局。

pageSnapping:每次滑动时,是否强制切换页面。

在这里插入图片描述

class PageViewPage extends StatefulWidget {
    @override
    State<StatefulWidget> createState() {
        return _PageViewPageState();
    }
}

class _PageViewPageState extends State<PageViewPage> {
    @override
    Widget build(BuildContext context) {
        List<Page> list = [];
        for (int i = 0; i < 5; i++) {
            list.add(Page(title: "$i"));
        }

        return Scaffold(
            appBar: AppBar(
                title: Text("PageView"),
            ),
            body: PageView(
                children: list,
            ),
        );
    }
}
class Page extends StatelessWidget {
    final String title;

    const Page({Key? key, required this.title}) : super(key: key);

    @override
    Widget build(BuildContext context) {
        return Center(
            child: Text(
                "$title",
                textScaleFactor: 2,
            ),
        );
    }
}

页面缓存

allowImplicitScrolling:设置为true,会预渲染下一个页面。

TabBarView

TabBarView是Material组件库提供的Tab布局组件,通过配合TabBar使用。

在这里插入图片描述

class TabBarViewPage extends StatefulWidget {
    @override
    State<StatefulWidget> createState() {
        return _TabBarViewPageState();
    }
}

class _TabBarViewPageState extends State<TabBarViewPage>
    with SingleTickerProviderStateMixin {
    final List tabs = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"];
    late TabController _controller;

    @override
    void initState() {
        super.initState();
        _controller = TabController(length: tabs.length, vsync: this);
    }

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

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text("TabBarView"),
                bottom: TabBar(
                    controller: _controller,
                    isScrollable: true,
                    tabs: tabs.map((e) {
                        return Tab(text: e);
                    }).toList(),
                ),
            ),
            body: TabBarView(
                controller: _controller,
                children: tabs.map((e) {
                    return Container(
                        alignment: Alignment.center,
                        child: Text(
                            e,
                            textScaleFactor: 3,
                        ),
                    );
                }).toList(),
            ),
        );
    }
}

CustomScrollView

CustomScrollView可以组合多个滚动组件,使他们滑动效果能统一起来。

在这里插入图片描述

var listView = SliverFixedExtentList(
    itemExtent: 50,
    delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
            return ListTile(title: Text("$index"));
        },
        childCount: 10,
    ),
);

CustomScrollView(
    slivers: [
        listView,
        listView,
    ],
);

在这里插入图片描述

//AppBar
final appBar = SliverAppBar(
    pinned: true, //滑动是标题是否固定
    expandedHeight: 250, //展开高度
    flexibleSpace: FlexibleSpaceBar(
        title: const Text("hello world"),
        background: Image.asset(
            "images/logo.png",
            fit: BoxFit.cover,
        ),
    ),
);

//GridView
final gridView = SliverGrid(
    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
        childAspectRatio: 1,
    ),
    delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
            return Container(
                alignment: Alignment.center,
                color: Colors.grey,
                child: Text("GridView $index"),
            );
        },
        childCount: 20,
    ),
);

//Padding
final padding = SliverPadding(
    padding: EdgeInsets.all(8),
    sliver: gridView,
);

//ListView
final listView = SliverFixedExtentList(
    delegate: SliverChildBuilderDelegate((context, index) {
        return Container(
            alignment: Alignment.center,
            color: Colors.lightBlue,
            child: Text("ListView $index"),
        );
    }),
    itemExtent: 50,
);

CustomScrollView(
    slivers: [
        appBar,
        padding,
        listView,
    ],
)

NestedScrollView

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>