flutter布局详解及代码示例(下)

布局

基本布局

  • GridView(二维滚动列表):比ListView多了一个方向的数据填充。
  • ListBody(滚动列表):相比ListView,没有回收复用,简单易用。
  • Table(表格布局):子元素类似表格一样在X轴和Y轴排列分布。
  • Flow(流式布局):相比Wrap布局的子元素自动换行,Flow需要自行在Delegate里写算法实现换行。
  • Wrap(流式布局):子元素在X轴放得下就放,放不下就去下一行的布局。
  • ScrollView(滚动视图):一般搭配Column使用的类似滚动列表的布局。

GridView

  • 就是二维的ListView
  • 有五种构造方式
    • GridView()
      • 默认构造;
      • 硬编码子widget,数量多有性能风险
    • GridVIew.builder()
      • 懒加载
    • GridView.costom()
    • GirdView.count()
      • 在交叉轴方向上固定数目
    • GridView.extent()
      • 在交叉轴方向上固定数目且有长度限制

代码

/// GridView()
GridView(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,//交叉轴方向的数目
  ),
  children: [
    Text('1'),Text('2'),Text('3'),
    Text('4'),Text('5'),Text('6'),
    Text('7'),Text('8'),Text('9'),
    ],
),
/// GridView.builder()
final List name = ['1','2','3',];
GridView.builder(
  itemCount: name.length,
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,//交叉轴方向item的数量
    mainAxisSpacing: 2.0,//主轴方向的间隔
    crossAxisSpacing: 2.0,//交叉轴之间的间隔
  ),
  itemBuilder: (context, index) {
    return Container(
      child: Text(name[index]),
    );
  },
),
/// GridView.costom()
 GridView.custom(
    gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
      maxCrossAxisExtent: 80.0,//在交叉轴方向上单个item的最大长度
      crossAxisCount: 3,//交叉轴方向item的数量
	  mainAxisSpacing: 2.0,//主轴方向的间隔
    ),
    childrenDelegate: SliverChildBuilderDelegate(
      (context, index) {
        return Container(
          child: Text(name[index]),
        );
      },
      childCount: name.length,
    ),
),
/// GirdView.count()
GridView.count(
  crossAxisCount: 3,
  children: [
    Text('1'),Text('2'),Text('3'),
    Text('4'),Text('5'),Text('6'),
    Text('7'),Text('8'),Text('9'),
    ],
),
/// GridView.extent()
GridView.extent(
  maxCrossAxisExtent: 100.0,//在交叉轴方向上item的最大长度
  crossAxisCount: 3,//交叉轴方向item的数量
  mainAxisSpacing: 2.0,//主轴方向的间隔
  children: [
    Container(
          child: Text("1"),
        ),
    Container(
          child: Text("2"),
        ),
    Container(
          child: Text("3"),
        ),
  ],
),

完整示例代码

import 'package:flutter/material.dart';

class HomeTabPage1 extends StatelessWidget {
  List listData = [
    {
      "title": "标题1",
      "author": "内容1",
      "image": "https://www.itying.com/images/flutter/1.png"
    },
    {
      "title": "标题2",
      "author": "内容2",
      "image": "https://www.itying.com/images/flutter/2.png"
    },
    {
      "title": "标题3",
      "author": "内容3",
      "image": "https://www.itying.com/images/flutter/3.png"
    },
    {
      "title": "标题4",
      "author": "内容4",
      "image": "https://www.itying.com/images/flutter/4.png"
    },
    {
      "title": "标题5",
      "author": "内容5",
      "image": "https://www.itying.com/images/flutter/5.png"
    },
    {
      "title": "标题6",
      "author": "内容6",
      "image": "https://www.itying.com/images/flutter/6.png"
    },
    {
      "title": "标题7",
      "author": "内容7",
      "image": "https://www.itying.com/images/flutter/7.png"
    },
    {
      "title": "标题8",
      "author": "内容8",
      "image": "https://www.itying.com/images/flutter/1.png"
    },
    {
      "title": "标题9",
      "author": "内容9",
      "image": "https://www.itying.com/images/flutter/2.png"
    },
    {
      "title": "标题1",
      "author": "内容1",
      "image": "https://www.itying.com/images/flutter/1.png"
    },
    {
      "title": "标题2",
      "author": "内容2",
      "image": "https://www.itying.com/images/flutter/2.png"
    },
    {
      "title": "标题3",
      "author": "内容3",
      "image": "https://www.itying.com/images/flutter/3.png"
    },
    {
      "title": "标题4",
      "author": "内容4",
      "image": "https://www.itying.com/images/flutter/4.png"
    },
    {
      "title": "标题5",
      "author": "内容5",
      "image": "https://www.itying.com/images/flutter/5.png"
    },
    {
      "title": "标题6",
      "author": "内容6",
      "image": "https://www.itying.com/images/flutter/6.png"
    }
  ];

  List _getData() {
    List list = [];
    for (var i = 0; i < listData.length; i++) {
      list.add(Container(
        child: Column(
          children: [
            Image.network(
              listData[i]["image"],
              fit: BoxFit.cover,
            ),
            Text(
              listData[i]["title"],
              textAlign:TextAlign.center,
            )
          ],
        ),
      ));
    }
    return list;
  }


  @override
  Widget build(BuildContext context) {
    return GridView.count(
      //设置滚动方向
      scrollDirection: Axis.vertical,
      //设置列数
      crossAxisCount: 5,
      //设置内边距(整个GridView的)
      padding: EdgeInsets.all(30),
      //设置横向间距(3个间距一起用就能控制item各种距离了)
      crossAxisSpacing: 30,
      //设置主轴间距
      mainAxisSpacing: 30,
      children: _getData(),
    );
  }
}

常用参数

gridDelegate
  • 有两个实现类
    • SliverGridDelegateWithFixedCrossAxisCount
      • 根据设置的个数显示二维滚动列表
    • SliverGridDelegateWithMaxCrossAxisExtent
      • 根据设置的长度显示二维滚动列表
scrollDirection
* 滚动方向
    * Axis.vertical
        * 竖向滚动
    * Axis.horizontal
        * 横向滚动
reverse
* 组件反向排序
controller
* 滚动监听
primary
* 值为false,内容不足不可滑动
* 值为true,内容不足可以尝试滑动
shrinkWrap
* 内容适配,默认为false
padding
* 内边距
crossAxisCount
* 列数
mainAxisSpacing
* 主轴之间的间距
crossAxisSpacing
* 横轴之间的间距
childAspectRatio
* 设置宽高的比例
* GridView的子组件直接设置宽高没有反应,可以通过childAspectRatio修改宽高
cacheExtent
* 设置预加载区域
children
* 组件元素 const Widget列表
* 数组内添加widget类型的数据
* flutter的所有组件都是widget,也就是说所有的GridView可以添加所有的组件
semanticChildCount
* 提供语义信息的子组件数量

ListBody

  • 很少单独使用,搭配如Row、Column、ListView、Flex一起使用。

代码

Column(
  //主轴垂直排列的列表,未限制宽,默认将充满屏幕
  children: [
    ListBody(
      //指定主轴方向与父框架相同
      mainAxis: Axis.vertical,
      reverse: false,//不反向
      children: [
        Container(color: Colors.red, width: 50.0, height: 50.0),
        Container(color: Colors.yellow, width: 50.0, height: 50.0),
        Container(color: Colors.green, width: 50.0, height: 50.0),
        Container(color: Colors.blue, width: 50.0, height: 50.0),
        Container(color: Colors.black, width: 50.0, height: 50.0),
      ],
    )
  ],
)

Table

  • 像表格一样布局

代码

Container(
  width: 300.0,height: 200.0,
  padding: EdgeInsets.all(2.0),
  color: Color(0xFFC5CAE9),
  child: Table(
    //每行中单元格的宽度,TableRow内元素个数,即列数,从第一个到最后一个的宽度
    //如果排列根据排列方向显示不同
    columnWidths: const {
      0: FixedColumnWidth(30.0),
      1: FixedColumnWidth(70.0),
      2: FixedColumnWidth(50.0),
      3: FixedColumnWidth(100.0),
    },
    //默认未显示宽度  默认的每一列宽度值,默认情况下均分。
    defaultColumnWidth: const FlexColumnWidth(1.0),
    //每个表格的排列方向,此处设置从右到左
    textDirection: TextDirection.rtl,
    //表格边框,此处设置蓝色,2像素宽,实线
    border: TableBorder.all(color: Colors.blue, width: 2.0, style: BorderStyle.solid),
    //每一个单元格的垂直方向的对齐方式,默认为顶部对齐
    defaultVerticalAlignment: TableCellVerticalAlignment.top,
    //基线类型,与TableCellVerticalAlignment.baseline一起使用
    //textBaseline: null,
    children: [
      TableRow(
        decoration: BoxDecoration(color: Colors.purpleAccent),
        children: [
          Text('A1'),Text('A2'),Text('A3'),Text('A4'),
        ],
      ),
      TableRow(
        decoration: BoxDecoration(color: Colors.purpleAccent),
        children: [
          Container(color: Colors.red, child: Text('赤')),
          Container(color: Colors.orange, child: Text('橙')),
          Container(color: Colors.yellow, child: Text('黄')),
          Container(color: Colors.green, child: Text('绿')),
        ],
      ),
      TableRow(children: [
        Text('B1'),Text('B2'),Text('B3'),Text('B4'),
      ]),
    ],
  ),
)

参数

  • columnWidth:每列单元格的宽度,int为从0到每行的个数-1
  • defaultColumnWidth:默认的每一列宽度值,默认情况下均分。
  • textDirection:每列的排列方向,默认从左到右
  • border:TableBorder 表格的边框
  • defaultVerticalAlignment:单元格默认垂直方向上的对齐方式,默认上对齐TableCellVerticalAlignment.top
  • textBaseline:TableCellVerticalAlignment.baseline与此属性配合使用。文本基线类型
  • children:存放每行的单元格内容的类别

Flow

  • 重点在于FlowDelegate的使用

FlowDelegate的方法

abstract class FlowDelegate {
  const FlowDelegate({ Listenable repaint }) : _repaint = repaint;
  final Listenable _repaint;
  //重写设置尺寸
  Size getSize(BoxConstraints constraints) => constraints.biggest;
  //重写设置约束
  BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) => constraints;
  //绘制children的位置和大小
  void paintChildren(FlowPaintingContext context);
  //是否要从新布局,可自己定制规则
  bool shouldRelayout(covariant FlowDelegate oldDelegate) => false;
  //是否从新绘制,可自己定制规则
  bool shouldRepaint(covariant FlowDelegate oldDelegate);
  @override
  String toString() => '$runtimeType';
}

代码

Flow(
  delegate: TestFlowDelegate(margin: EdgeInsets.all(5.0)),
  children: [
    new Container(
      width: 60.0,  height: 60.0, color: Colors.red,
      child: Text('红'), alignment: Alignment.center, 
    ),
    new Container(
      width: 60.0, height: 60.0,color: Colors.orange,
      child: Text('橙'),alignment: Alignment.center,  
    ),
    new Container(
      width: 60.0,height: 60.0,color: Colors.yellow,
      child: Text('黄'),alignment: Alignment.center,      
    ),
    new Container(
      width: 60.0,height: 60.0,color: Colors.green,
      child: Text('绿'),alignment: Alignment.center,  
    ),
    new Container(
      width: 60.0,height: 60.0,color: Colors.cyan,
      child: Text('青'),alignment: Alignment.center,     
    ),
    new Container(
      width: 60.0, height: 60.0,color: Colors.blue,
      child: Text('蓝'),alignment: Alignment.center,  
    ),
    new Container(
      width: 60.0,height: 60.0,color: Colors.purple,
      child: Text('紫'),alignment: Alignment.center,   
    ),
  ],
)
    
class TestFlowDelegate extends FlowDelegate {
  EdgeInsets margin = EdgeInsets.zero;
  TestFlowDelegate({this.margin});

  @override
  void paintChildren(FlowPaintingContext context) {
    var x = margin.left;
    var y = margin.top;
    for (int i = 0; i < context.childCount; i++) {
      var w = context.getChildSize(i).width + x + margin.right;
      if (w < context.size.width) {
        context.paintChild(i,
            transform: new Matrix4.translationValues(x, y, 0.0));
        x = w + margin.left;
      } else {
        x = margin.left;
        y += context.getChildSize(i).height + margin.top + margin.bottom;
        context.paintChild(i,
            transform: new Matrix4.translationValues(x, y, 0.0));
        x += context.getChildSize(i).width + margin.left + margin.right;
      }
    }
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return oldDelegate != this;
  }
}

Wrap

  • 在mainAxis上空间不足时,则向crossAxis上去扩展显示。
  • Wrap使用方便一些,但是Flow能实现Wrap,Flow更强一些。

参数

  • direction :主轴方向,默认水平
  • alignment :主轴方向方式,值为WrapAlignment的枚举值,详情请看Row的对齐方式
  • spacing :主轴方向上child之间的间距,默认为0
  • runAlignment : 新一行或一列的对齐方式
  • runSpacing :新的一行或一列的间距,默认为0
  • crossAxisAlignment:交叉轴的对齐方式,默认是从主轴开始位置开始
  • textDirection:每一行或一列的排列方式
    • 如果一行有三个元素,则第一行取出前三个元素2,1,0这样排列
  • verticalDirection:垂直方向上排列方式,值为VerticalDirection的枚举值,默认从上到下

代码

Container(
    alignment: Alignment.topCenter,
    child:  Wrap(
    //主轴方向,默认水平
    direction: Axis.horizontal,
    //主轴方向方式,包裹在一个控件内效果明显,默认主轴方向开始位置开始
    alignment: WrapAlignment.spaceBetween,
    //主轴方向上child之间的间距,默认为0
    spacing: 6.0,
    // 新一行或一列的对齐方式
    runAlignment: WrapAlignment.spaceBetween,
    //新的一行或一列的间距,默认为0
    runSpacing: 0.0,
    //交叉轴的对齐方式,默认是从主轴开始位置开始
    crossAxisAlignment: WrapCrossAlignment.start,
    //每一行或一列的排列方式,如果一行有三个元素,则第一行取出前三个元素2,1,0这样排列
    //默认从左到右
    textDirection: TextDirection.ltr,
    //垂直方向上排列方式,默认从上到下
    verticalDirection: VerticalDirection.down,
    children: [
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('1')),
        label: Text('Hamilton'),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('2')),
        label: Text('Lafayette'),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('3')),
        label: Text('Mulligan'),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('4')),
        label: Text('Laurens'),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('5')),
        label: Text('Hamilton'),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('6')),
        label: Text('Lafayette'),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('7')),
        label: Text('Mulligan'),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('8')),
        label: Text('Laurens'),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('9')),
        label: Text('Hamilton'),
      ),
      Chip(
        avatar: CircleAvatar(
            backgroundColor: Colors.blue.shade900, child: Text('10')),
        label: Text('Lafayette'),
      ),
      Chip(
        avatar: CircleAvatar(
            backgroundColor: Colors.blue.shade900, child: Text('11')),
        label: Text('Mulligan'),
      ),
      Chip(
        avatar: CircleAvatar(
            backgroundColor: Colors.blue.shade900, child: Text('12')),
        label: Text('Laurens'),
      ),
    ],
  ),
)

ScrollView

  • 有很多子类实现:
    • SingleChildScrollView

SingleChildScrollView

  • 有子view数组的ScrollView
代码
SingleChildScrollView(
    child: Column(
    children: [
        Container(
        height: 200,
        color: Colors.red,
        ),
        Container(
        height: 200,
        color: Colors.green,
        ),
        Container(
        height: 200,
        color: Colors.blue,
        ),
        Container(
        height: 200,
        color: Colors.yellow,
        ),
        Container(
        height: 200,
        color: Colors.orange,
        ),
    ],
    ),
),

自定义ScrollPhysics控制滑动效果

  • 准确的说这个属于所有physics参数共用的一个案例
  • 继承ScrollPhysics可以实现自定义滑动的效果
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyCustomScrollPhysics extends ScrollPhysics {
  const MyCustomScrollPhysics({ScrollPhysics? parent}) : super(parent: parent);

  @override
  MyCustomScrollPhysics applyTo(ScrollPhysics? ancestor) {
    return MyCustomScrollPhysics(parent: buildParent(ancestor));
  }

  @override
  double applyBoundaryConditions(ScrollMetrics position, double value) {
    // 检查是否已经滑动到边界
    if (value < position.pixels && position.pixels <= position.minScrollExtent) {
      // 滑动到顶部边界时,允许继续向上滚动
      return 0.0;
    } else if (value > position.pixels && position.pixels >= position.maxScrollExtent) {
      // 滑动到底部边界时,允许继续向下滚动
      return 0.0;
    }
    // 其他情况,使用默认的边界条件
    return super.applyBoundaryConditions(position, value);
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: CustomScrollView(
          physics: MyCustomScrollPhysics(), // 使用自定义的ScrollPhysics
          slivers: [
            SliverAppBar(
              expandedHeight: 200.0,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                title: Text('Custom Scroll Physics Example'),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return ListTile(
                    title: Text('Item $index'),
                  );
                },
                childCount: 100, // 你的列表项数量
              ),
            ),
          ],
        ),
      ),
    );
  }
}

你可能感兴趣的:(flutter,flutter,布局)