Flutter(六)之Flutter开发初体验

前言一:接下来一段时间我会陆续更新一些列Flutter文字教程前端

更新进度: 每周至少两篇;vue

更新地点: 首发于公众号,次日更新于掘金、思否、开发者头条等地方;算法

更多交流: 能够添加个人微信 372623326,关注个人微博:coderwhyshell

但愿你们能够 帮忙转发,点击在看,给我更多的创做动力。bash

一. 建立Flutter项目

建立Flutter项目有两种方式:经过命令行建立经过开发工具建立微信

1.1. 经过命令行建立

经过命令行建立很是简单,在终端输入如下命令便可:数据结构

  • **注意:**Flutter的名称不要包含特殊的字符,另外不可使用驼峰标识
  • 建立完以后使用本身喜欢的开发工具打开便可
flutter create learn_flutter
复制代码

image-20190915164546394

1.2. 经过开发工具建立

我这里也能够直接经过Android Studio来进行建立:app

  • 选择Start a new Flutter project,以后填写相关的信息便可,这里再也不赘述

image-20190901200434719

1.3. 默认程序分析

咱们讲建立的应用起来跑在模拟器上(我这里选择iPhone模拟器,Android也能够),会看到以下效果:less

image-20190901200718627

默认项目分析:ide

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

默认代码分析:

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

做为初学者,个人建议是将其中全部的代码所有删除掉,从零去建立里面的代码,这样咱们才能对Flutter应用程序的结构很是清晰;

二. 开始Flutter代码

2.1. Hello World

2.1.1. Hello World的需求

作任何的开发,咱们都是从祖传的Hello World开始,那么如今咱们的需求来了:

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

2.1.2. Hello World的实现

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

import 'package:flutter/material.dart';

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

image-20190916212127281

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

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

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

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

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

2.2. 代码分析

2.2.1. runApp和Widget

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

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

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

咱们先说Widget的翻译

  • Widget在国内有不少的翻译;
  • 作过Android、iOS等开发的人群,喜欢将它翻译成控件
  • 作过Vue、React等开发的人群,喜欢将它翻译成组件
  • 若是咱们使用Google,Widget翻译过来应该是小部件
  • 没有说哪一种翻译必定是对的,或者必定是错的,可是我我的更倾向于小部件或者组件

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.2. Material设计风格

material是什么呢?

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

Text小部件分析

  • 咱们可使用Text小部件来完成文字的显示;
  • 咱们发现Text小部件继承自StatelessWidget,StatelessWidget继承自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 {
	// ...省略代码
}
复制代码

2.3. 代码改进

2.3.1. 改进界面样式

咱们发现如今的代码并非咱们想要的最终结果:

  • 咱们可能但愿文字居中显示,而且能够大一些;
  • 居中显示: 须要使用另一个Widget,Center
  • 文字大一些: 须要给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: 36),
      ),
    )
  );
}
复制代码

image-20190916215857058

2.3.2. 改进界面结构

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

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

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

咱们经过下面的代码来实现:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: Center(
          child: Text(
            "Hello World",
            textDirection: TextDirection.ltr,
            style: TextStyle(fontSize: 36),
          ),
        ),
      ),
    )
  );
}
复制代码

image-20190916221014543

在最外层包裹一个MaterialApp

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

Scaffold是什么呢?

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

2.3.3. 进阶案例实现

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

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

main(List<String> args) {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Checkbox(
                value: true,
                onChanged: (value) => print("Hello World")),
              Text(
                "赞成协议",
                textDirection: TextDirection.ltr,
                style: TextStyle(fontSize: 20),
              )
            ],
          ),
        ),
      ),
    )
  );
}
复制代码

image-20190916221925380

2.4. 代码重构

2.4.1. 建立本身的Widget

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

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

  • 一、Flutter整个开发过程当中就是造成一个Widget树,因此造成嵌套是很正常的。
  • 二、关于Flutter的代码缩进,更多开发中咱们使用的是2个空格(前端开发2个空格居多,你喜欢4个也没问题)

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

  • 咱们能够对咱们的代码进行封装,将它们封装到本身的Widget中,建立本身的Widget;

如何建立本身的Widget呢?

  • 在Flutter开发中,咱们能够继承自StatelessWidget或者StatefulWidget来建立本身的Widget类;
  • StatelessWidget: 没有状态改变的Widget,一般这种Widget仅仅是作一些展现工做而已;
  • StatefulWidget: 须要保存状态,而且可能出现状态改变的Widget;

在上面的案例中对代码的重构,咱们使用StatelessWidget便可,因此咱们接下来学习一下若是利用StatelessWidget来对咱们的代码进行重构;

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

2.4.2. StatelessWidget

StatelessWidget一般是一些没有状态(State,也能够理解成data)须要维护的Widget:

  • 它们的数据一般是直接写死(放在Widget中的数据,必须被定义为final,为何呢?我在下一个章节讲解StatefulWidget会讲到);
  • 从parent widget中传入的并且一旦传入就不能够修改;
  • 从InheritedWidget获取来使用的数据(这个放到后面会讲解);

咱们来看一下建立一个StatelessWidget的格式:

  • 一、让本身建立的Widget继承自StatelessWidget;
  • 二、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方法什么状况下被执行呢?:

  • 一、当咱们的StatelessWidget第一次被插入到Widget树中时(也就是第一次被建立时);
  • 二、当咱们的父Widget(parent widget)发生改变时,子Widget会被从新构建;
  • 三、若是咱们的Widget依赖InheritedWidget的一些数据,InheritedWidget数据发生改变时;

2.4.3. 重构案例代码

如今咱们就能够经过StatelessWidget来对咱们的代码进行重构了

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

重构后的代码以下:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: HomeContent(),
      ),
    )
  }
}

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Checkbox(
              value: true,
              onChanged: (value) => print("Hello World")),
          Text(
            "赞成协议",
            textDirection: TextDirection.ltr,
            style: TextStyle(fontSize: 20),
          )
        ],
      ),
    );
  }
}
复制代码

image-20190917091913208

三. 案例练习

3.1. 案例最终效果

咱们先来看一下案例的最终展现效果:

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

image-20190917151554241

3.2. 自定义Widget

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

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

另外,三个展现的标题、描述、图片都是不同的,因此咱们可让Parent Widget来决定内容:

  • 建立三个成员变量保存父Widget传入的数据
class ProductItem extends StatelessWidget {
  final String title;
  final String desc;
  final String imageURL;

  ProductItem(this.title, this.desc, this.imageURL);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text(title, style: TextStyle(fontSize: 24)),
        Text(desc, style: TextStyle(fontSize: 18)),
        Image.network(imageURL)
      ],
    );
  }
}
复制代码

3.3. 列表数据展现

如今咱们就能够建立三个ProductItem来让他们展现了:

  • MyApp和上一个章节是一致的,没有任何改变;
  • HomeContent中,咱们使用了一个Column,由于咱们建立的三个ProductItem是垂直排列的
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.blueAccent
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: HomeContent(),
      ),
    );
  }
}

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

复制代码

运行效果以下:

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

image-20190917153343504

如何能够解决这个问题呢?

  • 咱们将Column换成ListView便可;
  • ListView可让本身的子Widget变成滚动的效果;

image-20190917153558093

3.4. 案例细节调整

3.4.1. 界面总体边距

若是咱们但愿整个内容距离屏幕的边缘有必定的间距,怎么作呢?

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

image-20190917153942819

3.4.2. 商品内边距和边框

咱们如今但愿给全部的商品也添加一个内边距,而且还有边框,怎么作呢?

  • 咱们可使用一个Container的Widget,它里面有padding属性,而且能够经过decoration来设置边框;
  • Container咱们也会在后面详细来说,咱们先用起来;

image-20190917154359364

3.4.3. 文字图片的间距

咱们但愿给图片和文字之间添加一些间距,怎么作呢?

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

image-20190917154630828

3.5. 最终实现代码

最后,我给出最终实现代码:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.blueAccent
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: HomeContent(),
      ),
    );
  }
}

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: ListView(
        children: <Widget>[
          ProductItem("Apple1", "Macbook Product1", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
          ProductItem("Apple2", "Macbook Product2", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
          ProductItem("Apple3", "Macbook Product3", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),
        ],
      ),
    );
  }
}

class ProductItem extends StatelessWidget {
  final String title;
  final String desc;
  final String imageURL;

  ProductItem(this.title, this.desc, this.imageURL);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(20),
      decoration: BoxDecoration(
        border: Border.all()
      ),
      child: Column(
        children: <Widget>[
          Text(title, style: TextStyle(fontSize: 24)),
          Text(desc, style: TextStyle(fontSize: 18)),
          SizedBox(height: 10,),
          Image.network(imageURL)
        ],
      ),
    );
  }
}

复制代码

备注:全部内容首发于公众号,以后除了Flutter也会更新其余技术文章,TypeScript、React、Node、uniapp、mpvue、数据结构与算法等等,也会更新一些本身的学习心得等,欢迎你们关注

公众号
相关文章
相关标签/搜索