手记

Flutter——容器类组件

容器类Widget和布局类Widget都作用于其子Widget,不同的是:

  • 布局类Widget一般都需要接收一个widget数组(children),他们直接或间接继承自(或包含)MultiChildRenderObjectWidget ;而容器类Widget一般只需要接收一个子Widget(child),他们直接或间接继承自(或包含)SingleChildRenderObjectWidget。
  • 布局类Widget是按照一定的排列方式来对其子Widget进行排列;而容器类Widget一般只是包装其子Widget,对其添加一些修饰(补白或背景色等)、变换(旋转或剪裁等)、或限制(大小等)。

填充(Padding)

Padding可以给其子节点添加填充(留白),注意是留白。

Padding({
  ...
  EdgeInsetsGeometry padding,
  Widget child,
})

EdgeInsetsGeometry是一个抽象类,开发中,我们一般都使用EdgeInsets类.
EdgeInsets提供的便捷方法:

  • fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的填充。
  • all(double value) : 所有方向均使用相同数值的填充。
  • only({left, top, right ,bottom }):可以设置具体某个方向的填充(可以同时指定多个方向)。
  • symmetric({ vertical, horizontal }):用于设置对称方向的填充,vertical指top和bottom,horizontal指left和right。
Padding(
      //上下左右各添加16像素补白
      padding: EdgeInsets.all(16.0),
      child: Column(
        ...;
      ),
    );

尺寸限制类容器

尺寸限制类容器用于限制容器大小,Flutter中提供了多种这样的容器,如ConstrainedBoxSizedBoxUnconstrainedBoxAspectRatio等.

ConstrainedBox

ConstrainedBox用于对子组件添加额外的约束.

ConstrainedBox({
    Key key,
    @required this.constraints,
    Widget child,
  })

this.constraints如下:

final BoxConstraints constraints;

也就是说,ConstrainedBox通过constraints这个参数进行设置限制。

ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: double.infinity, //宽度尽可能大
    minHeight: 50.0 //最小高度为50像素
  ),
  child: Container(
      height: 5.0, //不生效
      child: redBox 
  ),
)

BoxConstraints

BoxConstraints用于设置限制条件,它的定义如下:

const BoxConstraints({
  this.minWidth = 0.0, //最小宽度
  this.maxWidth = double.infinity, //最大宽度
  this.minHeight = 0.0, //最小高度
  this.maxHeight = double.infinity //最大高度
})

BoxConstraints还定义了一些便捷的构造函数,用于快速生成特定限制规则的BoxConstraints,如BoxConstraints.tight(Size size),它可以生成给定大小的限制;const BoxConstraints.expand()可以生成一个尽可能大的用以填充另一个容器的BoxConstraints.

SizedBox

SizedBox(
  width: 80.0,
  height: 80.0,
  child: ...;
)

实际上SizedBox只是ConstrainedBox的一个定制。
实际上ConstrainedBoxSizedBox都是通过RenderConstrainedBox来渲染的,我们可以看到ConstrainedBoxSizedBoxcreateRenderObject()方法都返回的是一个RenderConstrainedBox对象.

@override
RenderConstrainedBox createRenderObject(BuildContext context) {
  return new RenderConstrainedBox(
    additionalConstraints: ...,
  );
}

多重限制

ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), //父
    child: ConstrainedBox(
      constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
      child: redBox,
    )
)

有多重限制时,对于minWidth和minHeight来说,是取父子中相应数值较大的,对于maxWidth和maxHeight,反之;实际上,只有这样才能保证父限制与子限制不冲突。

UnconstrainedBox

UnconstrainedBox不会对子组件产生任何限制,它允许其子组件按照其本身大小绘制。一般情况下,我们会很少直接使用此组件,但在"去除"多重限制的时候也许会有帮助

ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 80.0), //父
    //"去除"父级限制,但注意并非是真正的去除
    //父ConstrainedBox是作用于子UnconstrainedBox上,而redBox只受子ConstrainedBox限制
    child: UnconstrainedBox(
       //“去除”父级限制
       child: ConstrainedBox(
         constraints: BoxConstraints(minWidth: 90.0, minHeight: 5.0),
         //子
         child: redBox,
       ),
 )),

注意,UnconstrainedBox对父组件限制的“去除”并非是真正的去除:上面例子中虽然红色区域大小是90×5,但上方仍然有75(80-5)的空白空间。也就是说父限制的minHeight(80.0)仍然是生效的,只不过它不影响最终子元素redBox的大小,但仍然还是占有相应的空间,可以认为此时的父ConstrainedBox是作用于子UnconstrainedBox上,而redBox只受子ConstrainedBox限制,这一点请读者务必注意。

装饰容器DecoratedBox

DecoratedBox可以在其子组件绘制前(或后)绘制一些装饰(Decoration),如背景、边框、渐变等。DecoratedBox定义如下:

const DecoratedBox({
  Decoration decoration,
  DecorationPosition position = DecorationPosition.background,
  Widget child
})
  • decoration:代表将要绘制的装饰,它的类型为Decoration。Decoration是一个抽象类,它定义了一个接口 createBoxPainter(),子类的主要职责是需要通过实现它来创建一个画笔,该画笔用于绘制装饰。
  • position:此属性决定在哪里绘制Decoration,它接收DecorationPosition的枚举类型,该枚举类有两个值:
    1. background:在子组件之后绘制,即背景装饰。
    2. foreground:在子组件之上绘制,即前景。

BoxDecoration

我们通常会直接使用BoxDecoration类,它是一个Decoration的子类,实现了常用的装饰元素的绘制。

BoxDecoration({
  Color color, //颜色
  DecorationImage image,//图片
  BoxBorder border, //边框
  BorderRadiusGeometry borderRadius, //圆角
  List<BoxShadow> boxShadow, //阴影,可以指定多个
  Gradient gradient, //渐变
  BlendMode backgroundBlendMode, //背景混合模式
  BoxShape shape = BoxShape.rectangle, //形状
})
DecoratedBox(
    decoration: BoxDecoration(
      gradient: LinearGradient(colors:[Colors.red,Colors.orange[700]]), //背景渐变
      borderRadius: BorderRadius.circular(3.0), //3像素圆角
      boxShadow: [ //阴影
        BoxShadow(
            color:Colors.black54,
            offset: Offset(2.0,2.0),
            blurRadius: 4.0
        )
      ]
    ),
  child: Padding(padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0),
    child: Text("Login", style: TextStyle(color: Colors.white),),
  )
)

变换(Transform)

Transform可以在其子组件绘制时对其应用一些矩阵变换来实现一些特效.
Matrix4是一个4D矩阵,通过它我们可以实现各种矩阵操作,下面是一个例子:

Container(
  color: Colors.black,
  child: new Transform(
    alignment: Alignment.topRight, //相对于坐标系原点的对齐方式
    transform: new Matrix4.skewY(0.3), //沿Y轴倾斜0.3弧度
    child: new Container(
      padding: const EdgeInsets.all(8.0),
      color: Colors.deepOrange,
      child: const Text('Apartment for rent!'),
    ),
  ),
);

平移

Transform.translate接收一个offset参数,可以在绘制时沿x、y轴对子组件平移指定的距离。

DecoratedBox(
  decoration:BoxDecoration(color: Colors.red),
  //默认原点为左上角,左移20像素,向上平移5像素  
  child: Transform.translate(
    offset: Offset(-20.0, -5.0),
    child: Text("Hello world"),
  ),
)

旋转

Transform.rotate可以对子组件进行旋转变换,如:

DecoratedBox(
  decoration:BoxDecoration(color: Colors.red),
  child: Transform.rotate(
    //旋转90度
    angle:math.pi/2 ,
    child: Text("Hello world"),
  ),
)

缩放

Transform.scale可以对子组件进行缩小或放大,如:

DecoratedBox(
  decoration:BoxDecoration(color: Colors.red),
  child: Transform.scale(
      scale: 1.5, //放大到1.5倍
      child: Text("Hello world")
  )
)

注意

  • Transform的变换是应用在绘制阶段,而并不是应用在布局(layout)阶段,所以无论对子组件应用何种变化,其占用空间的大小和在屏幕上的位置都是固定不变的,因为这些是在布局阶段就确定的。
  • 由于矩阵变化只会作用在绘制阶段,所以在某些场景下,在UI需要变化时,可以直接通过矩阵变化来达到视觉上的UI改变,而不需要去重新触发build流程,这样会节省layout的开销,所以性能会比较好。

RotatedBox

RotatedBox和Transform.rotate功能相似,它们都可以对子组件进行旋转变换,但是有一点不同:RotatedBox的变换是在layout阶段,会影响在子组件的位置和大小。

Container

Container是一个组合类容器,它本身不对应具体的RenderObject,它是DecoratedBox、ConstrainedBox、Transform、Padding、Align等组件组合的一个多功能容器,所以我们只需通过一个Container组件可以实现同时需要装饰、变换、限制的场景。下面是Container的定义:

Container({
  this.alignment,
  this.padding, //容器内补白,属于decoration的装饰范围
  Color color, // 背景色
  Decoration decoration, // 背景装饰
  Decoration foregroundDecoration, //前景装饰
  double width,//容器的宽度
  double height, //容器的高度
  BoxConstraints constraints, //容器大小的限制条件
  this.margin,//容器外补白,不属于decoration的装饰范围
  this.transform, //变换
  this.child,
})

注意

  • 容器的大小可以通过width、height属性来指定,也可以通过constraints来指定;如果它们同时存在时,width、height优先。实际上Container内部会根据width、height来生成一个constraints。
  • color和decoration是互斥的,如果同时设置它们则会报错!实际上,当指定color时,Container内会自动创建一个decoration。
Container(
  margin: EdgeInsets.only(top: 50.0, left: 120.0), //容器外填充
  constraints: BoxConstraints.tightFor(width: 200.0, height: 150.0), //卡片大小
  decoration: BoxDecoration(//背景装饰
      gradient: RadialGradient( //背景径向渐变
          colors: [Colors.red, Colors.orange],
          center: Alignment.topLeft,
          radius: .98
      ),
      boxShadow: [ //卡片阴影
        BoxShadow(
            color: Colors.black54,
            offset: Offset(2.0, 2.0),
            blurRadius: 4.0
        )
      ]
  ),
  transform: Matrix4.rotationZ(.2), //卡片倾斜变换
  alignment: Alignment.center, //卡片内文字居中
  child: Text( //卡片文字
    "5.20", style: TextStyle(color: Colors.white, fontSize: 40.0),
  ),
);

Padding和Margin

直观的感觉就是margin的留白是在容器外部,而padding的留白是在容器内部,读者需要记住这个差异。事实上,Container内margin和padding都是通过Padding 组件来实现的.

Scaffold、TabBar、底部导航

Scaffold

一个完整的数路由页可能会包含导航栏、抽屉菜单(Drawer)以及底部Tab导航菜单等。Scaffold常用的几个组件:

组件名称 解释
AppBar 一个导航栏骨架
MyDrawer 抽屉菜单
BottomNavigationBar 底部导航栏
FloatingActionButton 漂浮按钮

剪裁(Clip)

Flutter中提供了一些剪裁函数,用于对组件进行剪裁。

剪裁Widget 作用
ClipOval 子组件为正方形时剪裁为内贴圆形,为矩形时,剪裁为内贴椭圆
ClipRRect 将子组件剪裁为圆角矩形
ClipRect 剪裁子组件到实际占用的矩形大小(溢出部分剪裁)
// 头像  
    Widget avatar = Image.asset("imgs/avatar.png", width: 60.0);
    ...;
          ClipOval(child: avatar), //剪裁为圆形
          ClipRRect( //剪裁为圆角矩形
            borderRadius: BorderRadius.circular(5.0),
            child: avatar,
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ClipRect(//将溢出部分剪裁
                child: Align(
                  alignment: Alignment.topLeft,
                  widthFactor: .5,//宽度设为原来宽度一半
                  child: avatar,
                ),
              ),
              Text("你好世界",style: TextStyle(color: Colors.green))
            ],
          ),
	 ...;

CustomClipper

如果我们想剪裁子组件的特定区域,比如,在上面示例的图片中,如果我们只想截取图片中部40×30像素的范围应该怎么做?这时我们可以使用CustomClipper来自定义剪裁区域,实现代码如下:
首先,自定义一个CustomClipper:

class MyClipper extends CustomClipper<Rect> {
  @override
  Rect getClip(Size size) => Rect.fromLTWH(10.0, 15.0, 40.0, 30.0);

  @override
  bool shouldReclip(CustomClipper<Rect> oldClipper) => false;
}
  • getClip(): 是用于获取剪裁区域的接口,由于图片大小是60×60,我们返回剪裁区域为Rect.fromLTWH(10.0, 15.0, 40.0, 30.0),及图片中部40×30像素的范围。
  • shouldReclip(): 接口决定是否重新剪裁。如果在应用中,剪裁区域始终不会发生变化时应该返回false,这样就不会触发重新剪裁,避免不必要的性能开销。如果剪裁区域会发生变化(比如在对剪裁区域执行一个动画),那么变化后应该返回true来重新执行剪裁。

然后,我们通过ClipRect来执行剪裁,为了看清图片实际所占用的位置,我们设置一个红色背景:

  decoration: BoxDecoration(
    color: Colors.red
  ),
  child: ClipRect(
      clipper: MyClipper(), //使用自定义的clipper
      child: avatar
  ),
)
0人推荐
随时随地看视频
慕课网APP