Flutter——Widget系列3、Material widgets之Card和ListTile、列表ListView、GridView和相对布局Stack

那个,Card常常做圆角背景,然后,Card和ListTile,是经常一起玩,ListTile可以试下各种丰富的item效果,因此ListTile经常和列表一起玩。
最后,说说九宫格和相对布局。

文中参阅了很多文章,感谢各位大佬。

不说了,开始吧。先来个Card

image.png

一、Card和ListTile

安卓里面,有CardView。Flutter为什么有Card,不言而喻了。

一.1、Card

Card的构造函数

 * 卡片布局,相当于Android中的CardView
 * const Card({
    Key key,
    this.color,//背景色
    this.elevation,//阴影大小
    this.shape,//设置边,可以设置圆角
    this.margin = const EdgeInsets.all(4.0),
    this.clipBehavior = Clip.none,
    this.child,
    this.semanticContainer = true,
    })
  • key 相当于id
  • color 颜色
  • elevation 阴影大小
  • shape 设置边,可以设置圆角
  • margin 外边距
  • clipBehavior 对Widget截取的行为,比如 Clip.antiAlias 指抗锯齿
  • semanticContainer 语义容器? 默认为true

例子

一个简单的例子,演示了边框,阴影的。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new CardSimple1()
    );
  }
}

class CardSimple1 extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Container(
        width: 200,
        height: 200,
        child: Card(
          color: Colors.red,
          // 普通的边
          shape: Border.all(
              color: Colors.yellow,
              width: 5.0
          ),
          elevation: 20,// 阴影大小
          child: new Text("Card Widget"),
        ),
      )
    );
  }
}

image.png

看呢,是看到了,但是不是我们熟悉的原角CardView。

来个圆角的

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new CardSimple1()
    );
  }
}

class CardSimple1 extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Container(
        width: 200,
        height: 200,
        child: Card(
          color: Colors.red,

          //设置圆角
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),

          // 普通的边
/*          shape: Border.all(
              color: Colors.yellow,
              width: 5.0
          ),*/
          elevation: 20,// 阴影大小
          child: new Text("Card Widget"),
        ),
      )
    );
  }
}

显而易见,我们通过RoundedRectangleBorder实现Card的圆角。

而且,Card里面的元素,居然显示在Card之外,这目前不知道怎么解决。

image.png

加上个抗锯齿吧 clipBehavior

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new CardSimple1());
  }
}

class CardSimple1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Card(
          //设置圆角
//          shape:
//              RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
          color: Colors.purple,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0)),),
          // 普通的边
/*          shape: Border.all(
              color: Colors.yellow,
              width: 5.0
          ),*/
          // 抗锯齿
          clipBehavior: Clip.antiAlias,

          elevation: 20, // 阴影大小
          child: new Container(
            width: 200,
            height: 200,
            alignment: Alignment.center,
            child: new Text("Card Widget",style: TextStyle(color: Colors.white),),
          )),
    );
  }
}
image.png

指定角,实现圆角

shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.only(
                topLeft: Radius.circular(50.0),
                topRight: Radius.circular(50.0),
                bottomLeft: Radius.zero,
                bottomRight: Radius.zero),
          ),
image.png

一.2、ListTile

Card和ListTile,是经常一起玩。

ListTile的构造函数

  const ListTile({
    Key key,
    this.leading,
    this.title,
    this.subtitle,
    this.trailing,
    this.isThreeLine = false,
    this.dense,
    this.contentPadding,
    this.enabled = true,
    this.onTap,
    this.onLongPress,
    this.selected = false,
  }) 
  • key 相当于殴打
  • leading 将图像或图标添加到列表的开头。这通常是一个图标。
  • title 标题
  • subtitle 子标题
  • trailing 设置拖尾将在列表的末尾放置一个图像。这对于指示主-细节布局特别有用。(trailing本身是拖尾的意思)
  • isThreeLine = false 默认为false 3行,当列表标题、副标题,有需要更多的空间来容纳长度超过一行的文本,可开启
  • dense 让文本变小 (dense本身是稠密的意思)
  • contentPadding 内容的padding
  • enabled = true 可否点击。可通过将 enable 设置为 false,来禁止点击事件
  • onTap 点击
  • onLongPress 长按
  • selected = false 是否选中,默认为否 ,如果选中列表的 item 项,那么文本和图标的颜色将成为主题的主颜色。

.
.
.

基本属性先用起来

这4个,先一起来,leading、title、subtitle、trailing

leading
将图像或图标添加到列表的开头。这通常是一个图标。
title
标题
subtitle
子标题
trailing
设置拖尾将在列表的末尾放置一个图像。这对于指示主-细节布局特别有用。(trailing本身是拖尾的意思)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new ListTileSimle());
  }
}


class ListTileSimle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Card(

          color: Colors.white,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0)),),
          // 抗锯齿
          clipBehavior: Clip.antiAlias,
          elevation: 20,
          // 阴影大小
          child: new Container(
            height: 100,
            alignment: Alignment.center,
            
            // 演示 ListTile
            child: new ListTile(
              title: new Text("海贼王"),
              subtitle: new Text("来自东海的路飞"),

              // item左侧的图像
              leading: new Icon(
                Icons.ac_unit,
                color: Colors.blue[500],
              ),

              // 列表尾部的图标
              trailing: new Icon(Icons.chevron_right),

            ),
          )),
    );
  }
}
image.png
其他属性的使用
  • isThreeLine = false 默认为false 3行,当列表标题、副标题,有需要更多的空间来容纳长度超过一行的文本,可开启
  • dense 让文本变小 (dense本身是稠密的意思)
  • contentPadding 内容的padding
  • enabled = true 可否点击。可通过将 enable 设置为 false,来禁止点击事件
  • onTap 点击
  • onLongPress 长按
  • selected = false 是否选中,默认为否 ,如果选中列表的 item 项,那么文本和图标的颜色将成为主题的主颜色。
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new ListTileSimle());
  }
}

class ListTileSimle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Card(
          color: Colors.white,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(20.0)),
          ),
          // 抗锯齿
          clipBehavior: Clip.antiAlias,
          elevation: 20,
          // 阴影大小
          child: new Container(
            height: 100,
            alignment: Alignment.center,

            // 演示 ListTile
            child: new ListTile(
              title: new Text("海贼王"),
              subtitle: new Text("来自东海的路飞"),

              // item左侧的图像
              leading: new Icon(
                Icons.ac_unit,
                color: Colors.blue[500],
              ),

              // 列表尾部的图标
              trailing: new Icon(Icons.chevron_right),

              isThreeLine:true,

              dense: true, // 让文本变小
              contentPadding:EdgeInsets.symmetric(horizontal: 20.0),

              selected: true,  // 如果选中列表的 item 项,那么文本和图标的颜色将成为主题的主颜色。

              onTap: () { // 点击会有水波纹效果
                // do something
              },
              onLongPress: (){
                // do something else
              },

            ),
          )),
    );
  }
}
image.png

.
.
.

二 、列表 ListView

ListView,做列表呀。

ListView的属性

  ListView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  }) 
常见属性
  • scrollDirection
    Axis 设置滚动的方向,horizontal(水平)或vertical(垂直)
  • reverse
    bool 是否翻转
  • itemExtent
    double 滚动方向子控件的长度,垂直方向即为高度,水平方向即为宽度
  • controller
    ScrollController 用来控制滚动位置及监听滚动事件
  • shrinkWrap
    bool 是否根据子widget的总长度来设置ListView的长度
  • padding
    EdgeInsetsGeometry 间距
  • children
    List 子控件
  • primary
    bool 当内容不足以滚动时,是否支持滚动;对于iOS系统还有一个效果:当用户点击状态栏时是否滑动到顶部。

  • physics
    ScrollPhysics:控制用户滚动视图的交互

    • AlwaysScrollableScrollPhysics:列表总是可滚动的。在iOS上会有回弹效果,在android上不会回弹。那么问题来了,如果primary设置为false(内容不足时不滚动),且 physics设置为AlwaysScrollableScrollPhysics,列表是否可以滑动?答案是可以,感兴趣的可以试一下
    • PageScrollPhysics:一般是给PageView控件用的滑动效果。如果listview设置的话在滑动到末尾时会有个比较大的弹起和回弹
    • ClampingScrollPhysics:滚动时没有回弹效果,同android系统的listview效果
    • NeverScrollableScrollPhysics:就算内容超过列表范围也不会滑动
    • BouncingScrollPhysics:不论什么平台都会有回弹效果
    • FixedExtentScrollPhysics:不适用于ListView,原因:需要指定scroller为 - FixedExtentScrollController,这个scroller只能用于ListWheelScrollViews
  • shrinkWrap: scroll view在滑动方向上的高度是否由内容高度决定,false:则高度为滑动方向上的最大允许高度;如果在滑动方向上没有设置约束,则这个字段必须设置为true,否则会报错。

  • cacheExtent:可见区域的前后会有一定高度的空间去缓存子控件,当滑动时就可以迅速呈现

  • semanticChildCount:有含义的子控件的数量,如ListView会用children的长度,ListView.separated会用children长度的一半

用于构造SliverChildListDelegate的属性
  • addAutomaticKeepAlives:是否将子控件包裹在AutomaticKeepAlive控件内
  • addRepaintBoundaries:true:是否将子控件包裹在 RepaintBoundary 控件内。用于避免列表滚动时的重绘,如果子控件重绘开销很小时,比如子控件就是个色块或简短的文字,把这个字段设置为false性能会更好
  • addSemanticIndexes:是否把子控件包装在IndexedSemantics里,用来提供无障碍语义

来个简单例子吧

Flutter ListView 用法详解这个文章,根据构造方法不同,列举了几个场景,看起来,比较恰当,本文也是这么来。

方式1 默认构造函数(传入 List children) 少数子View

适用场景:已知有限个Item的情况下

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
          child: new ListViewSimle1(),
        ),
      ),
    );

  }
}

class ListViewSimle1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new ListView(
      //控制方向 默认是垂直的
      scrollDirection: Axis.vertical,
      children: <Widget>[
        TileSimle(),
        TileSimle(),
        TileSimle(),
      ],
    );
  }
}

class TileSimle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Card(
          color: Colors.white,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(20.0)),
          ),
          // 抗锯齿
          clipBehavior: Clip.antiAlias,
          elevation: 20,
          // 阴影大小
          child: new Container(
            height: 100,
            alignment: Alignment.center,

            // 演示 ListTile
            child: new ListTile(
              title: new Text("海贼王"),
              subtitle: new Text("来自东海的路飞"),

              // item左侧的图像
              leading: new Icon(
                Icons.ac_unit,
                color: Colors.blue[500],
              ),

              // 列表尾部的图标
              trailing: new Icon(Icons.chevron_right),
            ),
          )),
    );
  }
}
image.png

方式2 默认构造函数(传入 List children) 少数子View

适用场景:长列表时采用builder模式,能提高性能。不是把所有子控件都构造出来,而是在控件viewport加上头尾的cacheExtent这个范围内的子Item才会被构造。在构造时传递一个builder,按需加载是一个惯用模式,能提高加载性能。

就是类似安卓的ViewHolder。

简单例子1
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),
          child: new ListView.builder(
              itemBuilder: (context, index) => Text("Item $index"),
              itemCount: 100),
        ),
      ),
    );

  }
}
image.png
构造多种样式的Item
abstract class ListItem {}

class HeadingItem implements ListItem {
  final String heading;

  HeadingItem(this.heading);
}

class MessageItem implements ListItem {
  final String sender;
  final String body;

  MessageItem(this.sender, this.body);
}

ListView.builder(
            itemBuilder: (context, index) {
              final item = items[index];

              if (item is HeadingItem) {
                return ListTile(
                  title: Text(
                    item.heading,
                    style: Theme.of(context).textTheme.headline,
                  ),
                );
              } else if (item is MessageItem) {
                return ListTile(
                  title: Text(item.sender),
                  subtitle: Text(item.body),
                );
              }
            },
            itemCount: items.length))

方式3 separated 带分割线 dart

适用场景:列表中需要分割线时,可以自定义复杂的分割线

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),
          child: ListView.separated(
              itemBuilder: (context, index) {
                return Text("Item $index");
              },
              separatorBuilder: (context, index) {
                return Container(
                  color: Colors.grey,
                  height: 3,
                );
              },
              itemCount: 100)

        ),
      ),
    );

  }
}
image.png

方式4 custom 自定义

适用场景:上面几种模式基本可以满足业务需求,如果你还想做一些其它设置(如列表的最大滚动范围)或获取滑动时每次布局的子Item范围,可以尝试custom模式

  • 看看构造函数
  const ListView.custom({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    @required this.childrenDelegate,
    double cacheExtent,
    int semanticChildCount,
  }) 

发现有一个是必须复写的 —— childrenDelegate
.
.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),
          child: ListView.custom(childrenDelegate: CustomSliverChildDelegate())

        ),
      ),
    );

  }
}

class CustomSliverChildDelegate extends SliverChildDelegate {
  /// 根据index构造child
  @override
  Widget build(BuildContext context, int index) {
    // KeepAlive将把所有子控件加入到cache,已输入的TextField文字不会因滚动消失
    // 仅用于演示
    return KeepAlive(
        keepAlive: true,
        child: TextField(decoration: InputDecoration(hintText: '请输入')));
  }

  /// 决定提供新的childDelegate时是否需要重新build。在调用此方法前会做类型检查,不同类型时才会调用此方法,所以一般返回true。
  @override
  bool shouldRebuild(SliverChildDelegate oldDelegate) {
    return true;
  }

  /// 提高children的count,当无法精确知道时返回null。
  /// 当 build 返回 null时,它也将需要返回一个非null值
  @override
  int get estimatedChildCount => 100;

  /// 预计最大可滑动高度,如果设置的过小会导致部分child不可见,设置报错
  @override
  double estimateMaxScrollOffset(int firstIndex, int lastIndex,
      double leadingScrollOffset, double trailingScrollOffset) {
    return 2500;
  }

  /// 完成layout后的回调,可以通过该方法获已完成布局的视图树包括哪些子控件
  @override
  void didFinishLayout(int firstIndex, int lastIndex) {
    print('didFinishLayout firstIndex=$firstIndex firstIndex=$lastIndex');
  }
}
image.png

参考:https://juejin.im/post/5cb1c9d5f265da037371777f

三 、列表 GridView

ListView都说完了,类似九宫格的GridView还会远吗

GridView的构造函数

GridView({
    Key key,
    Axis scrollDirection = Axis.vertical, 
    bool reverse = false,  
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false, 
    EdgeInsetsGeometry padding,  
    @required this.gridDelegate, 
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
  })

常见属性

  • scrollDirection Axis 设置滚动的方向,horizontal(水平)或vertical(垂直)
  • reverse bool 是否翻转
  • controller ScrollController 用来控制滚动位置及监听滚动事件
  • shrinkWrap bool 是否根据子widget的总长度来设置GridView的长度
  • padding EdgeInsetsGeometry 间距
  • gridDelegate SliverGridDelegate 控制子Widget如何进行布局
  • children List 子控件

其实其他属性,已经没什么特别,需要重点看看的,也就是gridDelegate。
待会说。

GridView有好几种使用方式。

GridView的几种使用方式

方式1、GridView.count

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),
          child: GridView.count(
            // 方向,这行没设置页可以,默认就是垂直的
            scrollDirection:Axis.vertical,
            //水平子Widget之间间距
            crossAxisSpacing: 10.0,
            //垂直子Widget之间间距
            mainAxisSpacing: 30.0,
            //GridView内边距
            padding: EdgeInsets.all(10.0),
            //一行的Widget数量
            crossAxisCount: 2,
            //子Widget宽高比例
            childAspectRatio: 2.0,  // 比如2.0,就是 宽/高=2
            //子Widget列表
            children: getWidgetList(),
          ),
        ),
      ),
    );

  }
}


List<String> getDataList() {
  List<String> list = [];
  for (int i = 0; i < 100; i++) {
    list.add(i.toString());
  }
  return list;
}

List<Widget> getWidgetList() {
  return getDataList().map((item) => getItemContainer(item)).toList();
}

Widget getItemContainer(String item) {
  return Container(
    alignment: Alignment.center,
    child: Text(
      item,
      style: TextStyle(color: Colors.white, fontSize: 20),
    ),
    color: Colors.blue,
  );
}

如果设定了宽高比childAspectRatio,那么手动设定的宽高会失效

image.png

SliverGridDelegate的设置,直接在这种模式使用,但是我们结合build模式说,感觉合适一些。

方式2、GridView.build

这个,可以好好说下。

SliverGridDelegate,可以控制GridView的布局。

其中,有两个实现类

  • SliverGridDelegateWithMaxCrossAxisExtent (创建一个具有交叉轴最大值的一个网格布局)
  • SliverGridDelegateWithFixedCrossAxisCount(设置交叉轴上子控件的个数)

分开说

实现类1 SliverGridDelegateWithMaxCrossAxisExtent

MaxCrossAxis
创建一个具有交叉轴最大值的一个网格布局,元素的行/列数不是一定的,动态设定的

  • 对于SliverGridDelegateWithMaxCrossAxisExtent而言,水平方向元素个数不再固定
  • 其水平个数也就是有几列,由 maxCrossAxisExtent 和屏幕的宽度以及padding和mainAxisSpacing等决定。

构造方法

  const SliverGridDelegateWithMaxCrossAxisExtent({
    @required this.maxCrossAxisExtent, //子控件的最大宽度,实际宽度是根据交叉轴的值进行平分,也就是说最大宽度并不一定是实际宽度,很有可能子控件的实际宽度要小于设置的最大宽度
    this.mainAxisSpacing = 0.0, //主轴之间的间距
    this.crossAxisSpacing = 0.0,//交叉轴之间的间距
    this.childAspectRatio = 1.0,//子控件的宽高比
  }

记住,最大特点是元素的行/列数不是一定的,动态设定的

.
.

  • 当 maxCrossAxisExtent 为 50
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  List<String> datas = getDataList();
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),

          child: GridView.builder(
            // 方向,这行没设置页可以,默认就是垂直的
            scrollDirection:Axis.vertical,

            itemBuilder: (BuildContext context,int index){
              return getItemContainer(datas[index]);
            },

              // MaxCrossAxis 其水平个数也就是有几列,由 maxCrossAxisExtent 和屏幕的宽度以及padding和mainAxisSpacing等决定。
              gridDelegate:SliverGridDelegateWithMaxCrossAxisExtent(
                //单个子Widget的水平最大宽度
                  maxCrossAxisExtent: 50,
                  //水平单个子Widget之间间距
                  mainAxisSpacing: 20.0,
                  //垂直单个子Widget之间间距
                  crossAxisSpacing: 10.0
              ),
          ),
        ),
      ),
    );

  }
}


List<String> getDataList() {
  List<String> list = [];
  for (int i = 0; i < 100; i++) {
    list.add(i.toString());
  }
  return list;
}

List<Widget> getWidgetList() {
  return getDataList().map((item) => getItemContainer(item)).toList();
}

Widget getItemContainer(String item) {
  return Container(
    alignment: Alignment.center,
    child: Text(
      item,
      style: TextStyle(color: Colors.white, fontSize: 20),
    ),
    color: Colors.blue,
  );
}
image.png

.
.

  • 当 maxCrossAxisExtent 为 100


    image.png

如果强制设置行/列数就不开心,怎么办?比如指定为4行,可以试试:
maxCrossAxisExtent: MediaQuery.of(context).size.width/4
通过MediaQuery.of(context).size.width就可得到屏幕的宽度,除以4,就是子控件的最大值,这样一来我们就可以确定要显示的子控件的个数了

其实使用了 MaxCrossAxis 还制定数量,感觉就是不合适的,有病的。
想指定数量,那么应该用 SliverGridDelegateWithFixedCrossAxisCount,人家说了FixedCrossAxisCount.

实现类2 SliverGridDelegateWithMaxCrossAxisExtent

核心就是:crossAxisCount 指定数量

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  List<String> datas = getDataList();
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),

          child: GridView.builder(
            // 方向,这行没设置页可以,默认就是垂直的
            scrollDirection:Axis.vertical,

            itemBuilder: (BuildContext context,int index){
              return getItemContainer(datas[index]);
            },

              // FixedCrossAxisCount 指定 行/列的数量,就垂直方向来说,指定行数
              gridDelegate:SliverGridDelegateWithFixedCrossAxisCount(

                  //横轴元素个数: 3
                  crossAxisCount: 3,

                  //水平单个子Widget之间间距
                  mainAxisSpacing: 20.0,
                  //垂直单个子Widget之间间距
                  crossAxisSpacing: 10.0
              ),
          ),
        ),
      ),
    );

  }
}


List<String> getDataList() {
  List<String> list = [];
  for (int i = 0; i < 100; i++) {
    list.add(i.toString());
  }
  return list;
}

List<Widget> getWidgetList() {
  return getDataList().map((item) => getItemContainer(item)).toList();
}

Widget getItemContainer(String item) {
  return Container(
    alignment: Alignment.center,
    child: Text(
      item,
      style: TextStyle(color: Colors.white, fontSize: 20),
    ),
    color: Colors.blue,
  );
}

image.png

.
.

方式3、GridView.custom

这个也没什么特别的,之前ListView也有说过了,类似

  • 构造函数
  const GridView.custom({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required this.gridDelegate,
    @required this.childrenDelegate,
    double cacheExtent,
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  }) 

看看构造函数,有两个必须复写的,简单示例下


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  List<String> datas = getDataList();
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),

          child: GridView.custom(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3, mainAxisSpacing: 10.0, crossAxisSpacing: 20.0, ),
              childrenDelegate: SliverChildBuilderDelegate((context, position) {
                return getItemContainer(datas[position]);
              }, childCount: datas.length))
        ),
      ),
    );

  }
}


List<String> getDataList() {
  List<String> list = [];
  for (int i = 0; i < 100; i++) {
    list.add(i.toString());
  }
  return list;
}

List<Widget> getWidgetList() {
  return getDataList().map((item) => getItemContainer(item)).toList();
}

Widget getItemContainer(String item) {
  return Container(
    alignment: Alignment.center,
    child: Text(
      item,
      style: TextStyle(color: Colors.white, fontSize: 20),
    ),
    color: Colors.blue,
  );
}

image.png

参考:
https://blog.csdn.net/yuzhiqiang_1993/article/details/87968234
Flutter GridView

四、相对布局Stack

这货。堆叠的意思,跟安卓比,类似于相对布局,谁出现的晚,谁就可以出现在上方,覆盖别人。

看看构造函数

  Stack({
    Key key,
    this.alignment = AlignmentDirectional.topStart,
    this.textDirection,
    this.fit = StackFit.loose,
    this.overflow = Overflow.clip,
    List<Widget> children = const <Widget>[],
  })
  • alignment:对齐方式,默认是左上角(topStart)。
  • textDirection:文本的方向,绝大部分不需要处理。
  • fit:定义如何设置non-positioned节点尺寸,默认为loose。
    其中StackFit有如下几种:
    • loose:子节点宽松的取值,可以从min到max的尺寸;
    • expand:子节点尽可能的占用空间,取max尺寸;
    • passthrough:不改变子节点的约束条件。
  • overflow:超过的部分是否裁剪掉(clipped)。

布局行为

Stack的布局行为,根据child是positioned还是non-positioned来区分。

  • 对于positioned的子节点,它们的位置会根据所设置的top、bottom、right以及left属性来确定,这几个值都是相对于Stack的左上角;
  • 对于non-positioned的子节点,它们会根据Stack的aligment(左上角)来设置位置。

对于绘制child的顺序,则是第一个child被绘制在最底端,后面的依次在前一个child的上面。调整先后出现,可以改变展示顺序。

例子

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  List<String> datas = getDataList();
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      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> {
  @override
  Widget build(BuildContext context) {
    //关键代码
    var stack = new Stack(
      alignment: const Alignment(0.0, 0.6),  //分析 2
      children: [
        new CircleAvatar(   //分析 3
          backgroundImage: new AssetImage('images/lake.jpg'),
          radius: 100.0,
        ),
        new Container(   //分析 4
          decoration: new BoxDecoration(
            color: Colors.black45,
          ),
          child: new Text(
            '添加水印',
            style: new TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      ],
    );
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        elevation: 5.0,
      ),
      body: Center(  //分析 1
        child: stack,
      ),
    );
  }
}
image.png

关于Stack,还有一个IndexedStack,这里就不展开了。

参考:
Flutter 布局(八)- Stack、IndexedStack、GridView详解
https://www.jianshu.com/p/f1b8fbe5cda0

.
.
.
END

推荐阅读更多精彩内容