Says flutter---06布局Widget

381 阅读5分钟

01-单子布局

单子布局即只有一个子组件,常用的有Align、Center、Padding、Container等。
  • Align组件
    1.常用属性:
      alignment: Alignment.center, // 对齐方式,默认居中对齐
      widthFactor, // 宽度因子,不设置的情况,会尽可能大
      heightFactor, // 高度因子,不设置的情况,会尽可能大
      child // 要布局的子Widget
      其中,alignment设置有效的前提是父组件得知自己的宽度和高度;
           如果widthFactor和heightFactor不设置,那么Align默认尽可能大的占据自己的父组件;
           设置之后的意思是Align的宽度是其子组件的widthFactor倍。
    2.示例:
    class AlignDemo extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Align(
          alignment: Alignment(1, 1),
          widthFactor: 5,
          heightFactor: 5,
          child: Icon(Icons.pets, size: 50)
      );
     }
    }
    
  • Center组件
    Center组件继承自Align,只是将alignment设置为Alignment.center
  • Padding组件(没有Margin组件)
    class PaddingDemo extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Padding(
        padding: EdgeInsets.only(
            bottom: 10
        ),
        // padding: EdgeInsets.all(10)  意为全部内边距都为10
        child: Text("你好啊", style: TextStyle(fontSize: 30, backgroundColor: Colors.red)),
      );
     }
    }
    
  • Container组件
    1.常用属性:
      alignment,//对齐方式
      padding, //容器内补白,
      color, // 背景色
     	decoration, // 背景装饰
     	foregroundDecoration, //前景装饰
    	width,//容器的宽度
      height, //容器的高度
     	constraints, //容器大小的限制条件
      margin,//容器外补白,
    	transform, //变换
      child,//子组件
      其中,容器的大小可以通过widthheight属性指定,也可以通过constrains指定,如果同时存在,widthheight优先级高;
      colordecorationcolor是互斥的,只能存在一个。
    2.decoration属性:
      其对应的类型是Decoration抽象类,使用其实现类BoxDecoration进行实例化。对应属性为:
        color, // 颜色,会和Container中的color属性冲突
    	  image, // 背景图片
        border, // 边框,对应类型是Border类型,里面每一个边框使用BorderSide
        borderRadius, // 圆角效果
        boxShadow, // 阴影效果
        gradient, // 渐变效果
        backgroundBlendMode, // 背景混合
        shape = BoxShape.rectangle, //形变
    3.示例:
      class HYHomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          //      color: Colors.red,
          width: 200,
          height: 200,
          alignment: Alignment(0, 0),
          padding: EdgeInsets.all(20),
          margin: EdgeInsets.all(10),
          child: Text("Hello World"),
          transform: Matrix4.rotationZ(50),
          decoration: BoxDecoration(
              color: Colors.red,
              border: Border.all(
                width: 5,
                color: Colors.purple
              ),
              borderRadius: BorderRadius.circular(100),
              boxShadow: [
                BoxShadow(color: Colors.orange, offset: Offset(10, 10), spreadRadius: 5, blurRadius: 10),
                BoxShadow(color: Colors.blue, offset: Offset(-10, 10), spreadRadius: 5, blurRadius: 10),
              ]
          ),
        );
       }
    }
    4.实现圆角头像(Container+BoxDecorationclass HomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Container(
            width: 200,
            height: 200,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(20),
              image: DecorationImage(
                image: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
              )
            ),
          ),
        );
      }
    }
    

02-多子布局

多个Widget放在一起布局,水平方向、垂直方向或者层叠,常用组件为RowColumn、Stack。
  • Flex组件
    介绍:RowColumn都继承自Flex,区别在于Flex的属性中direction为Axis.horizontal时是Row,为Axis.vertical时为Column
    • Row组件
      所有子组件排成一行,水平方向也是尽可能占据比较大的空间,垂直方向包裹内容。
      1.常用属性:mainAxisAlignment = MainAxisAlignment.start, // 主轴对齐方式
      		  mainAxisSize = MainAxisSize.max, // 水平方向尽可能大(默认),
      		  textDirection=TextDirection.ltr, // 水平方向子widget的布局顺序(默认为从左向右)
                crossAxisAlignment = CrossAxisAlignment.center, // 交叉轴对齐方式
      		  verticalDirection = VerticalDirection.down, // 表示纵轴(交叉轴)的对齐方向
      		  textBaseline, // 如果上面是baseline对齐方式,那么选择什么模式(有两种可选)
      		  children = const <Widget>[]
          其中,MainAxisAlignment:
                start: 主轴的开始位置挨个摆放元素(默认值)
                end: 主轴的结束位置挨个摆放元素
                center: 主轴的中心点对齐
          	  spaceBetween: 左右两边的间距为0, 其它元素之间平分间距
                spaceAround: 左右两边的间距是其它元素之间的间距的一半
                spaceEvenly: 所有的间距平分空间
              CrossAxisAlignment:
                start: 交叉轴的起始位置对齐
                end: 交叉轴的结束位置对齐
                center: 中心点对齐(默认值)
                baseline: 基线对齐(必须有文本的时候才起效果)
                stretch: 先Row占据交叉轴尽可能大的空间, 将所有的子Widget交叉轴的高度, 拉伸到最大
      2.示例:
        class RowDemo1 extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return Container(
            height: 300,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              textBaseline: TextBaseline.ideographic,
              children: <Widget>[
                Container(
                  width: 80,
                  height: 60,
                  color: Colors.red,
                  child: Text(
                    "Hellxo",
                    style: TextStyle(fontSize: 20),
                  ),
                ),
                Container(
                  width: 120,
                  height: 100,
                  color: Colors.green,
                  child: Text(
                    "Woxrld",
                    style: TextStyle(fontSize: 30),
                  ),
                ),
              ],
            ),
          );
        }
      }
      
    • 特殊组件Expanded
      1.使用场景:希望某个组件能够占据剩余的空间,而不是设置固定的宽度,可以使用Expanded包裹,其属性flex弹性系数会决定剩下空间的占据比例。
      2.示例:
        class ExpandedDemo extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return Container(
            height: 300,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.center,
          	mainAxisSize: MainAxisSize.max,
          	textBaseline: TextBaseline.ideographic,
          	children: <Widget>[
                Expanded(flex:1child: Container(color: Colors.red)),
                Expanded(flex: 2,child: Container(color: Colors.green)),
                Container(width: 90, height: 80, color: Colors.blue),
                Container(width: 50, height: 120, color: Colors.orange),
              ],
            ),
          );
        }
      }
      
    • Column组件
      原理与Row组件基本一致
        class ColumnDemo extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        	  crossAxisAlignment: CrossAxisAlignment.center,
            textBaseline: TextBaseline.alphabetic,
            verticalDirection: VerticalDirection.down,
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Container(
                width: 80,
                height: 60,
                color: Colors.red,
                child: Text(
                  "Hellxo",
                  style: TextStyle(fontSize: 20),
                ),
              ),
          //...
            ],
          );
        }
      }
      
  • Stack组件
    1.使用场景:用于层叠布局,默认大小是包裹内容的
    2.常用属性:
      alignment = AlignmentDirectional.topStart,
      textDirection,
      fit = StackFit.loose,
      overflow = Overflow.clip,
      children = <Widget>[],
      其中,fit:此参数用于决定没有定位的子widget如何去适应Stack的大小。StackFit.loose表示使用子widget的大小,StackFit.expand表示扩伸到Stack的大小;
          overflow:此属性决定如何显示超出Stack显示空间的子widget,为Overflow.clip时,超出部分会被剪裁(隐藏),为Overflow.visible 时则不会。
    3.示例:
      class _RowDemo2State extends State<RowDemo2> {
      bool _isFavor = false;
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            Image.asset("assets/images/juren.jpeg"),
            Positioned(
              left: 0,
              right: 0,
              bottom: 0,
              child: Container(
                padding: EdgeInsets.symmetric(horizontal: 8),
                color: Color.fromARGB(150, 0, 0, 0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Text(
                      "进击的巨人挺不错的",
                      style: TextStyle(fontSize: 20, color: Colors.white),
                    ),
                    IconButton(
                      icon: Icon(
                        Icons.favorite,
                        color: _isFavor? Colors.red : Colors.white,
                      ),
                      onPressed: () {
                        setState(() {
                          _isFavor = !_isFavor;
                        });
                      },
                    )
                  ],
                ),
              ),
            )
          ],
        );
      }
    }
    

03-ListView组件

介绍:滚动组件,可以垂直或者水平排列所有子Widget。
  • 通过默认构造方法ListView()创建---会一次性创建出所有的子Widget
    class ListViewDemo1 extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return ListView(
          scrollDirection: Axis.horizontal,
          reverse: true,
          itemExtent: 100,
          children: List.generate(100, (index) {
            return ListTile(
              leading: Icon(Icons.people),
              trailing: Icon(Icons.delete),
              title: Text("联系人${index + 1}"),
              subtitle: Text("联系人电话号码:18866665555"),
            );
          }),
        );
      }
    }
    其中,ListTile组件就是经常用来实现形如有图标、文字、副标题和结束图标的布局的;
         scrollDirection属性是用来设置滚动的方向;
         itemExtent属性即为设置某一滚动方向上每个item所占据的宽度。
    
  • 通过ListView.builder创建---需要显示时才创建
    1.属性:itemBuilder:列表创建的方法,要求传入一个函数,该函数返回什么即显示什么内容;
           itemCount:列表项的数量,如若为空,则为无限列表。
    2.示例
    class ListViewDemo2 extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return ListView.builder(
        itemCount: 100,
        itemExtent: 60,
        itemBuilder: (BuildContext ctx, int index) {
          return Text(
            "Hello World: $index",
            style: TextStyle(fontSize: 20),
            );
          },
        );
      }
    }
    
  • 通过 ListView.separated创建---可以生成列表之间的分割器
    class ListViewDemo3 extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Container(
          height: 300,
          child: ListView.separated(
            itemCount: 100,
            itemBuilder: (BuildContext ctx, int index) {
              return Text(
                "Hello World: $index",
                style: TextStyle(fontSize: 20),
              );
            },
            separatorBuilder: (BuildContext ctx, int index) {
              return Divider(
                color: Colors.red,
                height: 30,
                indent: 30,
                endIndent: 30,
                thickness: 10,
              );
            },
          ),
        );
      } 
    }
    其中,比ListView.builder多了一个separatorBuilder属性。
    

04-GridView组件

介绍:展示多列或者是类似网格的布局
  • 通过默认构造方法GridView()实现
    class GridViewDemo1 extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return GridView(
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 400,   // 交叉轴的item宽度   
            crossAxisSpacing: 8, //交叉轴的间距
            mainAxisSpacing: 8,  //主轴的间距
            childAspectRatio: 1.8  //子Widget的宽高比
        ),
        children: List.generate(100, (index) {
          return Container(
            color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
            );
          }),
        );
      }
    }
    其中,属性gridDelegate要求传入的类型是SliverGridDelegate,因为其是一个抽象类,故使用其子类实现。其一子类是SliverGridDelegateWithMaxCrossAxisExtent,另外一个子类是SliverGridDelegateWithFixedCrossAxisCount,其中包括属性crossAxisCount为交叉轴的item个数,剩余属性和上面的一致。
    
  • 通过GridView.builder()实现---需要显示的时候才创建
    class GridViewDemo2 extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text("列表测试"),
        ),
        body: Padding(
          padding: EdgeInsets.symmetric(horizontal: 0),
          child: GridView.builder(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  mainAxisSpacing: 8,
                  crossAxisSpacing: 8
              ),
              itemBuilder: (BuildContext ctx, int index) {
                return Container(
                  color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
                );
              }
            ),
          ),
        );
      }
    }
    
  • 通过GridView.count()实现---与上面的属性基本一致
  • 通过GridView.extent()实现---与上面的属性基本一致

05-CustomScrollView

  • 使用场景
    当视图中出现包括标题视图、列表视图、网格视图等多种视图时,做到统一的滑动效果,统一管理多个滑动视图,即使用CustomScrollView组件统一管理。
    
  • Slivers基本使用
    CustomScrollView中,每一个独立的、可滚动的Widget都被称为Sliver。
    其中,CustomScrollViewslivers属性,里面放Sliver,有:
      SliverFixedExtentList:类似于SliverList可以设置滚动的高度;
      SliverGrid:类似于我们之前使用过的GridViewSliverPadding:设置Sliver的内边距,因为可能要单独给Sliver设置内边距;
      SliverAppBar:添加一个AppBar,通常用来作为CustomScrollViewHeaderViewSliverSafeArea:设置内容显示在安全区域(比如不让齐刘海挡住我们的内容);
      SliverList:类似于我们之前使用过的ListView;
    示例:
    class CustomScrollView extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
      	//      appBar: AppBar(
      	//        title: Text("Slivers Demo"),
      	//      ),
        body: CustomScrollView(
          slivers: <Widget>[
            SliverAppBar(
              expandedHeight: 300,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                title: Text("Hello World"),
                background: Image.asset("assets/images/juren.jpeg", fit: BoxFit.cover,),
              ),
            ),
            SliverGrid(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  crossAxisSpacing: 8,
                  mainAxisSpacing: 8,
                  childAspectRatio: 2
              ),
              delegate: SliverChildBuilderDelegate(
                      (BuildContext ctx, int int) {
                    return Container(color: Color.fromARGB(255, Random().nextInt(
                        256), Random().nextInt(256), Random().nextInt(256)));
                  },
                  childCount: 10
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                      (BuildContext ctx, int index) {
                    return ListTile(
                      leading: Icon(Icons.people),
                      title: Text("联系人$index"),
                    );
                  },
                  childCount: 20
              ),
            )
          ],
        ),
      );
     }
    }
    

06-监听滚动事件

  • 通过ScrollController

    controller可以设置默认值offset;可以监听滚动和滚动的位置。
    class MyHomePage extends StatefulWidget {
    	@override
    	State<StatefulWidget> createState() => MyHomePageState();
    }
    class MyHomePageState extends State<MyHomePage> {
    	ScrollController _controller;
      bool _isShowTop = false;
      @override
    	void initState() {
      	// 初始化ScrollController
        _controller = ScrollController();
      	// 监听滚动
        _controller.addListener(() {
        	var tempSsShowTop = _controller.offset >= 1000;
        	if (tempSsShowTop != _isShowTop) {
          	setState(() {
            		_isShowTop = tempSsShowTop;
          	});
        	}
        });
        super.initState();
      }
    	@override
    	Widget build(BuildContext context) {
      	return Scaffold(
        		appBar: AppBar(
          	title: Text("ListView展示"),
        		),
        		body: ListView.builder(
          		itemCount: 100,
          		itemExtent: 60,
          		controller: _controller,
          		itemBuilder: (BuildContext context, int index) {
            		return ListTile(title: Text("item$index"));
          		}
        		),
        			floatingActionButton: !_isShowTop ? null : FloatingActionButton(
          		child: Icon(Icons.arrow_upward),
          		onPressed: () {
            			_controller.animateTo(0, duration: Duration(milliseconds: 1000), curve: Curves.ease);
          		},
        		),
      	);
    	}
    }
    
  • 通过NotificationListener

    可以监听开始滚动和结束滚动。
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text("列表测试"),
        ),
        body: NotificationListener(
          onNotification: (ScrollNotification notification) {
            if (notification is ScrollStartNotification) {
              print("开始滚动");
            } else if (notification is ScrollUpdateNotification) {
              print("正在滚动...,总滚动距离:${notification.metrics.maxScrollExtent} 当前滚动的位置: ${notification.metrics.pixels}");
            } else if (notification is ScrollEndNotification) {
              print("结束滚动");
            }
            return true;
          },
          child: ListView.builder(
              controller: _controller,
              itemCount: 100,
              itemBuilder: (BuildContext ctx, int index) {
                return ListTile(
                  leading: Icon(Icons.people),
                  title: Text("联系人$index"),
                );
              }
          ),
        ),
        floatingActionButton: _isShowFloatingBtn? FloatingActionButton(
          child: Icon(Icons.arrow_upward),
          onPressed: () {
            _controller.animateTo(0, duration: Duration(seconds: 1), curve: Curves.easeIn);
          },
        ): null,
      );
    }