【Flutter】 自学(六)-Flutter可滑动组件

1,644 阅读4分钟

可滚动组件

我们在开发过程中,如果组件内容超过当前显示视口时,不做特殊处理的话Flutter会提示Overflow错误,这个时候我们就需要使用滑动组件.

Scrollbar是一个Material风格的滚动指示器(滚动条),如果要给可滚动组件添加滚动条,只需将Scrollbar作为可滚动组件的任意一个父级组件即可,通过child为其添加子控件

SingleChildScrollView

SingleChildScrollView类似于Android中的ScrollView,它只能接收一个子组件。一般是某个控件非常大的时候我们需要用到这个,但是不能超过屏幕太多,主要属性有

  • scrollDirection:滚动方向,默认是垂直方向,Axis.vertical
  • reverse:是否反向
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Scrollbar(
          child: SingleChildScrollView(
            child: Container(
              height: 2000,
              alignment: Alignment.center,
              color: Colors.yellow,
              child: Text("Hello World"),
            ),
          ),
        ));
  }
}

我们改下方向

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Scrollbar(
          child: SingleChildScrollView(
            ///滚动方向
            scrollDirection: Axis.horizontal,
            child: Container(
              width: 2000,
              height: 200,
              alignment: Alignment.center,
              color: Colors.yellow,
              child: Text("Hello World"),
            ),
          ),
        ));
  }
}

ListView

基础使用

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: ListView.builder(
            //item个数
            itemCount: 50,
            //item的宽度或者高度,取决于滑动方向
            itemExtent: 50,
            //item的构建器
            itemBuilder: (BuildContext context, int index) {
              return ListTile(title: Text("$index"));
            }));
  }
}

添加固定的头

如果我们想在上卖弄加一个固定的头部怎么做呢?

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Column(
          children: [
            ListTile(
              title: Text("固定头部"),
            ),
            ListView.builder(
                //item个数
                itemCount: 50,
                //item的宽度或者高度,取决于滑动方向
                itemExtent: 50,
                //item的构建器
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(title: Text("$index"));
                })
          ],
        ));
  }
}

这样写之后我们发现运行会报错

Error caught by rendering library, thrown during performResize()。 Vertical viewport was given unbounded height ...

大概的意思就是需要我们指定个高度,但是我们想让整个列表撑满屏幕,怎么弄?一个就是动态算,去除状态栏、导航栏、ListTile高度,另一个就是使用Flex,推荐后面这种

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Column(
          children: [
            ListTile(
              title: Text("固定头部"),
            ),
            Expanded(
                child: ListView.builder(
                    //item个数
                    itemCount: 50,
                    //item的宽度或者高度,取决于滑动方向
                    itemExtent: 50,
                    //item的构建器
                    itemBuilder: (BuildContext context, int index) {
                      return ListTile(title: Text("$index"));
                    })),
          ],
        ));
  }
}

添加分割线

这里就需要使用ListView.separated

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    //下划线widget预定义以供复用。
    Widget divider1 = Divider(color: Colors.blue);
    Widget divider2 = Divider(color: Colors.red);

    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Column(
          children: [
            ListTile(
              title: Text("固定头部"),
            ),
            Expanded(
                child: ListView.separated(
              //item个数
              itemCount: 50,
              //item的构建器
              itemBuilder: (BuildContext context, int index) {
                return ListTile(title: Text("$index"));
              },
              //分割器构造器
              separatorBuilder: (BuildContext context, int index) {
                return index % 2 == 0 ? divider1 : divider2;
              },
            )),
          ],
        ));
  }
}

GridView

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: GridView(
        //控制子widget
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          //横轴子元素的数量
          crossAxisCount: 3,
          //子元素在横轴长度和主轴长度的比例
          childAspectRatio: 1,
          //横轴方向子元素的间距
          crossAxisSpacing: 20,
          //主轴方向的间距
          mainAxisSpacing: 20,
        ),
        children: [
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("1"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("2"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("3"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("4"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("5"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("6"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("7"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("8"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("9"),
          ),
        ],
      ),
    );
  }
}

上面的可用GridView.count代替

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: GridView.count(
        //横轴子元素的数量
        crossAxisCount: 3,
        //子元素在横轴长度和主轴长度的比例
        childAspectRatio: 1,
        //横轴方向子元素的间距
        crossAxisSpacing: 20,
        //主轴方向的间距
        mainAxisSpacing: 20,
        children: [
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("1"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("2"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("3"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("4"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("5"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("6"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("7"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("8"),
          ),
          Container(
            alignment: Alignment.center,
            color: Colors.red,
            child: Text("9"),
          ),
        ],
      ),
    );
  }
}

CustomScrollView

如果我们想一个页面存在多个可滑动的组件,并且滑动效果统一,就需要用过CustomScrollView实现

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: CustomScrollView(
        slivers: [
          //grid
          SliverGrid(
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              mainAxisSpacing: 10.0,
              crossAxisSpacing: 10.0,
              childAspectRatio: 4.0,
            ),
            delegate: new SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return new Container(
                  alignment: Alignment.center,
                  color: Colors.blue,
                  child: new Text('grid item $index'),
                );
              },
              childCount: 20,
            ),
          ),
          //List
          SliverFixedExtentList(
            itemExtent: 50.0,
            delegate: new SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                //创建列表项
                return new Container(
                  alignment: Alignment.center,
                  color: Colors.lightGreenAccent,
                  child: new Text('list item $index'),
                );
              },
              childCount: 50,
              
            ),
          ),
        ],
      ),
    );
  }
}

滑动监听

使用ScrollController

  • offset:可滚动组件当前的滚动位置。
  • jumpTo(double offset)animateTo(double offset,...):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。

class _MyHomePageState extends State<MyHomePage> {
  ScrollController _scrollController = ScrollController();

  //是否显示FloatButton
  bool showFloatButton = false;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      print(_scrollController.offset);
      //判断滑动距离
      if (_scrollController.offset < 200 && showFloatButton) {
        setState(() {
          showFloatButton = false;
        });
      } else if (_scrollController.offset >= 200 && showFloatButton == false) {
        setState(() {
          showFloatButton = true;
        });
      }
    });
  }

  @override
  void dispose() {
    //为了避免内存泄露,需要调用_controller.dispose
    _scrollController.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
          controller: _scrollController,
          //item个数
          itemCount: 50,
          //item的宽度或者高度,取决于滑动方向
          itemExtent: 50,
          //item的构建器
          itemBuilder: (BuildContext context, int index) {
            return ListTile(title: Text("$index"));
          }),
      floatingActionButton: !showFloatButton
          ? null
          : FloatingActionButton(
              child: Icon(Icons.arrow_upward),
              onPressed: () {
                //返回到顶部时执行动画
                _scrollController.animateTo(0.0,
                    duration: Duration(milliseconds: 200), curve: Curves.ease);
              },
            ),
    );
  }
}