使用Flutter编写一个简单的天气查询App

Flutter项目目录分析

在这里插入图片描述

文件夹 作用
android 安卓工程相关代码
build 项目编译产生的目录
ios ios工程相关代码
lib flutter相关代码
test 用于放置测试代码
pubspec.yaml 配置文件,依赖等

我们首先看lib目录
在这里插入图片描述
lib目录下只有一个main.dart文件。

入口函数

里面有一个main方法我们称作为入口函数吧,其中void main() => runApp(MyApp()); 这是一种Dart语言特有的速写语法。

void main() => runApp(MyApp());
//等同于
void main() {
  return runApp(MyApp());
}

我们来看一下MyApp类:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

在Flutter框架中,很多内容都为Widget(控件),包括对齐(alignment)、填充(padding)和布局(layout)等,控件类又细分为有状态控件类(继承StatefulWidget抽象类)和无状态控件类(继承StatelessWidget抽象类),两者的差别在于是否有状态。

StatelessWidget 是不可变的,这意味着它们的属性不能改变–所有的值都是最终的。

StatefulWidget持有的状态可能在widget生命周期中发生变化。

实现一个StatefulWidget至少需要两个类:

  • 一个StatefulWidget类。
  • 一个State类。StatefulWidget类本身是不可变的,但是 State 类在widget生命周期中始终存在。

MyApp类继承于StatelessWidget,既无状态控件
重载build方法返回一个MaterialApp,MaterialApp继承自StatefulWidget既有状态控件,我们可以在上面定义主题 标题 主页 颜色等很多内容,它拥有以下参数

	this.navigatorKey, // 导航的key
    this.home, // 主页
    this.routes = const <String, WidgetBuilder>{},// 路由
    this.initialRoute,//初始路由
    this.onGenerateRoute,//生成路由
    this.onUnknownRoute,//位置路由
    this.navigatorObservers = const <NavigatorObserver>[],//导航的观察者
    this.builder,//widget的构建
    this.title = '',//设备用于识别用户的应用程序的单行描述。在Android上,标题显示在任务管理器的应用程序快照上方,当用户按下“最近的应用程序”按钮时会显示这些快照。 在iOS上,无法使用此值。 来自应用程序的`Info.plist`的`CFBundleDisplayName`在任何时候都会被引用,否则就会引用`CFBundleName`。要提供初始化的标题,可以用 onGenerateTitle。
    this.onGenerateTitle,//每次在WidgetsApp构建时都会重新生成
    this.color,//背景颜色
    this.theme,//主题,用ThemeData
    this.locale,//app语言支持
    this.localizationsDelegates,//多语言代理
    this.localeResolutionCallback,//
    this.supportedLocales = const <Locale>[Locale('en', 'US')],//支持的多语言
    this.debugShowMaterialGrid = false,//显示网格
    this.showPerformanceOverlay = false,//打开性能监控,覆盖在屏幕最上面
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,//打开一个覆盖图,显示框架报告的可访问性信息 显示边框
    this.debugShowCheckedModeBanner = true,//右上角显示一个debug的图标

home:主页面

进入程序后显示的第一个页面,传入的是一个Widget,但实际上这个Widget需要包裹一个Scaffold以显示该程序使用Material Design风格

我们看一下MyHomePage类,该类继承自StatefulWiget 并且重载了createState()方法

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

_MyHomePageState 类,该应用程序的大部分逻辑和状态管理代码都在该类中,在该实例中这个类保存了屏幕上用户点击的次数,随着用户的点击,次数不断增加,且展示在界面上,代码如下:


class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}


编写天气应用

接下来我们动手更改并编写天气应用Demo。
Demo效果图如下:
在这里插入图片描述
接口:https://www.tianqiapi.com/api/?version=v1
新建weather.dart文件创建Weather

class Weather{
  Weather();
  String cityName;//城市名字
  String updateTime;//更新时间
  List<DataBean> dataBean = new List();//天气数据
}
class DataBean{
  DataBean();
  String day;//日期
  String week;//周期
  String wea;//天气
  int air;//空气纯净指数
  String airLevel;//空气纯净度
  String airTips;
  String tem;//当前温度
  String tem1;//最高温度
  String tem2;//最低温度
}

修改pubspec.yaml内容添加http请求依赖:

http: ^0.11.0 

在这里插入图片描述
Terminal输入flutter packages get 执行获取packages操作

网络请求

将网络请求库import到main.dart文件中,因为需要操作json所以也要import 'dart:convert'

import 'package:http/http.dart' as http;
import 'dart:convert';

在_MyHomePageState类的initState方法中执行网络请求
initState()这个方法在生命周期中只调用一次。这里可以做一些初始化工作,比如初始化State的变量等。

//这里面的cityid我填写的是南京的,如过未填写cityid那么将通过IP获取地理位置来返回该IP地址的天气信息
 String url = "https://www.tianqiapi.com/api/?version=v1&cityid=101190101";
 @override
  void initState() {
    super.initState();
    getData();
  }
  void getData() async {
    http.get(url).then((http.Response response) {
      var jsonData = json.decode(response.body);
      print(jsonData);
    });
  }

getData()方法中使用了 async 关键字,用于告诉 Dart 它是异步方法,http.get() 前面的 await 关键字则表明这是阻塞调用。

打包运行,查看控制台输出的信息。
在这里插入图片描述

数据解析

void getData() async {
    http.get(url).then((http.Response response) {
      var jsonData = json.decode(response.body);
      print(jsonData["city"]);
    });
  }

打包运行,查看控制台输出的信息。
在这里插入图片描述
在mian.dart文件中import我们新建的weather.dart文件

import 'weather.dart';

编写相应的取值和赋值的代码。

  void getData() async {
    http.get(url).then((http.Response response) {
      var jsonData = json.decode(response.body);
      weather.cityName = jsonData["city"];
      weather.updateTime = jsonData["update_time"];
      List data = new List();
      data = jsonData["data"];
      for (int i = 0; i < data.length; i++) {
        DataBean dataBean = new DataBean();
        dataBean.air = data[i]["air"];
        dataBean.airLevel = data[i]["air_level"];
        dataBean.airTips = data[i]["air_tips"];
        dataBean.day = data[i]["day"];
        dataBean.week = data[i]["week"];
        dataBean.tem = data[i]["tem"];
        dataBean.tem1 = data[i]["tem1"];
        dataBean.tem2 = data[i]["tem2"];
        dataBean.wea = data[i]["wea"];
        weather.dataBean.add(dataBean);
      }
      setState(() {
      });
    });
  }

布局编写

在编写应用前,我们了解一下Flutter里基础的Widget

Flutter里基础的Widget

Flutter有一套丰富、强大的基础widget,其中以下是很常用并且本次实例有用到的widget:
Text:该 widget 可让创建一个带格式的文本类似于TextView。
RowColumn: 这些具有弹性空间的布局类Widget可让我们在水平(Row)和垂直(Column)方向上创建灵活的布局。其设计是基于web开发中的Flexbox布局模型。暂时可以把这两当作LinearLayout 里的horizontal与vertical。
Stack: 叠层布局(类似于安卓里面的帧布局)。
Container: Container 可让我们创建矩形视觉元素。container 可以装饰为一个BoxDecoration, 如 背景、边框、或者阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。
Image: 该 widget 可让创建一个图片控件,类似于安卓里面的ImageView,可以设置本地资源图片、网络图片链接等。

大概了解完毕后,我们分析一下布局:
在这里插入图片描述
此页面布局我们主要分为三个部分,
显示数据的父布局用Stack(帧布局),要用Image实现背景图片。
我们还要定义一个在数据获取状态时显示的布局,我们写一个_loading方法返回widget,在Scaffold的body中通过三元表达式判断并设置body,在数据为空时用_loading()的布局,在有数据时用Stack
由于该页面数据需要滑动所以用滑动控件SingleChildScrollView主要我们数据显示的控件放在此控件的子控件(child)之中。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: weather.dataBean.length == 0
          ? _loading()
          : Stack(
              children: <Widget>[
                Image.network(
                  "http://pic.netbian.com/uploads/allimg/190510/221228-15574975489aa1.jpg",
                  fit: BoxFit.fill,
                  height: double.infinity,
                ),
                SingleChildScrollView(
                  child: body(),
                )
              ],
            ),
    );
  }
  
  Widget _loading() {
    return new Center(
      child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new CircularProgressIndicator(
              strokeWidth: 5.0,
            ),
            new Container(
              margin: EdgeInsets.only(top: 10.0),
              child: new Text(
                "正在加载..",
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
            ),
          ]),
    );
  }

  Widget body() {
    return Container(
      width: double.infinity,//宽度占满
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          AppBar(
            centerTitle: true,//标题居中显示
            elevation: 0,//阴影为0
            title: Text("${weather.cityName}"),//获取城市名字
            backgroundColor: Colors.transparent,//背景颜色透明
            actions: <Widget>[
              Container(
                padding: EdgeInsets.only(right: 10),//右边padding为10
                alignment: Alignment.center,
                child: Text(
                  DateTime.now().hour.toString() +
                      ":" +
                      DateTime.now().minute.toString(),//获取时间并显示
                  textAlign: TextAlign.center,
                ),
              )
            ],
          ),
          topView(),//上
          centerView(),//中
          bottomView(),//下
          Padding(
            padding: EdgeInsets.only(bottom: 20, top: 10),
            child: Text(
              "API来源为:https://www.tianqiapi.com",
              style: TextStyle(color: textColor),//文字颜色白色
            ),
          )
        ],
      ),
    );
  }


一个Container和一横向布局Row
我们写一个topView方法并且返回Widget
Container中我们可以设置背景颜色,padding和margin

Widget topView() {
    return Container(
      padding: EdgeInsets.all(10),
      margin: EdgeInsets.all(10),
      color: containerColor,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Text(
            weather.dataBean[0].tem,
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 25,
                color: textColor,
              )),
          Text(
            weather.dataBean[0].wea,
            style: TextStyle(
              fontWeight: FontWeight.bold,
              fontSize: 25,
              color: textColor,
            ),
          )
        ],
      ),
    );
  }

Container添加子布局ColumnColumn子布局树添加RowTextView
我们写一个centerView方法并且返回Widget

Widget centerView() {
    TextStyle textStyle = TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 25,
                  color: textColor,
                );
    return Container(
      padding: EdgeInsets.all(10),
      margin: EdgeInsets.all(10),
      color: containerColor,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text("空气质量",
              style: textStyle
              ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              Text("${weather.dataBean[0].air}",
                  style: textStyle
                  ),
              Text(
                "${weather.dataBean[0].airLevel}",
                style: textStyle,
              )
            ],
          ),
          Text(
            "${weather.dataBean[0].airTips}",
            style: TextStyle(color: textColor),
          )
        ],
      ),
    );
  }

Container添加子布局ColumnColumn子布局树添加Row
我们写一个bottomView方法并且返回Widget,并且写一个_listView方法返回Widget树。
listView需要item,在这里我们把Row当作list的item,写一个itemView方法

  Widget bottomView() {
    return Container(
      padding: EdgeInsets.all(10),
      margin: EdgeInsets.all(10),
      color: containerColor,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text("预报",
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 25,
                color: textColor,
              )),
          Column(
            children: _listView(),
          )
        ],
      ),
    );
  }
  
List<Widget> _listView() {
    List<Widget> widgets = new List();
    for (int i = 0; i < weather.dataBean.length; i++) {
      widgets.add(itemView(weather.dataBean[i]));
    }
    return widgets;
  }

  List<Widget> _listView() {
    List<Widget> widgets = new List();
    for (int i = 0; i < weather.dataBean.length; i++) {
      widgets.add(itemView(weather.dataBean[i]));
    }
    return widgets;
  }

  Widget itemView(DataBean data) {
    return Container(
      padding: EdgeInsets.only(top: 10, bottom: 10),
      width: double.infinity,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Text("${data.day}",
              style: TextStyle(
                fontWeight: FontWeight.bold,
                color: textColor,
              )),
          Text(
            "${data.wea}",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: textColor,
            ),
          ),
          Row(
            children: <Widget>[
              Text(
            "${data.tem1}",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: textColor,
            ),
          ),
          Text(
            "/",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: textColor,
            ),
          ),
          Text(
            "${data.tem2}",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: textColor,
            ),
          ),
            ],
          )
        ],
      ),
    );
  }

运行
在这里插入图片描述

全部代码:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'weather.dart';

void main() {
  return runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '天气',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: '天气'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Weather weather = new Weather();
  Color textColor = Colors.white;
  Color containerColor = Colors.black38;
  String url = "https://www.tianqiapi.com/api/?version=v1&cityid=101190101";
  @override
  void initState() {
    super.initState();
    getData();
  }

  void getData() async {
    http.get(url).then((http.Response response) {
      var jsonData = json.decode(response.body);
      weather.cityName = jsonData["city"];
      weather.updateTime = jsonData["update_time"];
      List data = new List();
      data = jsonData["data"];
      for (int i = 0; i < data.length; i++) {
        DataBean dataBean = new DataBean();
        dataBean.air = data[i]["air"];
        dataBean.airLevel = data[i]["air_level"];
        dataBean.airTips = data[i]["air_tips"];
        dataBean.day = data[i]["day"];
        dataBean.week = data[i]["week"];
        dataBean.tem = data[i]["tem"];
        dataBean.tem1 = data[i]["tem1"];
        dataBean.tem2 = data[i]["tem2"];
        dataBean.wea = data[i]["wea"];
        weather.dataBean.add(dataBean);
      }
      setState(() {
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: weather.dataBean.length == 0
          ? _loading(context)
          : Stack(
              children: <Widget>[
                Image.network(
                  "http://pic.netbian.com/uploads/allimg/190510/221228-15574975489aa1.jpg",
                  fit: BoxFit.fill,
                  height: double.infinity,
                ),
                SingleChildScrollView(
                  child: body(),
                )
              ],
            ),
    );
  }

  Widget body() {
    return Container(
      width: double.infinity,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          AppBar(
            centerTitle: true,
            elevation: 0,
            title: Text("${weather.cityName}"),
            backgroundColor: Colors.transparent,
            actions: <Widget>[
              Container(
                padding: EdgeInsets.only(right: 10),
                alignment: Alignment.center,
                child: Text(
                  DateTime.now().hour.toString() +
                      ":" +
                      DateTime.now().minute.toString(),
                  textAlign: TextAlign.center,
                ),
              )
            ],
          ),
          topView(),
          centerView(),
          bottomView(),
          Padding(
            padding: EdgeInsets.only(bottom: 20, top: 10),
            child: Text(
              "API来源为:https://www.tianqiapi.com",
              style: TextStyle(color: textColor),
            ),
          )
        ],
      ),
    );
  }

  Widget topView() {
    return Container(
      padding: EdgeInsets.all(10),
      margin: EdgeInsets.all(10),
      color: containerColor,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Text(
            weather.dataBean[0].tem,
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 25,
                color: textColor,
              )),
          Text(
            weather.dataBean[0].wea,
            style: TextStyle(
              fontWeight: FontWeight.bold,
              fontSize: 25,
              color: textColor,
            ),
          )
        ],
      ),
    );
  }

  Widget centerView() {
    TextStyle textStyle = TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 25,
                  color: textColor,
                );
    return Container(
      padding: EdgeInsets.all(10),
      margin: EdgeInsets.all(10),
      color: containerColor,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text("空气质量",
              style: textStyle
              ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              Text("${weather.dataBean[0].air}",
                  style: textStyle
                  ),
              Text(
                "${weather.dataBean[0].airLevel}",
                style: textStyle,
              )
            ],
          ),
          Text(
            "${weather.dataBean[0].airTips}",
            style: TextStyle(color: textColor),
          )
        ],
      ),
    );
  }

  Widget bottomView() {
    return Container(
      padding: EdgeInsets.all(10),
      margin: EdgeInsets.all(10),
      color: containerColor,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text("预报",
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 25,
                color: textColor,
              )),
          Column(
            children: _listView(),
          )
        ],
      ),
    );
  }

  List<Widget> _listView() {
    List<Widget> widgets = new List();
    for (int i = 0; i < weather.dataBean.length; i++) {
      widgets.add(itemView(weather.dataBean[i]));
    }
    return widgets;
  }

  Widget itemView(DataBean data) {
    return Container(
      padding: EdgeInsets.only(top: 10, bottom: 10),
      width: double.infinity,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Text("${data.day}",
              style: TextStyle(
                fontWeight: FontWeight.bold,
                color: textColor,
              )),
          Text(
            "${data.wea}",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: textColor,
            ),
          ),
          Row(
            children: <Widget>[
              Text(
            "${data.tem1}",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: textColor,
            ),
          ),
          Text(
            "/",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: textColor,
            ),
          ),
          Text(
            "${data.tem2}",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: textColor,
            ),
          ),
            ],
          )
        ],
      ),
    );
  }

  Widget _loading(context) {
    return new Center(
      child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new CircularProgressIndicator(
              strokeWidth: 5.0,
            ),
            new Container(
              margin: EdgeInsets.only(top: 10.0),
              child: new Text(
                "正在加载..",
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
            ),
          ]),
    );
  }
}
发布了13 篇原创文章 · 获赞 24 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/sinat_28502161/article/details/90545164
今日推荐