(九)flutter入门之常见的功能组件与包裹组件

上篇博客我们学习了flutter中提供的ListView等常见的滚动类组件,本篇博客我们开始对常用的功能类组件和包裹容器类组件的学习

功能类组件

InkWell

在flutter开发中,我们可能会遇到一种场景下我们需要给没有默认提供手势点击事件的组件添加点击事件或者双击事件等操作,从而触发一系列操作,这个时候我们应该怎么做呢?在原生安卓开发中,我们知道,可以给任意view添加一个onclick即可完成点击事件,那么在flutter中如何去做?其实在flutter中给我们提供了一个功能组件用来包裹需要出发手势点击功能的组件--InkWell,翻看源码才发现,当前组件只有一个默认的构造,如下:

const InkWell({
    Key key,
    Widget child,//需要添加事件的子组件,在这里挂载
    GestureTapCallback onTap,//给子组件提供的点击事件能力,回调中写需要触发的功能
    GestureTapCallback onDoubleTap,//给子组件提供的双击触发事件的能力,回调中写需要实现的功能
    GestureLongPressCallback onLongPress,//给子组件提供的长按触发事件的能力,回调中填写具体实现业务
    GestureTapDownCallback onTapDown,//子组件提供的按下触发的事件的能力,回调中填写具体业务
    GestureTapCancelCallback onTapCancel,//子组件提供的点击取消事件的能力,回调中填写具体实现业务
    ValueChanged<bool> onHighlightChanged,//值改变的时候触发的事件
    Color highlightColor,//值改变的时候的突出的颜色
    Color splashColor,
    InteractiveInkFeatureFactory splashFactory,//可以实现飞溅效果的工厂,类型为InteractiveInkFeatureFactory
    double radius,
    BorderRadius borderRadius,
    ShapeBorder customBorder,
    bool enableFeedback = true,//是否启用反馈操作,默认启用
    bool excludeFromSemantics = false,//是否将当前组件从from中排除,不受管控,默认是false,不排除
  }) : super(
    key: key,
    child: child,
    onTap: onTap,
    onDoubleTap: onDoubleTap,
    onLongPress: onLongPress,
    onTapDown: onTapDown,
    onTapCancel: onTapCancel,
    onHighlightChanged: onHighlightChanged,
    containedInkWell: true,
    highlightShape: BoxShape.rectangle,
    highlightColor: highlightColor,
    splashColor: splashColor,
    splashFactory: splashFactory,
    radius: radius,
    borderRadius: borderRadius,
    customBorder: customBorder,
    enableFeedback: enableFeedback,
    excludeFromSemantics: excludeFromSemantics,
  );

可以从构造中看出来,只要添加一个子组件和一个具体触发的事件就可以了,接下来我们来简单实现一个案例:

import 'package:flutter/material.dart';

class MyWell extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return new _MyWell();
  }
}
class _MyWell extends State<StatefulWidget>{
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body:new Column(
        children: <Widget>[
          new Center(
            child:new Material(
              child: new InkWell(//添加了InkWell,重写了onTap点击事件,提供点击事件的能力
                onTap: (){
                  print('触发了点击事件');
                },
                child: new Container(//不要在这里设置背景色,会遮挡水波纹效果,如果设置的话在Material下面的color来实现
                  width: 300.0,
                  height: 100.0,
                  margin: EdgeInsets.all(0.0),//整体没有外边距
                ),
              ),
              color: Colors.yellow,
            ),
          ),
        ],
      ),
    );
  }
}

这样本身没有默认点击事件的容器就可以指定触发点击 事件等操作了,其他的操作可以自行操作研究

WillPopScope

在app开发中,我们会有这样的需求,整个页面开发的时候我们需要拦截back键的操作,不可以回退某些页面,或者有点击多次退出app等这样的需求,同样在flutter上也有对应的功能性组件----WillPopScope,构造如下(前面组件介绍已经写过注释的属性我们后面不会在写注释):

const WillPopScope({
    Key key,
    @required this.child,//包裹的子组件,相当于我们在什么组件上触发回退事件的时候需要进行拦截back操作,原理和LinkWell差不多
    @required this.onWillPop,//当前属性是一个回调函数,类型为WillPopCallback,当我们点击返回按钮的时候,改回调需要返回一个Future对象,返回的结果为false的话,说明当前的页面路由不从栈中移除(即不移除当前页面),如果为true,说明当前页面退出(出栈)
  }) : assert(child != null),
       super(key: key);

构造函数看起来很简单,那么我们通过当前组件实现一个app开发经常使用的功能,即页面中拦截back操作,当我们1s内点击两次后,退出当前页面(也可以是退出app,看你实现)的案例:

import 'package:flutter/material.dart';
class WillPopScopeTest extends StatefulWidget {
  @override
  WillPopScopeTestState createState() {
    return new WillPopScopeTestState();
  }
}
class WillPopScopeTestState extends State<WillPopScopeTest> {
  DateTime _oldTime; //上次点击时间
  @override
  Widget build(BuildContext context) {
    return new WillPopScope(
        onWillPop: () async {
          //如果上次点击的时间为null,并且上次的时间和当前的时间差距大于1s
          //DateTime.now()方法获取当前的时间(按照当前语言国际化的时间),
          //difference函数可以拿前一个时间与参数中的时间进行比较和计算,计算出时间差,单位为秒或者毫秒
          if (_oldTime == null || DateTime.now().difference(_oldTime) > Duration(seconds: 1)) {
            //两次点击间隔超过1秒则重新计时
            _oldTime = DateTime.now();
            return false;
          }
          return true;
        },
        child: Container(
          alignment: Alignment.center,
          child: Text("连续点击两次才可以退出当前页面"),
        )
    );
  }
}

包裹容器

Padding

相信看到Padding 这个组件你一定不会陌生了,不错,上面的很多案例中我们都使用了当前组件,而当前组件的作用是给子组件进行留白(填充空白,内边距填充空白)功能,构造如下:

 const Padding({
    Key key,
    @required this.padding,//当前参数必填,类型为EdgeInsetsGeometry,我们一般开发的时候使用EdgeInsets类来进行位置偏移和留白的操作
    Widget child,//需要进行填充的子组件
  }) : assert(padding != null),
       super(key: key, child: child);

可以看出来,基本上都是padding属性实现的效果,我们来看下EdgeInsets类:

const EdgeInsets.fromLTRB(this.left, this.top, this.right, this.bottom);

默认构造只要我们提供一个上下左右坐标的偏移量,就可以了,除此之外还有些简易的快捷构造,如下:

函数 函数功能介绍
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 ,指定一个对称方向即这两个方向同时指定一样数值进行偏移留白

那么我们来根据这些简单的实现个案例:

import 'package:flutter/material.dart';

class PaddingTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      //上下左右各添加16像素补白
      padding: EdgeInsets.all(16.0),
      child: Column(
        //指定对齐方式为左对齐(横轴对齐)
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.only(left: 8.0),//仅仅是左边偏移8px进行补白
            child: Text("only left"),
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 8.0),//垂直方向整体设置了8px补白(上下各8px)
            child: Text("top and bottom"),
          ),
          Padding(
            padding: const EdgeInsets.fromLTRB(20.0,0,20.0,0),//四个方向都分别指定不一样的偏移和补白策略
            child: Text("left, top, right ,bottom"),
          )
        ],
      ),
    );
  }
}

RenderConstrainedBox

flutter中常用到一种约束组件,可以用来限制子组件的大小,或者强制改变内部组件的空间的一类组件,这类组件都是通过RenderConstrainedBox 来渲染,所以这里归为一类学习,而开发中我们常用的有ConstrainedBoxSizedBox

ConstrainedBox

ConstrainedBox可以用来给子组件设置额外的约束,比如内部的子widget的最小的高度我们需要指定为60px,就可以使用当前的组件进行限制,构造如下:

ConstrainedBox({
    Key key,
    @required this.constraints, //必传参数,类型为BoxConstraints,当前属性指定了对子组件的约束行为
    Widget child   //需要进行约束的子组件,可以强制改变子组件的行为
  }) : assert(constraints != null),
       assert(constraints.debugAssertIsValid()),
       super(key: key, child: child);

可以看出来我们通过BoxConstraints进行约束组件,那么该类的默认构造我们看一下:

const BoxConstraints({
    this.minWidth = 0.0,//可以限制最小的宽度
    this.maxWidth = double.infinity,//可限制的最大宽度,默认为尽可能大,即宽度最大是多少就是多少
    this.minHeight = 0.0,//可以限制的最小高度
    this.maxHeight = double.infinity//可以限制的最大高度
  });

从上面我们可以看出来,ConstrainedBox可以限制子组件的宽度和高度的值,接下来我们来实现一个限制子组件的案例:

//定义一个蓝色的盒子容器,但是我们不去指定宽度和高度
Widget blueBox=new DecoratedBox(
  decoration: BoxDecoration(color: Colors.blue),
);
//在使用的组件中使用当前组件
new ConstrainedBox(
        constraints: new BoxConstraints(
          minWidth: double.infinity, //宽度最大
          minHeight: 50.0 //最小高度为50px
        ),
        child: new Container(
            height: 5.0, 
            child: blueBox 
        ),
      )

写完以后我们会发现,我们虽然设置的高度是5px,但是效果却是50px,宽度撑满了屏幕,这是因为我们限制了最小宽度和最小高度,这个时候,如果子组件不足这个数,会强制限制按照这个数值来显示,同理设置了最大值也是一样的效果,如果子组件大于这个数值,也会按照这个数值来显示

SizedBox

SizedBox可以用来给每一个子的widget强制指定宽高,例如:

SizedBox(
  width: 80.0,//强制指定宽度80
  height: 80.0,//强制指定高度也是80
  child: blueBox
)

然后我们就会看到子组件的大小和宽高变化成我们约束的样式了,这个时候我们就很奇怪,ConstrainedBox不也可以限制我们子组件的大小吗?这两者有什么区别吗?其实这两者没有太大区别,因为SizedBox就是基于ConstrainedBox实现的,我们可以看成一个简化版,接着我们看下SizedBox的构造:

const SizedBox({ Key key, this.width, this.height, Widget child })
    : super(key: key, child: child);

从构造可以看出来,我们只要传递宽高和当前组件就可以了,比起ConstrainedBox要简单许多,而我们上面的案例等价于

ConstrainedBox(
  constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
  child: blueBox, 
)

而BoxConstraints.tightFor这个构造其实等同于

BoxConstraints(minHeight: 80.0,maxHeight: 80.0,minWidth: 80.0,maxWidth: 80.0)

所以正常情况下我们需要限制子组件的宽高的时候建议都使用SizedBox

UnconstrainedBox

我们上面介绍了一些可以限制宽高的组件,那么我们如果在复杂的布局中,限制了大量的组件,但是我们需要按照触发条件不对某些组件进行限制,让他们按照自己的宽高进行展示该怎么办呢?别担心,UnconstrainedBox 组件可以接触多重限制,让限制类组件对当前子组件无效

首先我们先看看构造:

 const UnconstrainedBox({
    Key key,  
    Widget child, //接触限制的组件
    this.textDirection,//文字方向
    this.alignment = Alignment.center,//对齐方式默认为居中对齐
    this.constrainedAxis,//约束的轴方向
  }) : assert(alignment != null),
       super(key: key, child: child);

从构造可以看出来这一类约束组件需要的属性都比较少,所以我们来举一个例子:

AppBar(
   title: Text(title),
   actions: <Widget>[
         SizedBox(
             width: 20, //限制宽度20
             height: 20,//限制高度20
             child: CircularProgressIndicator(//加载进度条
                 strokeWidth: 3,
                 valueColor: AlwaysStoppedAnimation(Colors.white70),
             ),
         )
   ],
)

我们在appBar上挂载一个加载的进度条,但是我们可以看到SizedBox包裹了当前的进度条,也就是说我们当前的组件的宽高已经被固定了,即使我们修改了进度条也是无济于事,那么我们加入解除约束试下

AppBar(
  title: Text(title),
  actions: <Widget>[
      UnconstrainedBox(//当前组件包裹的所有的组件以及子组件都会解除宽高的约束
            child: SizedBox(//当前的宽高约束为20,但是已经失效
              width: 20,
              height: 20,
              child: CircularProgressIndicator(
                strokeWidth: 3,
                valueColor: AlwaysStoppedAnimation(Colors.white70),
              ),
          ),
      )
  ],
)

我们再次运行,会看到约束的宽高已经失效了,那么我们加入了UnconstrainedBox组件以后是不是真的就是完全解除了约束了呢?还是说我们看到的只是显示出来的效果,并不是真的解除了约束?这点特性让我们不禁想起了安卓view的显示和隐藏,是不是也会出现视觉上的解除约束的情况呢?答案是肯定的,如果这个时候我们限制的宽高比较高,即使加入了UnconstrainedBox,我们会发现当前组件的确显示出了自己的宽高,但是SizedBox限制的宽度高度依然占了那么大的区域,所以我们可以看出来,UnconstrainedBox也只是解除了当前组件的约束,但是对于包裹当前组件的父容器无能为力。那么有木有什么办法可以强制解除所有的约束呢?答案是否定的,不存在一个组件可以解除所有的布局约束并且能无视约束组件自身的宽高,所以博主建议UnconstrainedBox组件慎用,最好的实现方式就是在开发的时候就确定了当前组件的布局以及大小

DecoratedBox(装饰器组件)

我们在开发的过程中经常遇到一种需求,比如给当前的组件头部或者尾部添加一个小尾巴,或者添加一个slogo/背景等,所以在flutter中也提供了一个可以用来对组件进行修饰的组件--DecoratedBox ,用来满足此类需求

首先,我们先来看构造:

const DecoratedBox({
  Decoration decoration,//用来创建具体绘制的实现以及画笔等,类型为Decoration抽象类的实现类,我们最常用的实现类是BoxDecoration类
  DecorationPosition position = DecorationPosition.background,//装饰生效的位置,默认是在背景生效,可选值为DecorationPosition.background(widget之后绘制,即背景装饰)或者DecorationPosition.foreground(在widget之前绘制,即前景装饰)
  Widget child   //需要装饰的组件
})

BoxDecorationDecoratedBox 最常见的子类,我们一般也使用当前类来实现一部分装饰的效果,我们先来看一下构造:

BoxDecoration({
  Color color, //颜色
  DecorationImage image,//装饰的图片
  BoxBorder border, //装饰的边框效果
  BorderRadiusGeometry borderRadius, //装饰的圆角效果
  List<BoxShadow> boxShadow, //阴影样式的集合
  Gradient gradient, //渐变的效果样式
  BlendMode backgroundBlendMode, //背景混合模式,类型为BlendMode
  BoxShape shape = BoxShape.rectangle, //装饰绘制的形状,默认值为长方形
})

例如我们需要给一个按钮添加一个渐变的效果,案例实现如下:

DecoratedBox(
    decoration: BoxDecoration(
      gradient: LinearGradient(colors:[Colors.red,Colors.orange[700]]), //背景渐变,颜色由红色渐变为700色域的橙色
      borderRadius: BorderRadius.circular(3.0), //快速构造一个3px的圆角
      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("渐变色按钮", style: TextStyle(color: Colors.white),),
  )
)

这样就可以实现一个由红->橙色过度的一个渐变的按钮效果了

Container(组合容器)

我们在flutter开发的过程中,经常看到Container 这个组件出现,我们也经常使用这个组件设置了很多属性,现在我们再回头看看,是不是会感觉很熟悉?咦,不知不觉间这些属性似乎我们都见过了?没错,Container 组件拥有DecoratedBoxConstrainedBoxPaddingAlign 等我们常见的包裹类容器的功能,而内部的众多属性,也对应了这些组件的特定属性,那么同样的,我们可以使用Container 组件实现同时拥有装饰,动画以及复杂布局限制的高级效果,这个组件也会是我们flutter开发过程中很重要的一个组件,很多时候我们都选择使用当前组件,接下来,我们先看Container 的构造:

Container({
  this.alignment,//对齐方式
  this.padding, //容器内补白,属于decoration的装饰管控范围
  Color color, // 背景颜色
  Decoration decoration, // 装饰器装饰组件,一般都是BoxDecoration
  Decoration foregroundDecoration, //前景装饰器组件,可以实现同时装饰前景和其他背景
  double width,//容器的宽度(等同ConstrainedBox组件限制的宽度)
  double height, //容器的高度(等同ConstrainedBox组件限制的高度)
  BoxConstraints constraints, //容器大小的限制条件
  this.margin,//容器外补白,不受decoration的装饰范围管控
  this.transform, //变换
  this.child,
})

我们从构造中可以看出来,这些属性都很熟悉,我们在上面的组件中都见到过,但是color和decoration 这两个其实是互斥的,如果指定了color,其内部也会自动创建一个decoration ,并且width/height与constraints其实都是会限制宽高,如果两个都设置了,那么会先按照width/height优先,constraints会按照宽高生成一个对应的实例出现

Container的Padding和Margin

在Container中有两个属性可以指定内边距和外边距,实现留白的效果,接下来我们看看使用的方式以及区别:

Container(
  margin: EdgeInsets.all(20.0), //容器外补白(外边距)
  color: Colors.orange,
  child: Text("Margin补白"),
),
Container(
  padding: EdgeInsets.all(20.0), //容器内补白(内边距)
  color: Colors.orange,
  child: Text("Padding补白"),
),

运行起来的效果会发现padding设置的范围内,组件变大了,分别多了对应20px的空间,这个和Padding组件的效果表现无二,同样的这两个代码的使用方式与下面的代码是一样的效果:

Padding(
  padding: EdgeInsets.all(20.0),//通过padding组件限制内边距补白
  child: DecoratedBox(
    decoration: BoxDecoration(color: Colors.orange),
    child: Text("Padding补白"),
  ),
),
DecoratedBox(
  decoration: BoxDecoration(color: Colors.orange),
  child: Padding(
    padding: const EdgeInsets.all(20.0),//限制子组件的padding所在的位置设置补白
    child: Text("Margin补白"),
  ),
),

至此,我们常用的功能类组件以及包裹容器组件介绍完毕,下一篇我们将会开始学习布局组件

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 在Flutter中,号称一切皆widget,手势是Widget,动画是Widget,UI更是Widget,今天我们...
    Realank阅读 4,615评论 0 7
  • Flutter中的布局容器主要分为两类:只能包含一个子Widget的布局容器和可以包含多个子Widget的容器,下...
    彡廿阅读 2,436评论 0 19
  • 转自 Q吹个大气球Q 本文主要介绍了Flutter布局相关的内容,对相关知识点进行了梳理,并从实际例子触发,进一步...
    chilim阅读 1,916评论 0 17
  • 在这篇文章中,我们主要了解两个部分的内容,一个是 Flutter 的基本渲染逻辑 另一个是 Flutter 和 ...
    c263ae2147d8阅读 969评论 0 9
  • 国庆后面两天在家学习整理了一波flutter,基本把能撸过能看到的代码都过了一遍,此文篇幅较长,建议保存(star...
    Nealyang阅读 4,337评论 1 17