Flutter学习指南:UI布局和控件(1),大专生三面蚂蚁金服


关于 build 方法的实现,在后面我们学习具体的控件时读者就会了解的,这里暂时忽略掉。

StatefulWidget 用起来麻烦一些,他还需要一个 State:

class BarWidget extends StatefulWidget {
  @override
  State createState() {
    return _BarWidgetState();
  }
}

class _BarWidgetState extends State {
  @override
  Widget build(BuildContext context) {
    // …
  }
}


这里看起来可能有些绕,BarWidget 依赖了 \_BarWidgetState,而 \_BarWidgetState 又继承了 State< BarWidget>。如果读者不太理解,其实也没有什么关系,这只是一个样板代码,照着写就行了。

从 BarWidget 的实现来看,好像跟前面使用 StatelessWidget 没有什么区别,都是在 build 方法里面返回一个 Widget,只是 stateful widget 把这个方法挪到了 State 里面。实际上,两者的区别非常大。stateless widget 整个生命周期里都不会改变,所以 build 方法只会执行一次。而 stateful widget 只要状态改变,就会调用 build 方法重新创建 UI。

为了触发 UI 的重建,我们可以调用 setState 方法。下面的代码读者留意一下即可,在后面我们学习了相关的控件后再回过头来看。

class BarWidget extends StatefulWidget {
  @override
  State createState() {
    return _BarWidgetState();
  }
}

class _BarWidgetState extends State {
  var i = 0;

@override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text(‘i = $i’),
        RaisedButton(
          onPressed: () {
            setState(() {
              ++i;
            });
          },
          child: Text(‘click’),
        )
      ],
    );
  }
}


下面我们开始学习一些具体的控件。

文本
--

为了展示文本,我们使用 Text:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(“Put your text here”);
  }
}


这就是最简单的文本了,它使用的是默认的样式。很多情况下,我们都需要对文本的样式进行修改,这个时候,可以使用 TextStyle:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      “Put your text here”,
      style: TextStyle(
        color: Colors.blue,
        fontSize: 16.0,
        fontWeight: FontWeight.bold
      ),
    );
  }
}


图片
--

使用 Image,可以让我们向用户展示一张图片。图片的来源可以是网络、文件、资源和内存,它们对应的构造函数分别是:

Image.asset(name);
Image.file(file);
Image.memory(bytes);
Image.network(src);


比方说,为了展示一张来自网络的图片,我们可以这样:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Image.network(
      “http://www.example.com/xxx.png”,
      width: 200.0,
      height: 150.0,
    );
  }
}


按钮
--

Flutter 提供了两个基本的按钮控件:FlatButton 和 RaisedButton,它们的使用方法是类似的:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var flatBtn = FlatButton(
      onPressed: () => print(‘FlatButton pressed’),
      child: Text(‘BUTTON’),
    );
    var raisedButton = RaisedButton(
      onPressed: () => print(‘RaisedButton pressed’),
      child: Text(‘BUTTON’),
    );
    return raisedButton;
  }
}


通过设置 onPressed 回调,我们可以在按钮被点击的时候得到回调。child 参数用于设置按钮的内容。虽然我们给 child 传递的是 Text,但这不是必需的,它可以接受任意的 Widget,比方说,Image。

注意,由于我们只是在按钮点击的时候打印一个字符串,这里使用 StatelessWidget 是没有问题的。但如果有其他 UI 动作(比如弹出一个 dialog,则必须使用 StatefulWidget)。

它们的区别只是样式不同而已的:

FlatButton:  

![flat-button](https://user-gold-cdn.xitu.io/2018/10/28/166b92eae5b2045e?imageView2/0/w/1280/h/960/ignore-error/1 "flat-button")

flat-button

RaiseButton:  

![raised-button](https://user-gold-cdn.xitu.io/2018/10/28/166b92eae5be6d28?imageView2/0/w/1280/h/960/ignore-error/1 "raised-button")

raised-button

文本输入框
-----

Flutter 的文本输入框叫 TextField。为了获取用户输入的文本,我们需要给他设置一个 controller。通过这个 controller,就可以拿到文本框里的内容:

class MessageForm extends StatefulWidget {
  @override
  State createState() {
    return _MessageFormState();
  }
}

class _MessageFormState extends State {
  var editController = TextEditingController();

@override
  Widget build(BuildContext context) {
    // Row、Expand 都是用于布局的控件,这里可以先忽略它们
    return Row(
      children: [
        Expanded(
          child: TextField(
            controller: editController,
          ),
        ),
        RaisedButton(
          child: Text(“click”),
          onPressed: () => print(‘text inputted: ${editController.text}’),
        )
      ],
    );
  }

@override
  void dispose() {
    super.dispose();
    // 手动调用 controller 的 dispose 方法以释放资源
    editController.dispose();
  }
}


显示弹框
----

在前面的 TextField 例子中,我们只是把用户的输入通过 print 打印出来,这未免也太无趣了。在这一小节,我们要把它显示在 dialog 里。为了弹出一个 dialog,我们需要调用 showDialog 方法并传递一个 builder:

class _MessageFormState extends State {
  var editController = TextEditingController();

@override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: TextField(
            controller: editController,
          ),
        ),
        RaisedButton(
          child: Text(“click”),
          onPressed: () {
            showDialog(
                // 第一个 context 是参数名,第二个 context 是 State 的成员变量
                context: context,
                builder: (_) {
                  return AlertDialog(
                    // dialog 的内容
                    content: Text(editController.text),
                    // actions 设置 dialog 的按钮
                    actions: [
                      FlatButton(
                        child: Text(‘OK’),
                        // 用户点击按钮后,关闭弹框
                        onPressed: () => Navigator.pop(context),
                      )
                    ],
                  );
                }
            );
          }
        )
      ],
    );
  }

@override
  void dispose() {
    super.dispose();
    editController.dispose();
  }
}


最简单的布局——Container、Padding 和 Center:
-----------------------------------

我们经常说,Flutter 里面所有的东西都是 Widget,所以,布局也是 Widget。

控件 Container 可以让我们设置一个控件的尺寸、背景、margin 等:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(‘text’),
      padding: EdgeInsets.all(8.0),
      margin: EdgeInsets.all(4.0),
      width: 80.0,
      decoration: BoxDecoration(
        // 背景色
        color: Colors.grey,
        // 圆角
        borderRadius: BorderRadius.circular(5.0),
      ),
    );
  }
}


如果我们只需要 padding,可以使用控件 Padding:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(8.0),
      child: Text(‘text’),
    );
  }
}


Center 就跟它的名字一样,把一个控件放在中间:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(8.0),
      margin: EdgeInsets.all(4.0),
      width: 200.0,
      height: 200.0,
      decoration: BoxDecoration(
        // 背景色
        color: Colors.grey,
        // 圆角
        borderRadius: BorderRadius.circular(5.0),
      ),

// 把文本放在 Container 的中间
      child: Center(
        child: Text(‘text’),
      ),
    );
  }
}


水平、竖直布局和 Expand
---------------

我们经常说,Flutter 里面所有的东西都是 Widget,所以,布局也是 Widget。水平布局我们可以使用 Row,竖直布局使用 Column。

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      // 只有一个子元素的 widget,一般使用 child 参数来设置;Row 可以包含多个子控件,
      // 对应的则是 children。
      children: [
        Text(‘text1’),
        Text(‘text2’),
        Text(‘text3’),
        Text(‘text4’),
      ],
    );
  }
}


Column 的使用是一样的:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(‘text1’),
        Text(‘text2’),
        Text(‘text3’),
        Text(‘text4’),
      ],
    );
  }
}


关于 Expand 控件,我们来看看 TextField 的那个例子:

class MessageForm extends StatefulWidget {
  @override
  State createState() {
    return _MessageFormState();
  }
}

class _MessageFormState extends State {
  var editController = TextEditingController();

@override
  Widget build(BuildContext context) {
    return Row(
      children: [
        // 占满一行里除 RaisedButton 外的所有空间
        Expanded(
          child: TextField(
            controller: editController,
          ),
        ),
        RaisedButton(
          child: Text(“click”),
          onPressed: () => print(‘text inputted: ${editController.text}’),
        )
      ],
    );
  }

@override
  void dispose() {
    super.dispose();
    editController.dispose();
  }
}


这里通过使用 Expand,TextField 才能够占满一行里除按钮外的所有空间。此外,当一行/列里有多个 Expand 时,我们还可以通过设置它的 flex 参数,在多个 Expand 之间按比例划分可用空间。

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          // 占一行的 2/3
          flex: 2,
          child: RaisedButton(child: Text(‘btn1’),),
        ),
        Expanded(
          // 占一行的 1/3
          flex: 1,
          child: RaisedButton(child: Text(‘btn2’),),
        ),
      ],
    );
  }
}


Stack 布局
--------

有些时候,我们可能会希望一个控件叠在另一个控件的上面。于是,Stack 应运而生:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Text(‘foobar’),
        Text(‘barfoo’),
      ],
    );
  }
}


默认情况下,子控件都按 Stack 的左上角对齐,于是,上面的两个文本完全一上一下堆叠在一起。我们还可以通过设置 alignment 参数来改变这个对齐的位置:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      // Aligment 的取值范围为 [-1, 1],Stack 中心为 (0, 0),
      // 这里设置为 (-0.5, -0.5) 后,可以让文本对齐到 Container 的 1/4 处
      alignment: const Alignment(-0.5, -0.5),
      children: [
        Container(
          width: 200.0,
          height: 200.0,
          color: Colors.blue,
        ),
        Text(‘foobar’),
      ],
    );
  }
}


效果如下:  

![screenshot-stack](https://user-gold-cdn.xitu.io/2018/10/28/166b92eae5aca111?imageView2/0/w/1280/h/960/ignore-error/1 "screenshot-stack")

screenshot-stack

通过组合 Row/Column 和 Stack,已经能够完成绝大部分的布局了,所以 Flutter 里没有相对布局之类的东西。更多的 Flutter 控件,读者可以参考 [flutter.io/widgets/](
)。

示例一
===

在这一节里,我们综合前面所学的知识,来实现下面这个界面。

![lakes-diagram](https://user-gold-cdn.xitu.io/2018/10/28/166b92eae5da0368?imageView2/0/w/1280/h/960/ignore-error/1 "lakes-diagram")

lakes-diagram

展示图片
----

1.  把图片 [lake](
) 放到项目根目录的 images 文件夹下(如果没有,你需要自己创建一个)
    
2.  修改 pubspec.yaml,找到下面这个地方,然后把图片加进来
    
    ```
    flutter:  
      
      # The following line ensures that the Material Icons font is  
      # included with your application, so that you can use the icons in  
      # the material Icons class.  
      uses-material-design: true  
      
      # To add assets to your application, add an assets section, like this:  
      # assets:  
      #  - images/a_dot_burr.jpeg  
      #  - images/a_dot_ham.jpeg  
    
    ```
    
    修改后如下:
    
    ```
    flutter:  
      
      # The following line ensures that the Material Icons font is  
      # included with your application, so that you can use the icons in  
      # the material Icons class.  
      uses-material-design: true  
      
      # To add assets to your application, add an assets section, like this:  
      assets:  
        - images/lake.jpg  
    
    ```
3.  现在,我们可以把这张图片展示出来了:
    
    ```
    void main() {  
      runApp(MyApp());  
    }  
      
    class MyApp extends StatelessWidget {  
      @override  
      Widget build(BuildContext context) {  
        return MaterialApp(  
          title: 'Flutter UI basic 1',  
          home: Scaffold(  
            appBar: AppBar(  
              title: Text('Top Lakes'),  
            ),  
            body: Image.asset(  
              'images/lake.jpg',  
              width: 600.0,  
              height: 240.0,  
              // cover 类似于 Android 开发中的 centerCrop,其他一些类型,读者可以查看  
              // https://docs.flutter.io/flutter/painting/BoxFit-class.html  
              fit: BoxFit.cover,  
            )  
          ),  
        );  
      }  
    }  
    
    ```

如果读者是初学 Flutter,**强烈建议**在遇到不熟悉的 API 时翻一翻文档,并在文档中找到 demo 所使用的 API。我们的例子不可能覆盖所有的 API,通过这种方式熟悉文档后,读者就可以根据文档实现出自己想要的效果。不妨就从 Image 开始吧,在 [docs.flutter.io/flutter/wid…](
) 找出上面我们使用的 Image.asset 构造函数的几个参数的含义,还有 BoxFit 的其他几个枚举值。

布局
--

在这一小节,我们来实现图片下方的标题区域。

![](https://user-gold-cdn.xitu.io/2018/10/28/166b92eae5322cf1?imageView2/0/w/1280/h/960/ignore-error/1)

我们直接来看代码:

class _TitleSection extends StatelessWidget {
  final String title;
  final String subtitle;
  final int starCount;

_TitleSection(this.title, this.subtitle, this.starCount);

@override
  Widget build(BuildContext context) {
    // 为了给 title section 加上 padding,这里我们给内容套一个 Container
    return Container(
      // 设置上下左右的 padding 都是 32。类似的还有 EdgeInsets.only/symmetric 等
      padding: EdgeInsets.all(32.0),
      child: Row(
        children: [
          // 这里为了让标题占满屏幕宽度的剩余空间,用 Expanded 把标题包了起来
          Expanded(
            // 再次提醒读者,Expanded 只能包含一个子元素,使用的参数名是 child。接下来,
            // 为了在竖直方向放两个标题,加入一个 Column。
            child: Column(
              // Column 是竖直方向的,cross 为交叉的意思,也就是说,这里设置的是水平方向
              // 的对齐。在水平方向,我们让文本对齐到 start(读者可以修改为 end 看看效果)
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // 聪明的你,这个时候肯定知道为什么突然加入一个 Container 了。
                // 跟前面一样,只是为了设置一个 padding
                Container(
                  padding: const EdgeInsets.only(bottom: 8.0),
                  child: Text(
                    title,
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                ),
                Text(
                  subtitle,
                  style: TextStyle(color: Colors.grey[500]),
                )
              ],
            ),
          ),

// 这里是 Row 的第二个子元素,下面这两个就没用太多值得说的东西了。
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),

Text(starCount.toString())
        ],
      ),
    );
  }
}


对齐
--

接下来我们要做的这一部分在布局上所用到的知识,基本知识在上一小节我们都已经学习了。这里唯一的区别在于,三个按钮是水平分布的。

![](https://user-gold-cdn.xitu.io/2018/10/28/166b92eae5e50aec?imageView2/0/w/1280/h/960/ignore-error/1)

实现如下:

Widget _buildButtonColumn(BuildContext context, IconData icon, String label) {
  final color = Theme.of(context).primaryColor;

return Column(
    // main axis 跟我们前面提到的 cross axis 相对应,对 Column 来说,指的就是竖直方向。
    // 在放置完子控件后,屏幕上可能还会有一些剩余的空间(free space),min 表示尽量少占用
    // free space;类似于 Android 的 wrap_content。
    // 对应的,还有 MainAxisSize.max
    mainAxisSize: MainAxisSize.min,
    // 沿着 main axis 居中放置
    mainAxisAlignment: MainAxisAlignment.center,

children: [
      Icon(icon, color: color),
      Container(
        margin: const EdgeInsets.only(top: 8.0),
        child: Text(
          label,
          style: TextStyle(
            fontSize: 12.0,
            fontWeight: FontWeight.w400,
            color: color,
          ),
        ),
      )
    ],
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //…

Widget buttonSection = Container(
      child: Row(
        // 沿水平方向平均放置
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          _buildButtonColumn(context, Icons.call, ‘CALL’),
          _buildButtonColumn(context, Icons.near_me, ‘ROUTE’),
          _buildButtonColumn(context, Icons.share, ‘SHARE’),
        ],
      ),
    );
  //…
}


关于 cross/main axis,看看下面这两个图就很清楚了:  

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MkA4gZMp-1630664501303)(https://user-gold-cdn.xitu.io/2018/10/28/166b92eb48d8f8fd?imageView2/0/w/1280/h/960/ignore-error/1)]

  

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CvY8wsLY-1630664501305)(https://user-gold-cdn.xitu.io/2018/10/28/166b92eb6a61e061?imageView2/0/w/1280/h/960/ignore-error/1)]

MainAxisAlignment 的更多的信息,可以查看 [docs.flutter.io/flutter/ren…](
)。

全部放到一起
## 学习福利

**【Android 详细知识点思维脑图(技能树)】**

> ![](https://img-blog.csdnimg.cn/img_convert/9a8e18a528690121d888c4de0a339b6f.png)

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,**现在高级工程师还是比较缺少的**,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

> 这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

![](https://img-blog.csdnimg.cn/img_convert/889ca467172eaf566f5a8cc26ba894e4.png)

**[CodeChina开源项目:《Android学习PDF+架构视频+面试文档+源码笔记》](https://codechina.csdn.net/m0_60958482/android_p7)**

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。



cs.flutter.io/flutter/ren…](
)。

全部放到一起
## 学习福利

**【Android 详细知识点思维脑图(技能树)】**

> [外链图片转存中...(img-8pypyzfc-1630664501307)]

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,**现在高级工程师还是比较缺少的**,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

> 这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中...(img-DlB8YCj3-1630664501309)]

**[CodeChina开源项目:《Android学习PDF+架构视频+面试文档+源码笔记》](https://codechina.csdn.net/m0_60958482/android_p7)**

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值