姬長信(Redy)

Flutter(一)之Flutter开发初体验


一. 创建Flutter项目

创建Flutter项目有两种方式:通过命令行创建 和 通过开发工具创建

1.1. 通过命令行创建

通过命令行创建非常简单,在终端输入以下命令即可:

flutter create learn_flutter

1.2. 通过开发工具创建

我这里也可以直接通过Android Studio来进行创建:

1.3. 默认程序分析

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

默认项目分析:

默认代码分析:

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

二. 开始Flutter代码

2.1. Hello World

2.1.1. Hello World的需求

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

2.1.2. Hello World的实现

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

import 'package:flutter/material.dart';

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

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

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

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

2.2. 代码分析

2.2.1. runApp和Widget

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

void runApp(Widget app) {
  ...省略代码
}

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

我们先说Widget的翻译

Widget到底什么东西呢?

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

2.2.2. Material设计风格

material是什么呢?

Text小部件分析

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简单介绍:

abstract class StatelessWidget extends Widget {
// ...省略代码
}

2.3. 代码改进

2.3.1. 改进界面样式

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

我们修改代码如下:

import 'package:flutter/material.dart';

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

2.3.2. 改进界面结构

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

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

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

import 'package:flutter/material.dart';

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

在最外层包裹一个MaterialApp

Scaffold是什么呢?

2.3.3. 进阶案例实现

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

import 'package:flutter/material.dart';

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

2.4. 代码重构

2.4.1. 创建自己的Widget

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

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

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

如何创建自己的Widget呢?

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

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

2.4.2. StatelessWidget

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

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

class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ;
  }
}

build方法的解析:

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

2.4.3. 重构案例代码

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

重构后的代码如下:

import 'package:flutter/material.dart';

main(List 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: [
          Checkbox(
              value: true,
              onChanged: (value) => print("Hello World")),
          Text(
            "同意协议",
            textDirection: TextDirection.ltr,
            style: TextStyle(fontSize: 20),
          )
        ],
      ),
    );
  }
}

三. 案例练习

3.1. 案例最终效果

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

3.2. 自定义Widget

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

另外,三个展示的标题、描述、图片都是不一样的,所以我们可以让Parent 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: [
        Text(title, style: TextStyle(fontSize: 24)),
        Text(desc, style: TextStyle(fontSize: 18)),
        Image.network(imageURL)
      ],
    );
  }
}

3.3. 列表数据展示

现在我们就可以创建三个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: [
        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"),
      ],
    );
  }
}

运行效果如下:

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

3.4. 案例细节调整

3.4.1. 界面整体边距

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

3.4.2. 商品内边距和边框

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

3.4.3. 文字图片的间距

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

3.5. 最终实现代码

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

import 'package:flutter/material.dart';
main(List 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: [
          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: [
          Text(title, style: TextStyle(fontSize: 24)),
          Text(desc, style: TextStyle(fontSize: 18)),
          SizedBox(height: 10,),
          Image.network(imageURL)
        ],
      ),
    );
  }
}