Flutter第三章(Paddiing,Row,Column,Expanded,Stack,AspectRatio,Card,Wrap)

版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!

情感语录:侠之大者为国为民,如果做不了侠者也别轻易做小人,多一些包容,其实很多事没你想像地那么糟糕!

哈喽,大家好,欢迎来到本章节,上一章节我们讲了Image组件ListView组件GridView组件 还记得吗?知识点回顾 戳这里 Fultter基础第二章在前两章节的捯饬下我相信你已经爱上了Flutter或者逐渐开始喜欢了上了,学习都是枯燥的,贵在坚持,加油!!!!

本章简要:

1、内距组件Paddiing
2、水平布局组件Row
3、垂直布局组件Column
4、权重布局组件Expanded
5、层叠 ( 帧 )组件Stack
6、纵横比组件AspectRatio
7、卡片组件Card
8、包裹组件Wrap

一、Paddiing组件

在 Android 原生控件中都有 padding 属性,但是 Flutter 中很多 Widget 是没有 padding 属性。这个时候我们可以用 Padding 组件处理容器与子元素之间的间距。

属性                 说明

padding             padding值, EdgeInsetss 设置填充的值

child               子组件

二、Row组件

Row水平布局组件其实理解也很简单,你可以看做是Android原生中LinearLayout布局控件属性为水平方向,或者说是一个横向的ListView控件,说是横向的ListView控件到更为贴切,因为在Flutter中Row的子元素接收的是一个List,这也使得Row控件变得更加灵活易用,开发中RowColumn使用频率是很高的。

属性                     说明

mainAxisAlignment        主轴的排序方式

crossAxisAlignment       次轴的排序方式

children                 组件子元素

三、Column组件

Column组件的用法其实和Row是一模一样的,只是一个是控制水平方向的,一个是垂直方向,会了Row组件的使用,Column自然也就会使用了。

属性                     说明

mainAxisAlignment        主轴的排序方式

crossAxisAlignment       次轴的排序方式

children                 组件子元素

四、Expanded组件

Expanded 可以用在 Row 和 Column 布局中,其的目的就是控制子元素之间的权重比,这和在原生中LinearLayout的权重使用是一个道理。

属性                说明

flex                元素站整个父 Row /Column 的比例

child               子元素

这上面的4个组件的属性并未全部列出,只是给出了常用的属性,在学习的同时,还是建议结合源码查看,只有对源码熟悉了学的才快嘛,通过上面的简单介绍,也对这4个组件有了简单的了解,下面我现将这学到的这几个组件进行下练习,毕竟光说不练那都是假把式嘛,还是要实操的O(∩_∩)O

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
          appBar: AppBar(
            title: Text("呆萌"),
          ),
          body: ViewLayout()),
    );
  }
}

class ViewLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        Text(
          "这是Paddiing的简单应用",
          style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
        ),

        SizedBox(
          height: 10,
        ),
        //用容器包装下 方便观察
        Container(
          color: Colors.deepPurple,
          height: 60,
          child: Padding(
            //可以通过fromLTRB分别设置左上右下的内距,也可以用all统一设置
            padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
            child: Image.network(
                'http://qukufile2.qianqian.com/data2/pic/246708144/246708144.jpg',
                fit: BoxFit.fitHeight),
          ),
        ),
        SizedBox(
          height: 10,
        ),
        Text(
          "这是Row应用(横向布局)",
          style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
        ),
        SizedBox(
          height: 10,
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          //spaceEvenly 主轴的排列方式最为常见
          crossAxisAlignment: CrossAxisAlignment.start,
          //用的比较少
          children: [
            IconWidget(Icons.search, color: Colors.blue),
            IconWidget(Icons.home, color: Colors.orange),
            IconWidget(Icons.select_all, color: Colors.red),
          ],
        ),

        SizedBox(
          height: 5,
        ),
        Text(
          "这是Column应用(垂直布局)",
          style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
        ),
        SizedBox(
          height: 5,
        ),
        Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          //spaceEvenly 主轴的排列方式最为常见
          crossAxisAlignment: CrossAxisAlignment.center,
          //用的比较少
          children: [
            IconWidget(Icons.search, color: Colors.blue),
            SizedBox(
              height: 5,
            ),
            IconWidget(Icons.home, color: Colors.orange),
            SizedBox(
              height: 5,
            ),
            IconWidget(Icons.select_all, color: Colors.red),
          ],
        ),
        SizedBox(
          height: 5,
        ),
        Text(
          "这是Expanded应用权重(1:2:1)",
          style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
        ),
        SizedBox(
          height: 5,
        ),

        Row(
          children: [
            Expanded(
                flex: 1, child: IconWidget(Icons.search, color: Colors.blue)),
            Expanded(
              flex: 2,
              child: IconWidget(Icons.home, color: Colors.orange),
            ),
            Expanded(
              flex: 1,
              child: IconWidget(Icons.select_all, color: Colors.red),
            ),
          ],
        ),
        SizedBox(
          height: 10,
        ),
        Text(
          "这是Expanded应用权重(1:2)",
          style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
        ),
        SizedBox(
          height: 10,
        ),

        Row(
          children: [
            Expanded(
                flex: 1, child: IconWidget(Icons.search, color: Colors.blue)),
            Expanded(
              flex: 2,
              child: IconWidget(Icons.home, color: Colors.orange),
            ),
          ],
        ),
      ],
    );
  }
}

//封装一个简单的Icon 方便调度
class IconWidget extends StatelessWidget {
  double size = 32.0;
  Color color = Colors.red;
  IconData icon;

  IconWidget(this.icon, {this.color, this.size});

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 50.0,
      width: 50.0,
      color: this.color,
      child:
          Center(child: Icon(this.icon, size: this.size, color: Colors.white)),
    );
  }
}

上面代码的布局效果如下:


实例1.png

读者在练习的时候尽量多手动写下,多尝试,比如我这里并没去细讲 ColumnRow中的MainAxisAlignment的属性以及效果,希望你在练习的时候能自己尝试感受下。

五、Stack组件

Stack 表示堆的意思,就是在该组件下放入的组件,会一层一层的累积,这样说理解起来可能有点抽象。想像一下,这就好比修房子,Stack就是房子的地基,而新建的第一层盖在地基上,第二层则盖在第一层上。有过Android原生开发的同学应该很好能理解,它就相当于原生中的FrameLayout控件。在Flutter中我们可以用 Stack 或者 Stack 结合 Align 或者 Stack 结合 Positiond 来实现页面的定位布局,Stack是定位发生的容器,只有在 Stack中,绝对定位的 Widget 才会生效

属性                说明

alignment           配置所有子元素的显示位置

children            子组件

其中关键的属性就是 children,除了几个样式控制的参数之外,通过 children 可以传入一个List,可以使列表用在 Stack 中进行绝对定位。

5.1 Align组件

Stack 组件中结合 Align 组件可以控制每个子元素的显示位置,既实现绝对定位

属性                说明

alignment           配置所有子元素的显示位置

child               子组件

无论是单独使用Stack或者结合Align 使用,其中对元素位置控制都是通过 alignment来指定的,下面我们来结合它的源码分析下:

///
/// The [x] and [y] arguments must not be null.
const Alignment(this.x, this.y)
: assert(x != null),
assert(y != null);

/// The distance fraction in the horizontal direction.
///
/// A value of -1.0 corresponds to the leftmost edge. A value of 1.0
/// corresponds to the rightmost edge. Values are not limited to that range;
/// values less than -1.0 represent positions to the left of the left edge,
/// and values greater than 1.0 represent positions to the right of the right
/// edge.
final double x;

/// The distance fraction in the vertical direction.
///
/// A value of -1.0 corresponds to the topmost edge. A value of 1.0
/// corresponds to the bottommost edge. Values are not limited to that range;
/// values less than -1.0 represent positions above the top, and values
/// greater than 1.0 represent positions below the bottom.
final double y;

@override
double get _x => x;

@override
double get _start => 0.0;

@override
double get _y => y;

/// The top left corner.
static const Alignment topLeft = Alignment(-1.0, -1.0);

/// The center point along the top edge.
static const Alignment topCenter = Alignment(0.0, -1.0);

/// The top right corner.
static const Alignment topRight = Alignment(1.0, -1.0);

/// The center point along the left edge.
static const Alignment centerLeft = Alignment(-1.0, 0.0);

/// The center point, both horizontally and vertically.
static const Alignment center = Alignment(0.0, 0.0);

/// The center point along the right edge.
static const Alignment centerRight = Alignment(1.0, 0.0);

/// The bottom left corner.
static const Alignment bottomLeft = Alignment(-1.0, 1.0);

/// The center point along the bottom edge.
static const Alignment bottomCenter = Alignment(0.0, 1.0);

/// The bottom right corner.
static const Alignment bottomRight = Alignment(1.0, 1.0);

这段源码一目了然,比较简单,首先Alignment是一个没有无参构造函数的类,创建该对象时必须传入非null 且是double类型的xy值,否则报错。接下来是对 xy值的描述,我们可以简单的理解为x(水平方向)的位置在当前容器的可见区域从左到右是(-1到1之间),超出该值范围,既超出了该容器的边,既不可以见,y(垂直方向)同理,只是从上到下而已。最后源码中帮我列出了9种常用的位置常量方便我们使用。

5.2 Positioned组件

Stack 组件中结合 Positioned 组件也可以控制每个子元素的显示位置,在绝对定位中Positioned可能更适合我们的开发习惯:

属性                说明

top                 子元素距离顶部的距离

bottom              子元素距离底部的距离

left                子元素距离左侧距离

right               子元素距离右侧距离

child               子组件

width               子元素的宽度

height              子元素的高度

下面我们从源码中去看下注释文档。

/// Creates a widget that controls where a child of a [Stack] is positioned.
///
/// Only two out of the three horizontal values ([left], [right],
/// [width]), and only two out of the three vertical values ([top],
/// [bottom], [height]), can be set. In each case, at least one of
/// the three must be null.
///
/// See also:
///
///  * [Positioned.directional], which specifies the widget's horizontal
///    position using `start` and `end` rather than `left` and `right`.
///  * [PositionedDirectional], which is similar to [Positioned.directional]
///    but adapts to the ambient [Directionality].
const Positioned({
Key key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
@required Widget child,
}) : assert(left == null || right == null || width == null),
assert(top == null || bottom == null || height == null),
super(key: key, child: child);

大致可以理解是在使用width的时候不能同时使用leftright,既不能同时出现这三个属性,否则报错,如果在使用width的同时又使用了leftright,既分别表示子元素从左边或者右边开始对齐移动leftright个单位值。同理heighttopbottom三者的使用亦是如此。

六、Card组件

Card 是卡片组件块,内容可以由大多数类型的 Widget 构成,Card 具有圆角和阴影,这让它看起来有立体感。

属性                     说明

margin                   外边距

child                    子组件

Shape                    Card 的阴影效果,默认的阴影效果为圆角的长方形边。

七、AspectRatio组件

AspectRatio 的作用是根据设置调整子元素 child 的宽高比, 它首先会在布局限制条件允许的范围内尽可能的扩展,widget 的高度是由宽度和比率决定的,类似于 BoxFit 中的 contain,按照固定比率去尽量占满区域。如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio 最终将会去优先适应布局限制条件,而忽略所设置的比率。

属性                           说明

aspectRatio                    宽高比,最终可能不会根据这个值去布局,
                               具体则要看综合因素,外层是否允许按照这
                               种比率进行布局,这只是一个参考值
                       
child                          子组件

该组件在理解上可能比较难,下面我们来看一个实例:

     new Container(
         height: 100,
         child: AspectRatio(
         aspectRatio: 3.0 / 1.0,
         child: Container(
          color: Colors.red,
      ),
    ))

示例代码是定义了一个高度为100的区域,内部AspectRatio比率设置为3,最终AspectRatio的宽是300,高是100的一个区域。

八、Wrap组件

开篇第一个例子讲了Column 或者 Row + Expanded Widget 来实现 flex 布局,但是 Row + Expanded 的实现方式有个致命问题是无法自动换行,而真正的 flex 布局是有一个 wrap 属性的,对于一行无法铺开的场景非常实用。Wrap组件可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Row 表现几乎一致。但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,mainAxis 上空间不足时,则向 crossAxis 上去扩展显示。

  /// Creates a wrap layout.
  ///
  /// By default, the wrap layout is horizontal and both the children and the
  /// runs are aligned to the start.
  ///
  /// The [textDirection] argument defaults to the ambient [Directionality], if
  /// any. If there is no ambient directionality, and a text direction is going
  /// to be necessary to decide which direction to lay the children in or to
  /// disambiguate `start` or `end` values for the main or cross axis
  /// directions, the [textDirection] must not be null.
  Wrap({
    Key key,
    this.direction = Axis.horizontal,
    this.alignment = WrapAlignment.start,
    this.spacing = 0.0,
    this.runAlignment = WrapAlignment.start,
    this.runSpacing = 0.0,
    this.crossAxisAlignment = WrapCrossAlignment.start,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    List children = const [],
  }) : super(key: key, children: children);

常用属性说明:

属性                 说明

direction            主轴的方向,默认水平

alignment            主轴的对其方式

spacing              主轴方向上的间距

textDirection        文本方向

verticalDirection    定义了children 摆放顺序,默认是 down,见Flex 相关属性介绍。

runAlignment         run 的对齐方式, 可以理解为新的行或者列,如果是水平方向布局的话,
                     run 可以理解为新的一行
                     
runSpacing           run 的间距

又讲了4个组件,加上开篇讲的4个组件本章节你已经掌握了8个常用的组件了,下面我们还是先对刚刚学到的4个组件来做个小的练习,加深下印象。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
          appBar: AppBar(
            title: Text("呆萌"),
          ),
          body: ViewLayout()),
    );
  }
}

class ViewLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        Text(
          "这是Stack的简单应用",
          style: TextStyle(fontSize: 18, color: Colors.green),
        ),

        SizedBox(
          height: 5,
        ),
        //用容器包装下设置背景色 方便观察
        Container(
          color: Colors.deepPurple,
          height: 60,
          child: Stack(
            alignment: Alignment(-1, -1), // 将子元素定位在左上,或者使用常量 topLeft
            children: [
              Container(
                width: 100,
                height: 40,
                color: Colors.red,
              ),
              Text('你说什么?', style: TextStyle(fontSize: 16, color: Colors.white))
            ],
          ),
        ),
        SizedBox(
          height: 5,
        ),
        Text(
          "这是Stack结合Align的应用",
          style: TextStyle(fontSize: 18, color: Colors.green),
        ),
        SizedBox(
          height: 5,
        ),
        Container(
          height: 100,
          width: 300,
          color: Colors.red,
          child: Stack(
            children: [
              Align(
                alignment: Alignment(1, 0), //定位最右边,垂直居中
                child: Icon(Icons.home, size: 30, color: Colors.white),
              ),
              Align(
                alignment: Alignment.center, //定位在容器的中心位置
                child: Icon(Icons.search, size: 30, color: Colors.white),
              ),
              Align(
                alignment: Alignment.bottomLeft, //定位在容器的左下
                child: Icon(Icons.ac_unit, size: 30, color: Colors.white),
              )
            ],
          ),
        ),
        SizedBox(
          height: 5,
        ),
        Text(
          "这是Stack结合Positioned的应用",
          style: TextStyle(fontSize: 18, color: Colors.green),
        ),
        SizedBox(
          height: 5,
        ),

        //用容器包装下设置背景色 方便观察
        Container(
          color: Colors.deepPurple,
          height: 60,
          child: Stack(
            children: [
              Positioned(
                right: 10, // 让子元素从右边开始对齐
                width: 120, //指定宽度为120个单位
                child: Icon(Icons.access_alarm, size: 30, color: Colors.white),
              ),
              Positioned(
                bottom: 0, // 让子元素从底部开始对齐
                left: 100,
                height: 50,
                child: Icon(Icons.memory, size: 30, color: Colors.white),
              ),
              Positioned(
                left: 5, // 让子元素从左边开始对齐
                width: 150,
                child: Text('你很帅,你造吗?',
                    style: TextStyle(fontSize: 16, color: Colors.white)),
              )
            ],
          ),
        ),
        SizedBox(
          height: 5,
        ),
        Text(
          "这是Card应用",
          style: TextStyle(fontSize: 18, color: Colors.green),
        ),
        SizedBox(
          height: 5,
        ),

        Card(
          margin: EdgeInsets.all(5),
          color: Colors.cyan,
          elevation: 10,
          //10个单位的阴影
          shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(14.0))),
          //设置圆角
          child: Column(
            children: [
              ListTile(
                title: Text("Mr.Z", style: TextStyle(fontSize: 18)),
                subtitle: Text("工程师", style: TextStyle(fontSize: 14)),
              ),
              ListTile(
                title: Text("电话:xxxxx"),
              ),
            ],
          ),
        ),

        SizedBox(
          height: 5,
        ),
        Text(
          "这是AspectRatio应用",
          style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
        ),
        SizedBox(
          height: 5,
        ),

        Container(
            height: 100,
            child: AspectRatio(
              aspectRatio: 3.0 / 1.0,
              child: Container(
                color: Colors.red,
              ),
            )),
        SizedBox(
          height: 5,
        ),
        Text(
          "这是Wrap应用",
          style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
        ),
        SizedBox(
          height: 5,
        ),
        Wrap(
           spacing: 10,
           runSpacing: 10,
           direction: Axis.horizontal,
           alignment:WrapAlignment.spaceEvenly,
           children: [
            ButtonItem("盗墓笔记"),
            ButtonItem("鬼吹灯"),
            ButtonItem("桃花怪大战菊花侠"),
            ButtonItem("无主之城"),
            ButtonItem("琅琊榜"),
            ButtonItem("仙剑奇侠传"),
            ButtonItem("风云决"),
            ButtonItem("哪吒"),
            ButtonItem("玄门大师"),
            ButtonItem("废材兄弟"),
            ButtonItem("爱情公寓"),
          ],
        )
      ],
    );
  }
}

//封装一个简单的Button 方便调度
class ButtonItem extends StatelessWidget {
  final String text;

  const ButtonItem(this.text, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
        child: Text(this.text,style: TextStyle(color: Colors.red),),
        textColor: Theme.of(context).cardColor,
        onPressed: () {});
  }
}

上述练习代码效果大致如下:


实例2.gif

本章实战:

由于本章讲述的组件相对较多但并不复杂,本章节实战环节跳过,但希望你在学习的时候多结合源码手动练习下,虽然不难但本章节的东西在开发中是使用非常频繁的。

实例源码地址: https://github.com/zhengzaihong/flutter_learn

好了本章节就此结束,又到了说再见的时候了,如果你喜欢请留下你的小红星,你们的支持才是创作的动力,如有错误,请热心的你留言指正, 谢谢大家观看,下章再会 O(∩_∩)O

你可能感兴趣的:(Flutter第三章(Paddiing,Row,Column,Expanded,Stack,AspectRatio,Card,Wrap))