Flutter基本组件1

文本

Flutter中使用Text来实现一般的文本,构建一个Text方法如下:

//构造实例
 Text(......)
 new Text(......)

属性

属性 作用
style 文本样式,返回TextStyle,可以设置字体颜色、下划线、字体大小等(最常用的玩意儿,基本上都得用)
textAlign 文本对齐方式
softWrap 是否自动换行,值为boolean型
overflow 当文本内容超过最大行数时,剩余内容的显示方式当文本内容超过最大行数时,剩余内容的显示方式
textDirection 文本方向,即文字从那边开始显示(布局里这属性有些用,这里貌似基本上没啥用)
locale 此属性很少设置,用于选择区域特定字形的语言环境(基本上没啥用)
semanticsLabel 图像的语义描述,用于向Andoid上的TalkBack和iOS上的VoiceOver提供图像描述(可忽略,暂时没啥用)

[站外图片上传中...(image-6886f2-1562061232581)]

TextStyle

TextStyle可以设置文字的属性,其中比较简单的包括Color(文本颜色)、fontSize(文字大小),如果不设置,文本将使用最接近的DefaultTextStyle的样式。如果给定样式的TextStyle.inherit属性为true(默认值),则给定样式将与最接近的DefaultTextStyle合并。以下是属性:

属性 作用
color 文本颜色。与foreground互斥
decoration 绘制文本装饰(下划线、上划线、删除线)
decorationColor Decoration的颜色,和decoration配合使用,单独使用没效果
decorationStyle 绘制文本装饰的样式
fontWeight 绘制文本时使用的字体粗细(默认w400),文字加粗效果用这个
fontStyle 字体变体(斜体、正常)
fontFamily 设置字体,外面导入的稀奇古怪的字体
fontSize 字体大小
letterSpacing 水平字母之间的空间间隔
wordSpacing 水平单词之间的空间间隔
height 文本行与行的高度(非控件高度,注意了)
locale 区域特定字形
background 文本背景色
foreground 文本的前景色,和color互斥,需要返回paint(画笔)

这里做了一个简单的实现

Widget _ZongheText() {
    return Text(
      "一片喧哗叫嚷之中,忽听得山下一个雄壮的声音说道:谁说星宿派武功胜得了丐帮的降龙十八掌?这声音也不如此响亮,但清清楚楚的传入了从人耳中,众人一愕之间,都住了口。hello world hheh aaaayy",
      style: TextStyle(
          inherit: false,
          color: Colors.pinkAccent,
          fontWeight: FontWeight.w600,
          fontSize: 16,
          letterSpacing: 2.0,
          wordSpacing: 5.0,
          //只适用于英文
          fontStyle: FontStyle.italic,
          textBaseline: TextBaseline.alphabetic,
          height: 3,
          decorationStyle: TextDecorationStyle.dashed,
          decoration: TextDecoration.underline,
          fontFamily: "systemmessage"),
    );
  } 

效果如下:

[站外图片上传中...(image-1dc262-1562061232581)]

RichText

如果需要对一段文本的不同段落采用不同的文字样式,可以使用该控件。具体使用方法如下:

//RichText构造实例
new RichText(......)
Text.rich(......)

//测试demo
  Widget _RichTestWidget(BuildContext context) {
    return Container(
        margin: EdgeInsets.fromLTRB(0, 10, 5, 0),
        padding: EdgeInsets.fromLTRB(0, 5, 0, 5),
        child: Text.rich(TextSpan(
            text: "flutter中RichText用于处理一段文字可能有不同的风格,如:",
            children: <TextSpan>[
              TextSpan(text: "红色,", style: TextStyle(color: Colors.red)),
              TextSpan(text: "黄色,", style: TextStyle(color: Colors.yellow)),
              TextSpan(text: "蓝色,", style: TextStyle(color: Colors.blue)),
              TextSpan(
                  text: "点我试试",
                  recognizer: TapGestureRecognizer()
                    ..onTap = () {
                      var snackBar = SnackBar(
                        content: Text("hello world"),
                        duration: Duration(seconds: 3), // 持续时间
                      );
                      _scaffoldkey.currentState.showSnackBar(snackBar);
                    }),
            ],
            style: TextStyle(color: Color(0xff000000)))));
  }

效果如下:

[站外图片上传中...(image-9b1769-1562061232581)]

PS

  1. Text没有宽度、高度以及padding设置,如果需要设置这些属性,则需要在外面包裹一层Container,设置Container的这些属性
  2. 不同于Android的TextView,Text没有点击触发监听,如果需要可点击,则需要设置手势(感觉比较麻烦,不推荐)或者使用FlatButton(相当于一个可点击的Text)
  3. color和foreground只能存在一个,否则报错
  4. RichText中如果内部的TextSpan设置了TextStyle,则外部的TextStyle无效。

按钮

在 Flutter 里有很多的 Button,包括了:MaterialButton、RaisedButton、FloatingActionButton、FlatButton、IconButton、ButtonBar、DropdownButton 等。
一般常用的 Button 是 MaterialButton、IconButton、FloatingActionButton。

一般按钮(MaterialButton、RaisedButton、OutlineButton、FlatButton)

RaisedButton、OutlineButton、FlatButton都是一般的按钮,这三个按钮均是继承于MaterialButton,它们的区别在于:

  1. RaisedButton:一般的按钮,类似android的button,有凸起
  2. FlatButton:扁平式按钮,相当于有点击事件的text
  3. OutlineButton:带边框的背景透明的按钮

构造方法如下:

//构造MaterialButton,参数里面如果没有onPressed,会报警告,不影响编译,默认为button被禁用,
//这里onPressed回调里面,()表示回调参数,这里没有表示未传参,{}里面编写回调后的操作代码
MaterialButton( onPressed: () {}) 

由于这三个button均是继承于MaterialButton,MaterialButton具有的属性这三个button也都具有,只是MaterialButton的部分属性它们不具有,这里MaterialButton的具体属性如下,常用属性加粗显示:

属性 作用
onPressed 点击按钮的回调,设置为null的话,点击效果失效
textColor 文字颜色
disabledTextColor 按钮被禁用时的文字颜色
color 按钮的背景色 均有
disabledColor 按钮禁用时的背景颜色
disabledTextColor 按钮禁用时的文字颜色
splashColor 点击按钮时水波纹的颜色
shape 设置按钮的形状
padding 内边距
elevation 阴影高度
highlightElevation 按钮高亮(按下)时的阴影高度
disabledElevation 按钮禁用时的阴影高度
minWidth 最小宽度
height 按钮高度

左边FlatButton,右边MaterialButton
[站外图片上传中...(image-49792b-1562061232581)]

    return Row(children: <Widget>[
      Container(
          height: 24,
          child: FlatButton(
            padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
            child: Text(
              this.text,
              style: TextStyle(color: _color(), fontSize: 12),
            ),
            onPressed: () {
              setState(() {
                if (this.choose) {
                  this.choose = false;
                } else {
                  this.choose = true;
                }
              });
            },
            shape: _shapeBorder(),
          )),
      MaterialButton(
        padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
        minWidth: 0,
        height: 24,
        child: Text(
          this.text,
          style: TextStyle(color: _color(), fontSize: 12),
        ),
        onPressed: () {
          setState(() {
            if (this.choose) {
              this.choose = false;
            } else {
              this.choose = true;
            }
          });
        },
        shape: _shapeBorder(),
      ),
    ]); 

shape

这个属性需要多注意下,用来设定button的边框形状。常用的有以下几个:

  1. BeveledRectangleBorder 带斜角的长方形边框
  2. CircleBorder 圆形边框
  3. RoundedRectangleBorder 圆角矩形
  4. StadiumBorder 两端是半圆的边框

图标按钮(IconButton)

这个比较简单,一个可交互的图形图标。属性页没啥难以理解的,边框是个透明圆形,类似Android的ImageButton。直接上代码:

   Widget _IconButton() {
    return IconButton(
      onPressed: () {},
      icon: Image.asset("assets/images/food01.jpeg"),
//      icon: Icon(Icons.forward),
      iconSize: 24,

    );
  }

悬浮按钮(FloatingActionButton)

FloatingActionButton,在Material Design中,一般用来处理界面中最常用,最基础的用户动作。它一般出现在屏幕内容的前面,通常是一个圆形,中间有一个图标。 FAB有三种类型:regular, mini, and extended。不要强行使用FAB,只有当使用场景符合FAB功能的时候使用才最为恰当。需要注意的属性如下:

属性 作用
heroTag hero效果使用的tag,系统默认会给所有FAB使用同一个tag,方便做动画效果,使得界面切换不再那么生硬
mini 是否为“mini”类型,默认为false,设置为true,要小一号,正常size = 56dp ,mini为size = 40dp
isExtended 是否为”extended”类型
tooltip FAB的文字解释,FAB被长按时显示在按钮上方(没啥用,可以不管)

floatingActionButtonLocation

这是Scaffold Widget的一个属性,和FloatingActionButton配合使用。可以设置FloatingActionButton的位置。具体效果如下图:
[站外图片上传中...(image-38ec77-1562061232581)]
使用FloatActionButtonLocation中的centerDocked属性和BottomNavigationBar配合可以实现下面的设计,这个设计在喵叽和王者荣耀里面出现过,具体效果如下:

[站外图片上传中...(image-7c7736-1562061232581)]

class TestPage extends StatefulWidget {
  @override
  _TestSate createState() {
    // TODO: implement createState
    return _TestSate();
  }
}

class _TestSate extends State<TestPage> {
  int _tabIndex = 0;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(title: new Text("ZXZ-IMAGE")),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
      floatingActionButton: FloatingActionButton(backgroundColor: Colors.lightBlueAccent,child : Text("+",style: TextStyle(fontSize: 24),),onPressed: (){}),
      body: _PageList()[_tabIndex],
      bottomNavigationBar:  BottomNavigationBar(
          items: _list(),
          currentIndex: _tabIndex,
          onTap: (index) {
            setState(() {
              _tabIndex = index;
            });
          }),
    );
  }

  List<BottomNavigationBarItem> _list() {
    List<BottomNavigationBarItem> list = [];
    list.add(BottomNavigationBarItem(
        icon: Image.asset("assets/images/house.png"),
        title: Text("Construct")));
    list.add(BottomNavigationBarItem(
        icon: Image.asset("assets/images/plane.png"),
        title: Text("Attribute")));
    return list;
  }

  List<Widget> _PageList() {
    List<Widget> result = [];
    result.add(ImageConstructPage());
    result.add(ImageAttributePage());
    return result;
  }
} 

下拉按钮(DropdownButton)

Material Style下拉菜单按钮,使用方法如下:

class _DropdownState extends State<_DropdownWidget>{

  CityItem _item;
  List<DropdownMenuItem<CityItem>> _list;

  @override
  void initState() {
    _list = [];
    _list.add(new DropdownMenuItem<CityItem>(child: Text("上海"),value: CityItem("上海", 0)));
   _list.add(DropdownMenuItem<CityItem>(child: Text("北京",style: TextStyle(color: Colors.red),),value: CityItem("北京", 1)));
  }
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return DropdownButton(onChanged: (value) {
      setState(() {
        _item = value;
      });
    },items: _list,
    value: _item,
    hint: Text("请选择城市"),);
  }
 

由于我们在点击每一个条目后,展示的选中条目会变化,所以DropdownButton应当继承StatefulWidget,在选中条目后也就是onChange的回调中使用setState((){})更新对象的状态。另外,这里的列表数据必须是List<DropdownMenuItem<T>>。DropdownMenuItem<T>里面包含一个child和value,绑定了view和data,这里每个item都可以设置一个child,意味着下拉菜单的每个item可以有不同的view。这里需要注意的是下面两个属性,其他的属性比较简单或者已经再其他button说明过,就不再说明。

属性 作用
onChanged 菜单选中回调
items 要显示的列表

上面代码的结果如下:

[站外图片上传中...(image-75c22c-1562061232581)]

ButtonBar

可以水平摆放一堆button的控件,但我试了下,也可以摆text,不可滑动,超过屏幕宽度报错,感觉这更像一个水平布局,不明白为啥官方归纳到基础组件里面。使用简单,在此不举例了。

PS

  1. RaisedButton、FlatButton以及OutlineButton的minWidth=88dp,minHeight=36dp,且无法设置,只有通过Container包裹设置其宽度,如果需要设置其宽度自适应,可以使用MaterialButton,设置minWidth = 0。
  2. 如果要设置button的高度,可以使用Container包裹button,或者使用MaterialButton(这玩意儿有属性height)。
  3. 如果设置MaterialButton的onPressed为null,则此时button为空白。
  4. DropdownButton下拉菜单选中的值(注意:在初始化时,要么为null,这时显示默认hint的值;如果自己设定值,则值必须为列表中的一个值,如果不在列表中,会抛出异常)
  5. IconButton需要被包裹在Scaffold widget中,否则会报错 textfield widgets require a material widget ancestor
  6. 如果需要去掉点击效果且不禁用该控件,可以把splashColor和hightlightColor同时设置为transparent
  7. OutlineButton是一个有默认边线且背景透明的按钮,也就是说我们设置其边线和颜色是无效的,其他属性跟MaterialButton中属性基本一致

图片

Image, 图片显示Widget, 和Android ImageView相似,但是从实际使用的方法上看,与常用的图片加载库,如Picasso,Glide等相似,支持本地图片,资源图片,网络图片等加载方式。

  1. Image:通过ImageProvider来加载图片
  2. Image.asset:用来加载本地资源图片
  3. Image.file:用来加载本地(File文件)图片
  4. Image.network:用来加载网络图片
  5. Image.memory:用来加载Uint8List资源(byte数组)图片

属性

属性 作用
width & height 容器宽度高度
fit 图片填充方式
color & colorBlendMode 感觉类似Android Paint Xfermode
repeat 复制方式,桌面的图片平铺 (横向、纵向、横向纵向)
centerSlice 当图片需要被拉伸显示的时候,centerSlice定义的矩形区域会被拉伸(感觉没啥用)
matchTextDirection 与Directionality配合使用,图片展示方向(镜像)

fit

属性 作用
BoxFit.contain 全图显示,显示原比例,不需充满
BoxFit.fill 全图显示,显示可能拉伸,填满
BoxFit.cover 显示可能拉伸,可能裁剪,充满
BoxFit.fitWidth 显示可能拉伸,可能裁剪,宽度充满
BoxFit.fitHeight 显示可能拉伸,可能裁剪,高度充满

看图纸观点:

[站外图片上传中...(image-f400a9-1562061232581)]

在平时的开发中,基本上都是用上面两个,其他基本上用不到。

圆角图片

  1. 使用ClipOval,这里注意如果原图不是正方形,使用这个会成一个椭圆。
  Widget _AssetImageRec() {
    return Image.asset(
      "assets/images/food01.jpeg",
      width: 200,
      height: 200,
      fit: BoxFit.fill,
    );
  }
  
  Widget _CircleAvatar() {
    return new ClipOval(child: _AssetImageRec());
  } 
  1. 可以用Container包裹一个Image,然后设置Container的decoration
  2. 使用_ClipRRect,通过设置BorderRadius来实现。
  Widget _ClipRRect() {
    return ClipRRect(
      child: _AssetImage(),
      borderRadius: BorderRadius.all(Radius.circular(60)),
    );
  } 

网络缓存图片(占位图片)

  1. 使用FadeInImage.assetNetwork(flutter自带)

Widget _FadeInImage() {
    return FadeInImage.assetNetwork(
      placeholder: "assets/images/food01.jpeg",//占位图
      image://网络图片
          "https://rpic.douyucdn.cn/live-cover/roomCover/cover_update/2019/05/29/43cf32dcde0b77c765681c6f0a9af6fb.jpg",
      width: 200,
      height: 200,
      fit: BoxFit.fill,
      fadeInDuration: Duration(milliseconds: 400),
      fadeOutDuration: Duration(milliseconds: 400),
    );
  } 
  1. 使用第三方库:(cached_network_image:https://pub.dartlang.org/packages/cached_network_image

列表item实现

下图是斗鱼极速版中首页的item,基本上可以通过text和image来实现,这里会用到Stack来实现控件的覆盖,Column实现竖直线性布局,Row实现水平线性布局,这里不多做介绍,具体代码贴上:

[站外图片上传中...(image-5658f9-1562061232581)]

Widget _RoomItem(RoomItem item, double screenWidth) {
    return FlatButton(
      child: Column(children: <Widget>[
        _ImageItem(item, screenWidth),
        Container(
          child: Text(item.roomName,
              textAlign: TextAlign.left,
              softWrap: false,
              overflow: TextOverflow.ellipsis),
          margin: EdgeInsets.fromLTRB(0, 5, 0, 0),
        )
      ], crossAxisAlignment: CrossAxisAlignment.start),
      padding: EdgeInsets.zero,
      onPressed: () {},
    );
  }

  Widget _ImageItem(RoomItem item, double screenWidth) {
    double itemWidth = (screenWidth - 5) / 2;
    return Stack(children: <Widget>[
      Image.network(
        item.coverUrl,
        width: itemWidth,
        height: itemWidth * 3 / 5,
        fit: BoxFit.fill,
      ),
      Container(
        width: itemWidth,
        height: itemWidth * 3 / 5,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            _CornerWidget(),
            Expanded(
              child: Container(),
            ),
            Container(
                height: 29,
                width: itemWidth,
                alignment: AlignmentDirectional.bottomStart,
                decoration: BoxDecoration(
                    gradient: LinearGradient(
                        colors: [Colors.transparent, Color(0x7b000000)],
                        begin: Alignment.topCenter,
                        end: Alignment.bottomCenter)),
                child: Row(children: <Widget>[
                  Container(
                      padding: EdgeInsets.fromLTRB(4, 0, 0, 6),
                      width: 100,
                      child: Text(item.authorName,
                          style: TextStyle(color: Colors.white, fontSize: 11),
                          softWrap: false,
                          overflow: TextOverflow.ellipsis)),
                  Expanded(
                    child: Container(
                        padding: EdgeInsets.fromLTRB(0, 0, 6, 6),
                        child: Row(
                            textDirection: TextDirection.rtl,
                            children: <Widget>[
                              Text(item.hot,
                                  style: TextStyle(
                                      color: Colors.white, fontSize: 11),
                                  textAlign: TextAlign.right),
                              Container(
                                child: Image.asset(
                                    "assets/images/icon_room_hot.webp",
                                    fit: BoxFit.contain,
                                    width: 9,
                                    height: 9),
                                margin: EdgeInsets.fromLTRB(0, 0, 4, 0),
                              )
                            ])),
                  )
                ]))
          ],
        ),
      ),
    ]);
  } 

单选框、复选框

Material widgets库中提供了Material风格的单选开关Switch和复选框Checkbox,它们都是继承自StatelessWidget,所以它们本身不会保存当前选择状态,所以一般都是在父widget中管理选中状态。当用户点击Switch或Checkbox时,它们会触发onChanged回调,我们可以在此回调中处理选中状态改变逻辑。

公共属性

属性 作用
value 当前控件的值
activeColor 选中时控件颜色
onChanged 状态发生变化时回调,(bool value) {}

Switch(ios的那种滑动的单项选择器)

属性 作用
inactiveTrackColor 未选中时横条颜色
inactiveThumbColor 未选中时滑块颜色
inactiveThumbImage 未选中时滑块上的图片
activeThumbImage 选中时滑块上的图片
activeTrackColor 选中时横条颜色

Checkbox(类似于android的checkbox)

属性 作用
checkColor 选中后里面那个勾的颜色
tristate 会添加一个状态(true-null-false),在null时会有显示一个横条
inactiveTrackColor 选中时横条颜色

Radio(类似于android的RadioButton)

属性 作用
groupValue 和value一起控制是否为选中状态,当groupValue = value时代表选中状态
tristate 会添加一个状态(true-null-false),在null时会有显示一个横条
inactiveTrackColor 选中时横条颜色

直接代码:

 class TestPage extends StatefulWidget {
  @override
  _TestState createState() {
    // TODO: implement createState
    return _TestState();
  }
}

class _TestState extends State<TestPage> {
  bool _isSwitchChoosed = false;
  bool _isCheckboxChoosed = false;
  String _radioChoosed = null;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("ZXZ-CHOOSE"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: <Widget>[_SwitchWidget(), _CheckBox(), _Radio()],
      ),
    );
  }

  Widget _SwitchWidget() {
    return Switch(
        onChanged: (bool value) {
          setState(() {
            _isSwitchChoosed = value;
          });
        },
        value: _isSwitchChoosed,
        activeColor: Color(0xFFFF6633),
        inactiveThumbColor: Colors.white);
  }

  Widget _CheckBox() {
    return Checkbox(
      onChanged: (bool value) {
        setState(() {
          _isCheckboxChoosed = value;
        });
      },
      value: _isCheckboxChoosed,
      activeColor: Color(0xFFFF6633),
      tristate: true,
    );
  }

  Widget _Radio() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Radio<String>(
            value: "北京",
            groupValue: _radioChoosed,
            onChanged: (String s) {
              setState(() {
                this._radioChoosed = s;
              });
            },
        activeColor: Color(0xFFFF6633),),
        Radio<String>(
            value: "上海",
            groupValue: _radioChoosed,
            onChanged: (String s) {
              setState(() {
                this._radioChoosed = s;
              });
            }),
        Expanded(child: RadioListTile<String>(
            value: "广州",
            title: new Text("广州"),
            groupValue: _radioChoosed,
            onChanged: (String s) {
              setState(() {
                this._radioChoosed = s;
              });
            }),)
        ,
        Radio<String>(
            value: "深圳",
            groupValue: _radioChoosed,
            onChanged: (String s) {
              setState(() {
                this._radioChoosed = s;
              });
            })
      ],
    );
  }
}

效果如下:

[站外图片上传中...(image-e0ca8e-1562061232581)]

PS

  1. 还有个CheckboxListTile和RadioListTile,可以关注下。

进度条

LinearProgressIndicator & CircularProgressIndicator

Flutter使用LinearProgressIndicator表示条形进度条,CircularProgressIndicator表示圆形进度条,由于两个控件的属性基本相同,下面是几个比较重要的属性:

属性 作用
value 当前进度,如果 value 为 null 或空,进度条会是一个动画,很像一个loading动画,值只能设置 0 ~ 1.0,如果大于 1,则表示已经结束,显示背景颜色。
valueColor 进度条的颜色,是个颜色渐变的动画

slider(滑块)

滑块组件,可用于数量的选择,一般可用于调节变量,像音量啥的,以下是它的属性:

属性 说明
value 控件的位置,只能在min和max之间,否则报错
onChanged 变化时回调
onChangeStart 滑动开始时回调一次
onChangeEnd 滑动结束时回调一次
min 最小值,不设置默认为0
max 最大值,不设置默认为1
divisions 分为几块,比如设置为5,Slider只能滑动到5个位置
label divisions设置显示在节点上的label,配合divisions使用,不设置divisions则无效
activeColor 滑动过的区域的颜色
inactiveColor 未 滑动过的区域的颜色

直接代码:

  Widget _getCircularProgressIndicator() {
    return Container(
      margin: EdgeInsets.fromLTRB(0, 10, 0, 10),
      child: CircularProgressIndicator(
        value: null,
        backgroundColor: Colors.grey,
        strokeWidth: 2,
      ),
    );
  } 
class _SliderTest extends StatefulWidget {
  @override
  _SliderState createState() {
    return _SliderState();
  }
}

class _SliderState extends State<_SliderTest> {
  double _value = 0;
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      child: Slider(
          value: _value,
          onChanged: (double val) {
            setState(() {
              this._value = val;
            });
          },
      divisions: 5,
      label: "1",),
    );
  }
}

这里截图如下:

[站外图片上传中...(image-94d658-1562061232581)]

PS

  1. LinearProgressIndicator、CircularProgressIndicator、slider都具有状态的变化,一般都应放在StatefulWidget中
  2. LinearProgressIndicator、CircularProgressIndicator的value如果设置为null,会形成一个永不停止的动画,很有loading动画的风格,简单的loading可以用这个一试。

输入框

Flutter中的文本输入框(TextField)就类似于Android中的EditText,但是用起来比EditText方便很多,改变样式也更加的方便。

属性

下面看下TextField中特有的属性(之前其他控件已列过的属性不再列)

属性 作用
controller 编辑框的控制器,如果不创建的话默认会自动创建,用于和文本交互,例如清除文本
focusNode 用于管理焦点
decoration 输入框的装饰器,用来修改外观,返回InputDecoration
keyboardType 设置输入类型,不同的输入类型键盘不一样
textInputAction 用于控制键盘动作(一般位于右下角,默认是完成)
autofocus 是否自动获取焦点
obscureText 是否隐藏输入的文字,一般用在密码输入框中
autocorrect 是否自动校验
maxLength 能输入的最大字符个数,设置这个了右下角会冒出个计数器角标,且没法隐藏这个角标,略鸡肋
maxLengthEnforced 配合maxLength一起使用,在达到最大长度时是否阻止输入,默认为true
onEditingComplete 点击键盘完成按钮时触发的回调,该回调没有参数,(){}
onSubmitted 同样是点击键盘完成按钮时触发的回调,该回调有参数,参数即为当前输入框中的值。(String){}
inputFormatters 对输入文本的校验
cursorWidth 光标的宽度
cursorRadius 光标的圆角
cursorColor 光标的颜色
onTap 点击输入框时的回调(){}

TextEditingController

TextEditingController作为TextField的controller属性。 在用户输入时,controller的text和selection属性不断的更新。要在这些属性更改时得到通知,请使用controller的addListener方法监听控制器 。 (如果你添加了一个监听器,需要在State对象的dispose方法中删除监听器)。该TextEditingController还可以让您控制TextField的内容。最常见的是TextEditingController.text用来获取当前输入框的内容,TextEditingController.clear清空输入框的内容。

InputDecoration

InputDecoration主要用于控制TextField的外观以及提示信息等。主要可以设置前缀样式、后缀样式等,属性很多,这里借用下网上的劳动成果,具体如下:

[站外图片上传中...(image-234926-1562061232581)]

斗鱼顶部搜索框

斗鱼顶部的搜索框前部和后面均有一个图标,这里一开始想用InputDecoration.prefix和InputDecoration.suffix来实现,但是和输入框没法对齐,因此这里就没用这俩属性。

class _TextField extends State<_TextFieldWidget> {
  bool _hasInput = false;
  TextEditingController controller;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    controller = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    double statusBarHeight = MediaQuery.of(context).padding.top;
    // TODO: implement build
    return Container(
        width: MediaQuery.of(context).size.width,
        padding: EdgeInsets.fromLTRB(16, statusBarHeight, 0, 0),
        child: Row(children: <Widget>[
          Expanded(child: _search()),
          _cancelBtn(context)
        ]));
  }

  Widget _search() {
    return Container(
        color: Color(0xffF0F2F5),
        child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
          Container(
              padding: EdgeInsets.all(10),
              child: Image.asset(
                "assets/images/icon_home_search.webp",
                width: 24,
                height: 24,
              )),
          Expanded(
            child: TextField(
              controller: controller,
              cursorColor: Color(0xffff7700),
              cursorWidth: 1.5,
              onChanged: (text) {
                //内容改变的回调
                setState(() {
                  if (text.length > 0) {
                    _hasInput = true;
                  } else {
                    _hasInput = false;
                  }
                });
              },
              decoration: InputDecoration(
                counter: null,
                  contentPadding: EdgeInsets.fromLTRB(0, 10, 0, 10),
                  fillColor: Color(0xffF0F2F5),
                  filled: true,
                  hintText: "搜索房间/主播/分类",
                  hintStyle: TextStyle(fontSize: 14, color: Color(0xffbbbbbb)),
                  border: OutlineInputBorder(
                      borderSide: BorderSide(color: Color(0xffF0F2F5)),
                      borderRadius: BorderRadius.all(Radius.circular(2))),
                  focusedBorder: OutlineInputBorder(
                      borderSide: BorderSide(color: Color(0xffF0F2F5)),
                      borderRadius: BorderRadius.all(Radius.circular(2))),
                  enabledBorder: OutlineInputBorder(
                      borderSide: BorderSide(color: Color(0xffF0F2F5)),
                      borderRadius: BorderRadius.all(Radius.circular(2)))),
              style: TextStyle(
                fontSize: 14,
                color: Color(0xff333333),
              ),
            ),
          ),
          Container(
              padding: EdgeInsets.fromLTRB(0, 0, 12, 0),
              child: IconButton(
                  splashColor: Colors.transparent,
                  highlightColor: Colors.transparent,
                  icon: Image.asset(
                    _hasInput ? "assets/images/icon_search_clear.webp" : "",
                    width: 16,
                    height: 16,
                  ),
                  onPressed: () {
                    setState(() {
                      controller.clear();
                    });
                  })),
        ]));
  }

  Widget _cancelBtn(BuildContext context) {
    return MaterialButton(
        minWidth: 0,
        padding: EdgeInsets.all(0),
        onPressed: () {
          showDialog(
              context: context,
              builder: (_) => new LoginDialog(),
              barrierDismissible: false);
        },
        child: Text("取消",
            style: TextStyle(
                color: Color(0x54000000),
                fontSize: 14,
                fontWeight: FontWeight.bold)),
        elevation: null);
  }
} 

[站外图片上传中...(image-ca4c74-1562061232581)]

PS

  1. Row无法包裹TextField,需要在TextField外再包裹一层Expanded方可
  2. TextField需要被包裹在Scaffold widget中,否则会报错 textfield widgets require a material widget ancestor,后来发现用Material包裹也行
  3. TextField的prefixIcon和prefix不好用,prefixIcon限制了类型,prefix的对齐效果贼差,建议还是用Row包裹Image+TextField实现图标后面紧跟输入框的视图。
  4. TextField的最大限制输入maxLength设置之后导致右下角会冒出个计数器角标,而且消不掉,这就尴尬了,感觉只能在onChanged里面处理了。

对话框

SimpleDialog & AlertDialog & AboutDialog

  1. SimpleDialog,一般可以利用多个SimpleDialogOption为用户提供了几个选项。
  2. AlertDialog,警告对话框。警告对话框有一个可选标题title和一个可选列表的actions选项。
  3. AboutDialog,关于对话框。提供一个app信息的对话框

展示 & 隐藏

  1. 对话框本质上是属于一个路由的页面route,由Navigator进行管理,所以控制对话框的显示和隐藏,也是调用Navigator.of(context)的push和pop方法。
  2. 在Flutter中,提供了showDialog()、showGeneralDialog()、showCupertinoDialog(),其中调用showDialog()方法展示的是material风格的对话框,调用showCupertinoDialog()方法展示的是ios风格的对话框。而这两个方法其实都会去调用showGeneralDialog()方法,可以从源码中看到最后是利用Navigator.of(context, rootNavigator: true).push()一个页面。这里我在android上使用showCupertinoDialog(),出现的渐变动画会有一点卡顿,另外还有一个showAboutDialog(),打开的是一个关于的对话框。
 //打开关于对话框
 showAboutDialog(
        context: context,
        applicationIcon:
        Image.asset("assets/images/house.png"),
        applicationName: "flutter-douyu",
       applicationVersion: "1.0.0"); }) 
//打开一个对话框
void _showDialog(BuildContext context){
     showDialog(context: context, builder: (_) =>
        AlertDialog(title: Text("测试"), content: Text("测试测试测试测试测试"),
          actions: <Widget>[
            FlatButton(onPressed: () {
              Navigator.of(context).pop();//关闭对话框
            }, child: Text("确定")),
            FlatButton(onPressed: () {
              Navigator.of(context).pop();
            }, child: Text("取消")),
          ],));
  }

自定义对话框

自定义Dialog可以有两种方式:

  1. 在Dialog提供了child参数供写视图界面
  2. 继承Dialog类,就需要重写build函数。

斗鱼的登录界面就是个自定义对话框,具体视图如下图所示:

[站外图片上传中...(image-bcd28a-1562061232581)]
代码太长,不一一贴,主要代码如下:

//第一种方式
 void _showDialog1(BuildContext context) {
    showDialog(
        context: context,
        builder: (_) => Dialog(
            backgroundColor: Colors.transparent,
            child: Container(
              width: 311,
              height: 500,
              child: Center(
                  child: Stack(
                children: <Widget>[
                  Container(
                    margin: EdgeInsets.fromLTRB(0, 80, 0, 0),
                    padding: EdgeInsets.all(0),
                    decoration: BoxDecoration(
                        borderRadius: BorderRadius.all(Radius.circular(2)),
                        color: Colors.white),
                    width: 311,
                    height: 420,
                    child: _CloseImg(context),
                  ),
                  _LogoImg(),
                  _MainWidget()
                ],
              )),
            )));
  }
  //第二种方式
class LoginDialog extends Dialog {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
        backgroundColor: Colors.transparent,
        body: Center(
            child: Stack(
          children: <Widget>[
            Container(
              margin: EdgeInsets.fromLTRB(0, 80, 0, 0),
              padding: EdgeInsets.all(0),
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(2)),
                  color: Colors.white),
              width: 311,
              height: 420,
              child:  _CloseImg(context),//
            ),//底部那个大白框
            _LogoImg(),//上面那个鲨鱼娘
            _MainWidget()//最主要的操作面板
          ],
        )));
  }
}

PS

  1. 按照我们公司的UI以往的设计方案(坑爹尿性)来看,基本上用Flutter自带的Dialog样式固定,基本上没啥用了,还是自定义吧~~
  2. 继承Dialog类,重写build函数时会发现里面所有的Text控件会默认有个双下划波浪线,不知道为啥这么设置。。。

总结

  1. 不同于android的控件,Flutter的控件很少能够自己设置宽高,基本上都需要被包裹在Container里面才行。
  2. 使用本地文件,除了导入文件之外,还需要在项目根目录下的pubspec.yaml文件中的assets:目录下添加该文件
  3. 实际写布局的时候会发现层级很深,导致代码里的child一个接一个,建议最好不要超过三层,如果超过写在新的方法体内,否则极难阅读。
  4. 控件里的属性并不是不设置就没有,例如button里面的splashColor不设置会使用默认的,如果想不使用该属性,需要自己设置。
  5. 部分控件属性极多,不需要完全搞明白,弄明白重要的就行
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容