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。
Row
、Column
: 这些具有弹性空间的布局类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
添加子布局Column
,Column
子布局树添加Row
和TextView
。
我们写一个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
添加子布局Column
,Column
子布局树添加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,
),
debugShowCheckedModeBanner: false,
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://tianqiapi.com/api?version=v1&appid=45325487&appsecret=u8L2WsAb";
@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() {
var date = DateTime.now();
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(
date.hour.toString() + ":" + date.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),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(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),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(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),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(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),
),
),
]),
);
}
}