Flutter开发初体验

2023-02-28  本文已影响0人  Imkata

一. 创建Flutter项目

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

1.1. 通过命令行创建

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

flutter create learn_flutter

1.2. 通过开发工具创建

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

1.3. 默认程序分析

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

项目目录分析:

  1. .dart_tool:dart语法依赖的第三方库,不用管,不用动。
  2. .idea:Android Studio是谷歌基于IDEA开发的,所以有这个文件。
  3. android:安卓工程。
  4. ios:ios工程。
  5. lib:里面会存放我们编写的Flutter代码,打开发现里面有一个main.dart,它是我们Flutter启动的入口文件,里面有main函数
  6. test:一些测试的东西。
  7. .gitignore:gith忽略。
  8. .metadata:flutter版本记录,不用手动改。
  9. .packagespubspec.lockpubspec.yaml:三方库依赖相关。

默认代码分析:

作为初学者,我的建议是将其中所有的代码全部删除掉,从零去创建里面的代码,这样我们才能对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<String> args) {
  // runApp方法要求传入一个widget,Text也是继承于widget
  // 第一个参数是文字,必传,第二个参数是命名可选参数,表示方向
  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<String> args) {
  runApp(
    // 居中组件
    Center(
      child: Text(
        "Hello World",
        textDirection: TextDirection.ltr,
        style: TextStyle(
            fontSize: 36,
            color: Colors.white
        ),
      ),
    )
  );
}

2.3.2. 改进界面结构

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

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

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

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(
    // 使用Material设计风格
    MaterialApp(
      // Scaffold脚手架,帮助我们快速搭建界面
      home: Scaffold(
        // 导航条
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        // 内容
        body: Center(
          child: Text(
            "Hello World",
            textDirection: TextDirection.ltr,
            style: TextStyle(fontSize: 36),
          ),
        ),
      ),
    )
  );
}

在最外层包裹一个MaterialApp

Scaffold是什么呢?

注意

// 使用material的系统风格
import 'package:flutter/material.dart';
// 使用cupertino系统风格,这种风格和苹果更相似
// 但是我们都使用material的系统风格也没事,因为material更丰富
import 'package:flutter/cupertino.dart';

2.3.3. 进阶案例实现

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

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: Center(
          // 使用row,可以一行放多个元素 
          child: Row(
            // 主轴对齐方式:居中对齐
            mainAxisAlignment: MainAxisAlignment.center,
            // children是个数组
            children: <Widget>[
              Checkbox(
                value: true,
                onChanged: (value) => print("Hello World")),
              Text(
                "同意协议",
                textDirection: TextDirection.ltr,
                style: TextStyle(fontSize: 20),
              )
            ],
          ),
        ),
      ),
    )
  );
}

2.4. 代码重构

2.4.1. 创建自己的Widget

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

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

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

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

如何创建自己的Widget呢?

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

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

2.4.2. StatelessWidget

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

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

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

build方法的解析:

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

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

2.4.3. 重构案例代码

现在我们就可以通过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),
          )
        ],
      ),
    );
  }
}

上面的StatelessWidget是没有数据交互的,如果想要有状态,我们需要使用StatefulWidget。

只要是widget都是带有@immutable注解的,都是不能定义状态的,包括StatefulWidget,既然StatefulWidget不能定义状态,所以创建一个单独的类,这个类负责维护状态,如下:

import 'package:flutter/material.dart';

/**
 * Widget:
 * 有状态的Widget: StatefulWidget 在运行过程中有一些状态(data)需要改变
 * 无状态的Widget: StatelessWidget 内容是确定没有状态(data)的改变
 */
class MyApp extends StatelessWidget {
  // build
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        //去掉右上角的debug标识
        debugShowCheckedModeBanner: false,
        home: HYHomePage()
    );
  }
}

class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("HYHomePage Build");
    return Scaffold(
        appBar: AppBar(
          title: Text("第一个Flutter程序"),
        ),
        body: HYContentBody()
    );
  }
}

// 只要是widget都是带有@immutable注解的,都是不能定义状态的,包括StatefulWidget
// StatefulWidget不能定义状态,所以创建一个单独的类, 这个类负责维护状态
class HYContentBody extends StatefulWidget {
  @override
  //必须实现createState方法,返回这个状态类
  State<StatefulWidget> createState() {
    return HYContentBodyState();
  }
}

//这个状态类里面可以定义状态,然后需要实现build方法
class HYContentBodyState extends State<HYContentBody> {
  //定义状态
  var flag = true;

  @override
  //实现build方法
  Widget build(BuildContext context) {
    print("HYContentBodyState Build");
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Checkbox(
            value: flag,
            onChanged: (value) {
              //需要刷新数据的地方,必须通过调用setState来改变
              this.setState(() {
                flag = value;
              });
            },
          ),
          Text("同意协议", style: TextStyle(fontSize: 20))
        ],
      ),
    );
  }
}

这时候点击Checkbox就会在选中和未选中之间切换了。

三. 案例练习

3.1. 案例最终效果

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

3.2. 自定义Widget

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

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

// 在Widget里面定义的所有的成员变量都要是final的
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来让他们展示了:

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"),
      ],
    );
  }
}

运行效果如下:

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

3.4. 案例细节调整

3.4.1. 界面整体边距

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

3.4.2. 商品内边距和边框

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

3.4.3. 文字图片的间距

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

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) {
    // 这里补充一个快捷键:alt+enter可以选择包裹一个容器
    return Container(
      // 设置内边距
      padding: EdgeInsets.all(20),
      // 设置边框
      decoration: BoxDecoration(
          border: Border.all(
              width: 5, // 设置边框的宽度
              color: Colors.pink// 设置边框的颜色
          )
      ),
      child: Column(
        children: <Widget>[
          Text(title, style: TextStyle(fontSize: 24)),
          Text(desc, style: TextStyle(fontSize: 18)),
          // 垂直方向添加间距 
          SizedBox(height: 10,),
          Image.network(imageURL)
        ],
      ),
    );
  }
}
上一篇 下一篇

猜你喜欢

热点阅读