likes
comments
collection
share

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

作者站长头像
站长
· 阅读数 23

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

一、前言

本系列文章旨在快速复习并上手Flutter开发,并在适当分享在项目实战过程中遇到的一些比较有价值的知识内容:

本系列文章内容篇幅如下:

  • 一、了解Flutter开发
      1. Flutter的特性与应用场景
      1. Flutter绘制原理
      1. 与Flutter相关的技术原理
      1. 搭建Flutter开发环境
      1. 创建Flutter项目的几种方式
  • 二、快速入门Flutter开发知识大纲
      1. Dart语言快速入门
      1. Flutter的Widget
  • 三、常见应用功能模块与开源项目
      1. 常见应用功能模块
      1. 不错的开源项目

二、创建Flutter项目

1. 新建一个Flutter项目

flutter create learn_flutter

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

2. 默认程序分析

我们将创建的应用起来跑在模拟器上,会看到如下效果: 04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

默认项目分析:

  • 我们之前已经分析过目录结构了,在目录下有一个lib文件夹,里面会存放我们编写的Flutter代码;
  • 打开发现里面有一个main.dart,它是我们Flutter启动的入口文件,里面有main函数

默认代码分析:

  • 这是一个计数器的案例程序,点击右下角的 + 符号,上面显示的数字会递增;
  • 但是我们第一次接触main.dart中的代码,可能会发现很多不认识的代码,不知道这个内容是如何编写出来的;

作为初学者,我的建议是将其中所有的代码全部删除掉,从零去创建里面的代码,这样我们才能对Flutter应用程序的结构非常清晰;

二. 开始Flutter代码

1.Hello World

1.1 显示Hello World

做任何的开发,我们都是从祖传的Hello World开始,那么现在我们的需求来了:

  • 在界面中心位置,显示一个Hello World;

1.2 Hello World的实现

下面,我们就动手开始编写Hello World:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(Text("Hello World", textDirection: TextDirection.ltr));
}

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

当然,上面的代码我们已经实现了在界面上显示Hello World:

  • 但是没有居中字体也有点小
  • 这些问题,我们放到后面再来解决,先搞懂目前的几行代码;

上面的代码我们有一些比较熟悉,有一些并不清楚是什么:

  • 比如我们知道Dart程序的入口都是main函数,而Flutter是Dart编写的,所以入口也是main函数;
  • 但是我们导入的Material是什么呢
  • 另外,我们在main函数中调用了一个runApp()函数又是什么呢?

下面,我们对不认识的代码进行一些分析。

2. 代码分析

2.1. runApp和Widget

runAppFlutter内部提供的一个函数,当我们启动一个Flutter应用程序时就是从调用这个函数开始的

  • 我们可以点到runApp的源码,查看到该函数
  • 我们暂时不分析具体的源码(因为我发现过多的理论,对于初学者来说并不友好)
void runApp(Widget app) {
  ...省略代码
}

该函数让我们传入一个东西:Widget

我们先说Widget的翻译

  • Widget在国内有很多的翻译;
  • 做过AndroidiOS等开发的人群,喜欢将它翻译成控件
  • 做过VueReact等开发的人群,喜欢将它翻译成组件
  • 如果我们使用GoogleWidget翻译过来应该是小部件
  • 没有说哪种翻译一定是对的,或者一定是错的,但是我个人更倾向于小部件或者组件

Widget到底什么东西呢?

  • 我们学习 Flutter,从一开始就可以有一个基本的认识:Flutter中万物皆Widget(万物皆可盘)
  • 在我们 iOS 或者 Android 开发中,我们的界面有很多种类的划分:
    • 应用(Application
    • 视图控制器(View Controller
    • 活动(Activity
    • View(视图)
    • Button(按钮)
    • ...
  • 但是在 Flutter 中,这些东西都是不同的Widget而已
  • 也就是我们整个应用程序中所看到的内容几乎都是 Widget ,甚至是内边距的设置,我们也需要使用一个叫Padding的Widget来做;

runApp 函数让我们传入的就是一个 Widget

  • 但是我们现在没有 Widget,怎么办呢?
  • 我们可以导入 Flutter 默认已经给我们提供的 Material 库,来使用其中的很多内置 Widget

2.2. Material设计风格

material是什么呢?

  • material是Google公司推行的一套设计风格,或者叫设计语言设计规范等;
  • 里面有非常多的设计规范,比如颜色文字的排版响应动画与过度填充等等;
  • 在Flutter中高度集成了Material风格的Widget
  • 在我们的应用中,我们可以直接使用这些Widget来创建我们的应用(后面会用到很多);

Text小部件分析

  • 我们可以使用Text小部件来完成文字的显示;
  • 我们发现Text小部件继承自StatelessWidgetStatelessWidget继承自Widget
  • 所以我们可以将Text小部件传入到runApp函数中
  • 属性非常多,但是我们已经学习了Dart语法,所以你会发现只有this.data属性是必须传入的。
class Text extends StatelessWidget {
  const Text(
    this.data, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
  });
}

StatelessWidget简单介绍:

  • StatelessWidget 继承自 Widget
  • 后面再介绍更详细的用法;
    abstract class StatelessWidget extends Widget {
         // ...省略代码
    }
    

3. 代码改进

3.1. 改进界面样式

我们发现现在的代码并不是我们想要的最终结果:

  • 我们可能希望文字居中显示,并且可以大一些;
  • 居中显示: 需要使用另外一个 WidgetCenter
  • 文字大一些: 需要给 Text 文本设置一些样式;

我们修改代码如下:

  • 我们在 Text 小部件外层包装了一个 Center 部件,让 Text 作为其 child
  • 并且,我们给 Text 组件设置了一个属性:style,对应的值是 TextStyle 类型;
import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(
    Center(
      child: Text(
        "Hello World",
        textDirection: TextDirection.ltr,
        style: TextStyle(fontSize: 37),
      ),
    )
  );
}

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

3.2. 改进界面结构

目前我们虽然可以显示HelloWorld,但是我们发现最底部的背景是黑色,并且我们的页面并不够结构化。

  • 正常的App页面应该有一定的结构,比如通常都会有导航栏,会有一些背景颜色

在开发当中,我们并不需要从零去搭建这种结构化的界面,我们可以使用Material库,直接使用其中的一些封装好的组件来完成一些结构的搭建。

我们通过下面的代码来实现:

import 'package:flutter/material.dart';

main(List<String> args){
    runApp(
         MaterialApp(
            home: Scaffold(
               appBar: AppBar(
                     title: const Text("我的导航栏"),
                     backgroundColor: Colors.deepOrange,
                ),
               body: const Center(
                 child: Text(
                     "Hello World",
                      textDirection: TextDirection.ltr,
                      style: TextStyle(
                          fontSize: 37,
                          color: Colors.deepPurple
                      ),
                 ),
               ),
            ),
        )
    );
}

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

在最外层包裹一个MaterialApp

  • 这意味着整个应用我们都会采用MaterialApp风格的一些东西,方便我们对应用的设计,并且目前我们使用了其中两个属性;
    • title:这个是定义在Android系统中打开多任务切换窗口时显示的标题;(暂时可以不写)
    • home:是该应用启动时显示的页面,我们传入了一个Scaffold

Scaffold是什么呢?

  • 翻译过来是脚手架,脚手架的作用就是搭建页面的基本结构;
  • 所以我们给MaterialApphome属性传入了一个Scaffold对象,作为启动显示的 Widget
  • Scaffold 也有一些属性,比如appBarbody
  • appBar 是用于设计导航栏的,我们传入了一个title属性
  • body 是页面的内容部分,我们传入了之前已经创建好的 Center 中包裹的一个 Text 的Widget;

3.3. 进阶案例实现

我们可以让界面中存在更多的元素:

  • 写到这里的时候,你可能已经发现嵌套太多了,不要着急,我们后面会对代码重构的
import 'package:flutter/material.dart';

main(List<String> args){
    runApp(
         MaterialApp(
            home: Scaffold(
                appBar: AppBar(
                     title: const Text("我的导航栏"),
                     backgroundColor: Colors.deepOrange,
                ),
               body: Center(
                 child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Checkbox(
                          value: true,
                          onChanged: (newValue) =>  print("Hello world")
                      ),
                      const Text(
                        "同意协议",
                        textDirection: TextDirection.ltr,
                        style: TextStyle(
                            fontSize: 15,
                            color: Colors.deepPurple
                        ),
                      ),
                    ],
                 )
               ),
            ),
        )
    );
}

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

4. 代码重构

4.1. 创建自己的Widget

很多学习 Flutter 的人,都会被Flutter的嵌套劝退,当代码嵌套过多时,结构很容易看不清晰。

这里有两点我先说明一下:

    1. Flutter 整个开发过程中就是形成一个Widget树,所以形成嵌套是很正常的。
    1. 关于 Flutter 的代码缩进,更多开发中我们使用的是2个空格(前端开发2个空格居多,你喜欢4个也没问题)

但是,我们开发一个这么简单的程序就出现如此多的嵌套,如果应用程序更复杂呢?

  • 我们可以对我们的代码进行封装,将它们封装到自己的 Widget 中,创建自己的 Widget

如何创建自己的 Widget 呢?

  • Flutter 开发中,我们可以继承自 StatelessWidget 或者 StatefulWidget 来创建自己的Widget类;
  • StatelessWidget: 没有状态改变的Widget,通常这种Widget仅仅是做一些展示工作而已;
  • StatefulWidget: 需要保存状态,并且可能出现状态改变的Widget;

在上面的案例中对代码的重构,我们使用StatelessWidget即可,所以我们接下来学习一下如果利用StatelessWidget来对我们的代码进行重构;

StatefulWidget我们放到后面的一个案例中来学习;

4.2. StatelessWidget

StatelessWidget 通常是一些没有状态(State,也可以理解成data)需要维护的Widget:

  • 它们的数据通常是直接写死(放在Widget中的数据,必须被定义为final,为什么呢?我们在下一篇文章来分享StatefulWidget会讲到);
  • parent widget 中传入的而且一旦传入就不可以修改;
  • InheritedWidget 获取来使用的数据(这个放到后面会讲解);

我们来看一下创建一个 StatelessWidget 的格式:

  • 1、让自己创建的 Widget 继承自 StatelessWidget
  • 2、StatelessWidget 包含一个必须重写的方法: build 方法;
    class MyStatelessWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return <返回我们的Widget要渲染的Widget,比如一个Text Widget>;
      }
    }
    

build方法的解析:

  • Flutter 在拿到我们自己创建的 StatelessWidget 时,就会执行它的 build 方法;
  • 我们需要在 build 方法中告诉 Flutter,我们的 Widget 希望渲染什么元素,比如一个 Text Widget
  • StatelessWidget 没办法主动去执行 build 方法,当我们使用的数据发生改变时,build 方法会被重新执行;

build方法什么情况下被执行呢?:

  • 1、当我们的 StatelessWidget 第一次被插入到 Widget 树中时(也就是第一次被创建时);
  • 2、当我们的父 Widget(parent widget)发生改变时,子Widget会被重新构建;
  • 3、如果我们的 Widget 依赖 InheritedWidget 的一些数据,InheritedWidget 数据发生改变时;

4.3. 重构案例代码

现在我们就可以通过StatelessWidget来对我们的代码进行重构了

  • 因为我们的整个代码都是一些数据展示,没有数据的改变,使用StatelessWidget即可;
  • 另外,为了体现更好的封装性,我对代码进行了两层的拆分,让代码结构看起来更加清晰;(具体的拆分方式,我会在后面的案例中不断的体现出来,目前我们先拆分两层)

重构后的代码如下:

import 'package:flutter/material.dart';
main(List<String> args){
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("我的导航栏"),
          backgroundColor: Colors.deepOrange,
        ),
        body: HomeContent()
      ),
    );
  }
}

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Checkbox(
                value: true,
                onChanged: (newValue) =>  print("Hello world")
            ),
            const Text(
              "Hello World",
              textDirection: TextDirection.ltr,
              style: TextStyle(
                  fontSize: 25,
                  color: Colors.green,
              ),
            ),
          ],
        )
    );
  }
}

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

三. 案例练习

1. 案例最终效果

我们先来看一下案例的最终展示效果:

  • 这个效果中我们会使用很多没有接触的Widget;
  • 没有关系,后面这些常用的Widget我会一个个讲解;
  • 这个案例最主要的目的还是让大家更加熟悉Flutter的开发模式以及自定义Widget的封装过程;

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

2.自定义Widget

在我们的案例中,很明显一个产品的展示就是一个大的Widget,这个Widget包含如下Widget:

  • 标题的Widget:使用一个Text Widget完成;
  • 描述的Widget:使用一个Text Widget完成;
  • 图片的Widget:使用一个Image Widget完成;
  • 上面三个Widget要垂直排列,我们可以使用一个Column的Widget(上一个章节中我们使用了一次Row是水平排列的)

另外,三个展示的标题、描述、图片都是不一样的,所以我们可以让Parent Widget来决定内容:

  • 创建三个成员变量保存父Widget传入的数据
class HomeProductItem extends StatelessWidget {
  final String title;
  final String desc;
  final String imageURL;
  // 初始化
  HomeProductItem(this.title,this.desc,this.imageURL);
  @override
  Widget build(BuildContext context) {
      return Column(
          children: <Widget>[
            Text(title,style: TextStyle(fontSize: 20)),
            Text(desc,style: TextStyle(fontSize: 13)),
            Image.network(imageURL)
          ],
      );
  }
}

3. 列表数据展示

现在我们就可以创建三个ProductItem来让他们展示了:

  • MyApp 和上一个章节是一致的,没有任何改变;
  • HomeContent 中,我们使用了一个 Column,因为我们创建的三个 ProductItem 是垂直排列的
import 'package:flutter/material.dart';
// 用箭头函数代替
void main() => runApp(MyApp());


class MyApp extends StatelessWidget {
    @override
  Widget build(BuildContext context) {
      return MaterialApp(
            home: Scaffold(
                appBar: AppBar(
                  title: Text("首页"),
                  backgroundColor: Colors.deepPurple,
                ),
                body: HomeContet(),
            )

      );
  }
}

class HomeContet extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
      return Column(
        children: <Widget>[
          HomeProductItem("Apple1", "Macbook Product1", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
          HomeProductItem("Apple2", "Macbook Product2", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
          HomeProductItem("Apple3", "Macbook Product3", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),
        ],
      );
  }
}

运行效果如下:

  • 错误信息:下面出现了黄色的斑马线;
  • 这是因为在 Flutter 的布局中,内容是不能超出屏幕范围的,当超出时不会自动变成滚动效果,而是会报下面的错误;

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

如何可以解决这个问题呢?

  • 我们将Column换成ListView即可;
  • ListView可以让自己的子Widget变成滚动的效果;

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

4. 案例细节调整

4.1 界面整体边距

如果我们希望整个内容距离屏幕的边缘有一定的间距,怎么做呢?

  • 我们需要使用另外一个 WidgetPadding,它有一个padding属性用于设置边距大小;
  • 没错,设置内边距也是使用Widget,这个Widget就是 Padding

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

4.2 商品内边距和边框

我们现在希望给所有的商品也添加一个内边距,并且还有边框,怎么做呢?

  • 我们可以使用一个 ContainerWidget,它里面有 padding 属性,并且可以通过 decoration 来设置边框;
  • Container 我们也会在后面详细来讲,我们先用起来;

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

4.3. 文字图片的间距

我们希望给图片和文字之间添加一些间距,怎么做呢?

  • 方式一:给图片或者文字添加一个向上的内边距或者向下的内边距;
  • 方式二:使用 SizedBoxWidget,设置一个 height 属性,可以增加一些距离;

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

5. 最终实现代码

04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】

import 'package:flutter/material.dart';
// 用箭头函数代替
void main() => runApp(MyApp());


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text("首页"),
            backgroundColor: Colors.deepPurple,
          ),
          body: HomeContet(),
        )

    );
  }
}

class HomeContet extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        HomeProductItem("Apple1", "Macbook Product1", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
        HomeProductItem("Apple2", "Macbook Product2", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
        HomeProductItem("Apple3", "Macbook Product3", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),
      ],
    );
  }
}

class HomeProductItem extends StatelessWidget {
  final String title;
  final String desc;
  final String imageURL;
  // 初始化
  HomeProductItem(this.title,this.desc,this.imageURL);
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(20),//设置间距
      decoration: BoxDecoration(//社会盒子的装饰
          border: Border.all(
              color: Colors.cyan
          )
      ),
      child: Column(
        children: <Widget>[
          Text(title,style: TextStyle(fontSize: 20)),
          Text(desc,style: TextStyle(fontSize: 13)),
          SizedBox(height: 10,),
          Image.network(imageURL)
        ],
      ),
    );
  }
}