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

布局

基本布局

  • Row(水平布局):在水平(X轴)方向上排列子widget的列表。
  • Column(垂直布局):在垂直(Y轴)方向上排列子widget的列表。
  • Stack(可重叠布局):可以允许其子widget简单的堆叠(Z轴)在一起。
  • IndexedStack(单一显示可重叠布局):相比Stack后面编码的widget显示在Z轴上方,IndexedStack通过index可以控制显示哪个widget显示在Z轴的上方,注意,只显示一个。
  • ListView(滚动列表):最常用的滚动widget。

Row

  • 不带滚动属性,如果超出约束范围,在debug版本下会提示溢出。

代码

Row(
   crossAxisAlignment: CrossAxisAlignment.start,
   children: <Widget>[
       new Text('1'),
       new Text('2'),
       new Text('3'),
   	],
)

常用参数

mainAxisAlignment
  • X轴方向上的对齐方式
  • spaceBetween:沿X轴方向均分,子widget之间的空白区域相等,两侧贴边,比如只有两个子widget就在两边,三个就左中右这样。
  • spaceAround:沿X轴方向均分,左右两侧不贴边,有空白区域,但空白区域是子widget之间的空白区域的1/2。
  • spaceEvenly:沿X轴方向均分,左右两侧不贴边,有空白区域,空白区域和子widget之间的空白区域大小一致。
crossAxisAlignment
  • Y轴方向上的对齐方式
  • stretch:让子widget充满Y轴。
  • baseline:在Y轴上和基线适配,搭配textBaseline用
textBaseline
  • 文本内容的基线类型,就是文本的水平边界线,注意,只有水平
  • alphabetic:以字母字符底部的水平线对齐(不论中英文,都是用文字的最底部对齐)
  • ideographic:以表意字符的水平线对齐。(英文如pgj这些底部会比中文矮一些)
mainAxisSize
  • X轴方向应占用多少空间,默认最大化的占用主轴空间。
  • 若为MainAxisSize.min,则mainAxisAlignment只能为start。
verticalDirection
  • 垂直方向上的排列顺序
  • up:子widget从bottom开始往top排列;
  • down:子widget从top开始往bottom排列。
  • 搭配crossAxisAlignment的start和end属性有如下不同:
textDirection
  • 与verticalDirection类似,水平方向上的排列顺序
  • ltr:left to right,从左到右排列;
  • rtl:rigth to left,从右到左排列;
  • 同verticalDirection类似,搭配mainAxisAlignment使用,如下:

Column

  • 不带滚动属性,如果超出约束范围,在debug版本下会提示溢出。

代码

Column(
   crossAxisAlignment: CrossAxisAlignment.start,
   children: <Widget>[
       new Text('1'),
       new Text('2'),
       new Text('3'),
   	],
)

常用参数

mainAxisAlignment
  • Y轴方向上的对齐方式
  • spaceBetween:沿Y轴方向均分,子widget之间的空白区域相等,比如只有两个子widget就在上下,三个就上中下这样。
  • spaceAround:沿Y轴方向均分,上下两侧不贴边,有空白区域,但空白区域是子widget之间的空白区域的1/2。
    • spaceEvenly:沿Y轴方向均分,上下两侧不贴边,有空白区域,空白区域和子widget之间的空白区域大小一致。
crossAxisAlignment
  • X轴方向上的对齐方式
  • stretch:让子widget充满X轴。
  • baseline:相当于start,因为textBaseline只有水平,因为没有文字是需要人歪头90度看的。
mainAxisSize
  • Y轴方向应占用多少空间,默认最大化的占用主轴空间。
  • 若为MainAxisSize.min,则mainAxisAlignment只能为start。
verticalDirection
  • 垂直方向上的排列顺序
  • up:子widget从bottom开始往top排列;
  • down:子widget从top开始往bottom排列。
  • 搭配mainAxisAlignment的start和end属性有如下不同:
textDirection
  • 与verticalDirection类似,水平方向上的排列顺序
  • ltr:left to right,从左到右排列;
  • rtl:rigth to left,从右到左排列;
  • 同verticalDirection类似,搭配crossAxisAlignment使用,如下:

Stack

  • 可以子widget无视位置压在另一个子widget的布局
  • 内部使用Positioned定位子widget位置宽高
    • width,left和right三个属性不能同时使用,必须至少有一个属性是null;
    • height,top和bottom三个属性不能同时使用,必须至少有一个属性是null。

代码

Stack(
  alignment: Alignment(-1.0,0.0),//等价于Alignment.centerLeft
  children: <Widget>[
    Positioned(child: Container(width: 100.0,height: 100.0,color: Colors.red,),),
    Positioned(child: Container(width: 90.0,height: 90.0,color: Colors.green,),),
    Positioned(child: Container(width: 80.0,height: 80.0,color: Colors.blue,),),
    new Text('-1,0\ncenterLeft',textAlign: TextAlign.left,),
  ],
)

完整示例

Container(
    width: 200,
    height: 200,
    child: Stack(
        children: <Widget>[
        Positioned(
            // 若是不指定位置就是(0,0)
            // 若是不指定size就是子view的size
            // top: 50.0,
            // left: 50.0,
            child: Container(
                width: 200,
                height: 200,
            ),
        ),
        Positioned(
            top: 0.0,
            left: 190.0,
            child: Container(
            width: 10.0,
            height: 10.0,
            color: Colors.green,
            ),
        ),
        ],
    ),
);

常用参数

alignment
  • 可以传递AlignmentDirectional、Alignment、FractionalOffset三大类,主要用的是带方向的AlignmentDirectional。
textDirection
  • 搭配AlignmentDirectional的start或者end实现类似Row的mainAxisAlignment+textDirection的效果。
fit
  • loose:从Stack传递过来的约束被解除,即:
    • 当Stack大小被限制为300*400,子widget元素的大小将可设置为,宽:0到300,高:0到400。
  • expand:从Stack传递过来的约束被锁定到最大,即:
    • 当Stack大小被限制为300*400,子widget元素的大小将锁定为,宽:300,高:400。
  • passthrough:不改变子组件约束条件。和loose一致。
overflow
  • 是否裁剪溢出的子widget元素
  • clip:裁剪溢出部分
  • visible:显示溢出部分

IndexedStack

  • 相比Stack多了一个index参数,控制显示哪个子widget
  • 内部使用Positioned定位子widget位置宽高
    • width,left和right三个属性不能同时使用,必须至少有一个属性是null;
    • height,top和bottom三个属性不能同时使用,必须至少有一个属性是null。

代码

IndexedStack(
  sizing: StackFit.expand,
  index: 1,
  children: <Widget>[
    Positioned(child: Container(width: 100.0,height: 100.0,color: Colors.red,),),
    Positioned(child: Container(width: 130.0,height: 90.0,color: Colors.green,),),
    Positioned(child: Container(width: 80.0,height: 120.0,color: Colors.blue,),),
   ],
),

index就是children的length的区间之中的值。

比如上面的代码,就只显示index为1的子widget,也就是第二个。

常用参数
  • 因为只显示一个,所以Stack的常用参数基本用不到

ListView

    • listview需要拓展长度,比如是纵向的listview,就需要指定父view的height。
  • 可以理解为可以滚动的Column。
  • 有四种构造方式
    • List():默认构造;
      • 直接把子widget硬编码,如果子widget较多,有性能风险。
    • ListView.builder();
      • 不带分割线
      • 懒加载,只会渲染可见的条目。
    • ListView.separated;
      • 带分割线
      • separatorBuilder是必传参数,用于构造分割线view
      • 分割线返回的是一个widget(不必一定是Divider),所以可以自定义
    • ListView.custom。
      • 搭配SliverChildBuilderDelegate自定义实现不同类型子widget,比如子widget和分割线交替出现等复杂场景。

代码

/// 默认构造
ListView(
  children: [ 
    new Text('1'),
    Divider(),
    new Text('2'),
    Divider(),
    new Text('3'),
    Divider(),
  ],
),
/// ListView.builder()构造
final List<String> name = <String>['1','2','3',];
ListView.builder(
  itemCount: name.length,
  itemBuilder: (context, index) {
    return Text(name[index]);
  },
),
/// ListView.separated()构造
ListView.separated(
  itemBuilder: (context, index) {
    return Text(name[index]);
  },
  separatorBuilder: (context, index) {
    return Divider();//分割线
  },
  itemCount: name.length,
),
/// ListView.custom()
ListView.custom(
  childrenDelegate: SliverChildBuilderDelegate(
    (context, index) {
      return Text(name[index]);
    },
    childCount: name.length,
  ),
),

详细代码

var lv1 = ListView(
    //主轴滚动方向:垂直vertical,竖直horizontal ,默认为垂直vertical,
    scrollDirection: Axis.vertical,
    //是否反向 默认为false,正常顺序从起始点开始正序,true为从末尾开始排列
    reverse: false,
    //滚动控制器,默认为null
    controller:null,
    //是否强制滚动(顶部或底部时是否可以滚动),默认是false,如果为true,Controller必须为null
    primary: false,
    //视图如何响应用户的手势滑动,有内置实现强制可以滚动 const AlwaysScrollableScrollPhysics();和强制不可以滚动const ScrollPhysics(),可忽略primary属性
    physics: const AlwaysScrollableScrollPhysics(),
    //默认为false,滚动视图在[滚动方向]中的范围是否应由正在查看的内容决定。
    shrinkWrap: true,
    //item的padding值
    padding: const EdgeInsets.all(10.0),
    //item交叉轴方向的大小,默认自适应,vertical时为高度,horizontal时为高度
    itemExtent: 50.0,
    //是否自动保存滑出屏幕外的字widget的状态,保证widget不被回收,可复用,false的手动保存。默认为true
    addAutomaticKeepAlives: true,
    //是否放置到重绘列表中,复杂widget可提高性能,默认为true
    addRepaintBoundaries: true,
    //缓存区大小,默认为250
    cacheExtent: 250.0,
    //默认构造函数中特有,直接将子widget放置内,一次性渲染完成,适合少量数据
    children: [
        new Text('测试1'),
        Divider(),
        new Text('测试2'),
        Divider(),
        new Text('测试3'),
        Divider(),
        new Text('测试4'),
        Divider(),
        new Text('测试5'),
        Divider(),
    ],
);

每个item不同的listview

  • 用switch-case返回不同的item来实现不同的item的view

    import 'package:flutter/material.dart';

    class MyListPage extends StatelessWidget {
    final List<Item> itemList = [
    Item(type: ItemType.button, data: ['Button 1', 'Button 2']),
    Item(type: ItemType.text, data: 'Hello World'),
    Item(
    type: ItemType.image,
    data: 'https://example.com/images/image.jpg'),
    ];

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('My List Page'),
        ),
        body: ListView.builder(
          itemCount: itemList.length,
          itemBuilder: (BuildContext context, int index) {
            Item item = itemList[index];
    
            Widget itemWidget;
    
            // 根据item类型构建不同的widget
            switch (item.type) {
              case ItemType.button:
                List<String> buttonTexts = item.data;
                itemWidget = Row(
                  children: [
                    ElevatedButton(
                      onPressed: () {},
                      child: Text(buttonTexts[0]),
                    ),
                    ElevatedButton(
                      onPressed: () {},
                      child: Text(buttonTexts[1]),
                    ),
                  ],
                );
                break;
    
              case ItemType.text:
                String textData = item.data;
                itemWidget = Text(textData);
                break;
    
              case ItemType.image:
                String imageUrl = item.data;
                itemWidget = Image.network(imageUrl);
                break;
            }
    
            return ListTile(
              title: itemWidget,
            );
          },
        ),
      );
    }
    

    }

    class Item {
    final ItemType type;
    final dynamic data;

    Item({
      required this.type,
      required this.data,
    });
    

    }

    enum ItemType {
    button,
    text,
    image,
    }

垂直滑动里嵌套水平滑动

class _PageBizCertificateState extends eState<PageBizCertificate> {

  final List<String> items = List.generate(20, (index) => 'Item $index');

  @override
  Widget pageBody(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        return MyListItem(title: items[index]);
      },
    );
  }

  Widget MyListItem({required String title}) {
    return Container(
      padding: EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('第一行文本'),
          SizedBox(height: 8.0),
          MyHorizontalScrollList(),
          SizedBox(height: 8.0),
          Text('第三行文本'),
        ],
      ),
    );
  }

  Widget MyHorizontalScrollList() {
    return Container(
      height: 100.0,
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: 10,
        itemBuilder: (context, index) {
          return Container(
            width: 100.0,
            margin: EdgeInsets.all(4.0),
            color: Colors.blue,
            child: Center(
              child: Text('水平项 $index'),
            ),
          );
        },
      ),
    );
  }

}

参数列表

  • scrollDirection:主轴滚动方向;默认垂直滚动Axis.vertical,还可水平滚动Axis.horizontal
  • reverse:是否反向排列,默认false,即正序排列
  • controller:滚动控制器,默认为null
  • primary:是否强制滚动,默认false
  • physics:响应用户滑动。
    • NeverScrollableScrollPhysics(), // 禁止滑动
  • shrinkWrap:滚动视图在滚动方向中的范围是否应由正在查看的内容决定。默认为false
  • padding:距边框item边间距
  • cacheExtent:缓存大小,默认250.0
  • itemExtent:item在交叉轴方向上的长度;null时为自适应
  • addAutomaticKeepAlives:是否自动保存滑出屏幕外的字widget的状态,默认true,保存,可复用
  • addRepaintBoundaries:是否放置到重绘列表中;默认true,可提高性能
  • itemBuilder:builder和separated构造函数特有要传入参数,定义item的样式
  • separatorBuilder:separated构造函数特有要传入的参数,定义分割线的样式
  • itemCount:列表可滚动的item数量,默认构造没有
  • childrenDelegate:内部都是靠此类实现滚动和布局,但只有custom构造函数需要传入,其余都内部实现
相关推荐
江上清风山间明月15 小时前
Flutter开发的应用页面非常多时如何高效管理路由
android·flutter·路由·页面管理·routes·ongenerateroute
Zsnoin能1 天前
flutter国际化、主题配置、视频播放器UI、扫码功能、水波纹问题
flutter
早起的年轻人1 天前
Flutter CupertinoNavigationBar iOS 风格导航栏的组件
flutter·ios
HappyAcmen1 天前
关于Flutter前端面试题及其答案解析
前端·flutter
coooliang1 天前
Flutter 中的单例模式
javascript·flutter·单例模式
coooliang1 天前
Flutter项目中设置安卓启动页
android·flutter
JIngles1231 天前
flutter将utf-8编码的字节序列转换为中英文字符串
java·javascript·flutter
B.-2 天前
在 Flutter 中实现文件读写
开发语言·学习·flutter·android studio·xcode
freflying11192 天前
使用jenkins构建Android+Flutter项目依赖自动升级带来兼容性问题及Jenkins构建速度慢问题解决
android·flutter·jenkins
机器瓦力2 天前
Flutter应用开发:对象存储管理图片
flutter