该项目是一个Flutter项目,适合新手,运用了很多的常见组件和布局。

项目特点就是简洁,好理解,运用第三方API实现天气的查询。
适用范围:
1.用于完成学校Flutter作业的朋友。
2.需要一个Flutter项目来学习技术的朋友。
3.想要写一个天气查询软件但是自己不想写界面的朋友,可以再此之上继续自己添加内容。
4.觉得Dart语言难用不想写界面的朋友。

这个app相当简单只有五个界面。
首先是欢迎界面

void main() {runApp(MyApp());
}class MyApp extends StatefulWidget{@overrideState<StatefulWidget> createState() {return _MyApp();}
}class _MyApp extends State<MyApp>{@overrideWidget build(BuildContext context) {// TODO: implement buildreturn MaterialApp(debugShowCheckedModeBanner: false,title: "天气app",//theme: ThemeData.dark(),home: WelcomePage());}}
class WelcomePage extends StatefulWidget{@overrideState<StatefulWidget> createState() {// TODO: implement createStatereturn _WelcomePage();}
}class _WelcomePage extends State<WelcomePage>{@overrideWidget build(BuildContext context) {void getLocationData() async {var weatherData = await WeatherModel().getLocationWeather();Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context){return AppHome(locationWeather: weatherData,);}), (route) => false);}// TODO: implement buildFuture.delayed(Duration(seconds: 2),(){getLocationData();});return Scaffold(body: Container(alignment: Alignment.center,child: Column(children: <Widget>[Expanded(flex: 1,child: Text("")),Expanded(flex: 1,child: Column(children: [Image(image: AssetImage("assets/images/welcome.png")),Text("Welcome To Weather App",style: TextStyle(fontSize: 26,color: Colors.blue,fontStyle: FontStyle.italic))],)),],)),);}}

加载欢迎页面两秒后,调用聚合数据的api请求天气数据。

请求网络之前自定义一个工具类

class NetworkHelper{NetworkHelper(this.url);final String url;Future getData() async{try{http.Response response = await http.get(url);if(response.statusCode==200){String data = response.body;return jsonDecode(data);}else{print(response.statusCode);return;}} catch(e){return "empty";}}
}

接口类

// const apiKey = 'a1229a6169b9ca8fa751980e7917fae5';
const openWeatherMapURL = 'http://v.juhe.cn/weather/geo';
const openCityWeatherMapURL = 'http://v.juhe.cn/weather/index';
class WeatherModel {//http://v.juhe.cn/weather/index?format=2&cityname=%E8%8B%8F%E5%B7%9E&key=您申请的KEYFuture<dynamic> getCityWeather(String cityName) async{NetworkHelper networkHelper = NetworkHelper('$openCityWeatherMapURL?format=1&key=$apiKey&cityname=$cityName&dtype=json');var weatherData =await networkHelper.getData();return weatherData;}Future<dynamic> getLocationWeather() async{Location location = Location();await location.getCurrentLocation();NetworkHelper networkHelper = NetworkHelper('$openWeatherMapURL?format=2&key=$apiKey&dtype=json&lat=${location.latitude}&lon=${location.longitude}');var weatherData = await networkHelper.getData();return weatherData;}String getMessage(int temp) {if (temp > 25) {return '好热,现在适合吃冰淇淋!';} else if (temp > 20) {return '适合穿短袖T恤 ';} else if (temp <= 10) {return '好冷,戴上围巾和手套吧';} else {return '温度宜人,开心玩耍吧';}}
}

getMessage方法是设置之后界面的一些文本丰富界面。
这里说到两种请求聚合api的方式,一种是通过所处地理位置的经纬度。
获取经纬度的方式。

class Location{double latitude;double longitude;Future<void> getCurrentLocation() async{try{Position position = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.low);latitude = position.latitude.abs();longitude = position.longitude.abs();}catch(e){print(e);}}
}

还有一种就是通过城市的名称。

请求的返回结果有多种情况:
1.手机没有网络的情况,会抛出一个没有网络异常,自定义返回一个字符串,方便之后的判断。
2.有网络,请求失败。
3.有网络请求成功。

最后无论是通过聚合接口还是我们自己自定义的,请求网络之后都会有一个返回值,通过不同的返回值来处理相关的逻辑。

拿到返回值后,就把返回值(无论成功与否)通过欢迎界面,传递给主界面。
主界面导航

class AppHome extends StatefulWidget {AppHome({this.locationWeather});final locationWeather;@overrideState<StatefulWidget> createState() {// TODO: implement createStatereturn _HomePageState();}
}class _HomePageState extends State<AppHome>{int _currentIndex=0;List<Widget> _widgets=List();@overridevoid initState() {super.initState();_widgets.add(LocationScreen(locationWeather: widget.locationWeather,));_widgets.add(NewsPage());_widgets.add(MyPage());}@overrideWidget build(BuildContext context) {// TODO: implement buildreturn Scaffold(body: IndexedStack(index: _currentIndex,children: _widgets,),bottomNavigationBar: BottomNavigationBar(items: const <BottomNavigationBarItem>[BottomNavigationBarItem(icon: Icon(Icons.wb_sunny),title: Text("今日天气")),BottomNavigationBarItem(icon: Icon(Icons.library_books),title: Text("今日目标")),BottomNavigationBarItem(icon: Icon(Icons.person),title: Text("关于我的"))],currentIndex: _currentIndex,onTap: _itemTapped,),);}void _itemTapped (int index){setState(() {_currentIndex=index;});}
}

一些简单的写法,不必多言。

在主界面添加子页面的时候,在把从欢迎页面请求的数据,通过主页面传递给天气页面。

class LocationScreen extends StatefulWidget {LocationScreen({this.locationWeather});final locationWeather;@override_LocationScreenState createState() => _LocationScreenState();}
class _LocationScreenState extends State<LocationScreen> {WeatherModel weather = WeatherModel();String temperature;String condition;String cityName;String imgId="assets/images/init.JPG";String weatherMessage;@overridevoid initState() {super.initState();updateUI(widget.locationWeather);}Future<void> updateUI(dynamic weatherData) async {SharedPreferences prefs=await SharedPreferences.getInstance();prefs.setString('temperature', "∅");prefs.setString('condition', "未知");prefs.setString('weatherMessage', "没有查到天气");prefs.setString('cityName', '绵阳');prefs.setString('imgId', 'assets/images/init.JPG');setState(()  {if(weatherData=="empty"||weatherData['result']==null){temperature = prefs.get('temperature');condition = prefs.get('condition');weatherMessage = prefs.get('weatherMessage');cityName = prefs.get('cityName');imgId=prefs.get('imgId');}else {var result = weatherData['result'];var sk = result['sk'];var today = result['today'];temperature = sk['temp'];cityName = weatherData['result']['today']['city'];condition = today['weather'];weatherMessage = weather.getMessage(int.parse(temperature));if(condition.contains("雨")){imgId="assets/images/rain.jpg";}else if(condition.contains("晴")){imgId="assets/images/qing.png";} else if(condition.contains("多云")){imgId="assets/images/duoyun.png";}}});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(leading: Icon(Icons.wb_sunny,color: Colors.white,),title: Text("今日天气"),backgroundColor: Color(0xff343434),),body: Container(decoration: BoxDecoration(image: DecorationImage(image: AssetImage(imgId==null?'assets/images/init.JPG':imgId),fit: BoxFit.cover,),),//constraints: BoxConstraints.expand(),child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween,crossAxisAlignment: CrossAxisAlignment.stretch,children: <Widget>[Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[FlatButton(onPressed: () async {var weatherData = await weather.getLocationWeather();updateUI(weatherData);},child: Icon(Icons.near_me,color: Colors.white,size: 50.0,),),FlatButton(onPressed: () async{var typedName =await Navigator.push(context,MaterialPageRoute(builder: (context) {return CityScreen();},),);if(typedName!=null){var weatherData = await weather.getCityWeather(typedName);updateUI(weatherData);}},child: Icon(Icons.location_city,color: Colors.white,size: 50.0,),),],),Padding(padding: EdgeInsets.only(left: 15.0),child: Row(children: <Widget>[Text('$temperature°',style: kTempTextStyle,),Text(condition,style: kConditionTextStyle,),],),),Padding(padding: EdgeInsets.only(right: 15.0),child: Text('$weatherMessage in $cityName',textAlign: TextAlign.right,style: kMessageTextStyle,),),],),),);}
}

再说回之前请求的情况,如果是没有网络则捕获异常返回“empty”,如果有网络但请求失败,返回的数据中的result==null(试出来的)
通过以上代码,可以看出来,我把这两种情况放在一起,当条件满足时,加载SharedPreferences 存储好的数据(其实没必要用,我用是为了完成老师的打分点)。

然后就是请求成功的情况,解析相应的json串,更新ui。通过返回的不同的天气状况,温度,设置不同的背景图片,通过getMessage()提示不同的语句。

右上角的按钮是进入城市选择界面

class CityScreen extends StatefulWidget {@override_CityScreenState createState() => _CityScreenState();
}class _CityScreenState extends State<CityScreen> {String cityName;@overrideWidget build(BuildContext context) {return Scaffold(resizeToAvoidBottomInset: false,appBar: AppBar(title: Text("选择城市"), backgroundColor: Color(0xff343434),),body: Container(decoration: BoxDecoration(image: DecorationImage(image: AssetImage("assets/images/city_bac.jpg"),fit: BoxFit.cover,),),constraints: BoxConstraints.expand(),child: Column(children: <Widget>[Container(padding: EdgeInsets.all(20.0),child: TextField(style: TextStyle(color: Colors.black,), //TextStyledecoration: kTextFieldInputDecoration,onChanged: (value){cityName = value;},),),FlatButton(onPressed: () {Navigator.pop(context,cityName);},child: Text('Get Weather',style: kButtonTextStyle,),),],),),);}
}


输入城市就可以查到相应城市的天气
左上角的按钮则是定位到当前位置,获取当前位置的天气。

为了完成老师的考核点,设置第二个界面设定目标界面,其实很简单。就是添加了一个文本框,点击按钮,将文本框的内容添加到下方的列表视图中,并保存到数据库中。

class NewsPage extends StatefulWidget{@overrideState<StatefulWidget> createState() {// TODO: implement createStatereturn _NewsPage();}
}
class MyListView extends StatelessWidget {String title;MyListView(this.title);@overrideWidget build(BuildContext context) {return Container(child: Column(children: <Widget>[Container(height: 53,child: _mineItem(title),),Container(color: Color(0xffeaeaea),constraints: BoxConstraints.expand(height: 1.0),),],));}Widget _mineItem(String title) {return InkWell(onTap: (){},child: Row(children: <Widget>[Expanded(flex: 1,child: Container(padding: EdgeInsets.only(left: 16),child: Icon(Icons.access_time)),),Expanded(flex: 6,child: Container(padding: EdgeInsets.only(left: 10),child: Text(title,style: TextStyle(fontSize: 16),),),),Expanded(flex: 1,child: Container(child: Icon(Icons.brightness_5,size: 20,color: Colors.grey,),),)],),);}
}
class _NewsPage extends State<NewsPage> {String goal;List widgets=[];@overridevoid initState() {super.initState();DatabaseHelper.instance.queryAllRows().then((value) {setState(() {value.forEach((element) {widgets.add(element['goalText']);});});}).catchError((onError){print(onError);});}@overrideWidget build(BuildContext context) {// TODO: implement buildreturn Scaffold(appBar: AppBar(backgroundColor: Color(0xff343434),leading: Icon(Icons.library_books,color: Colors.white,),title: Text("今日目标"),),body:Column(children: [Container(padding: EdgeInsets.all(20.0),child: new TextField(style: TextStyle(color: Colors.black,), //TextStyledecoration: InputDecoration(filled: true,fillColor: Colors.white,icon: Icon(Icons.location_city,color: Colors.black,), //IconhintText: '输入今天的目标吧!',hintStyle: TextStyle(color: Colors.grey,), //TextStyleborder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0),),borderSide: BorderSide.none,),),onChanged: (value){goal=value;},),),FlatButton(color: Colors.black,onPressed: () {setState(() {if(goal!=""){widgets.add(goal);DatabaseHelper.instance.insert(new Goal(goalText: goal));}});},child: Text('设定目标!',style: TextStyle(fontSize: 15,fontStyle: FontStyle.italic,color: Colors.white),//style: kButtonTextStyle,),),Expanded(child:new ListView.builder(itemCount: widgets.length,itemBuilder:(context,index){// return ListTile(//   leading: new Icon(Icons.access_time),//   title: Text('${widgets[index]}'),// );return new MyListView(widgets[index]);},),)],),);}
}

自定义了列表项,没什么用,就是丰富一下加个图标。

数据库部分也很简单直接贴代码就ok了。

class Goal {int id;String goalText;Goal({ this .id,this .goalText});Map<String, dynamic> toMap() {return { 'id':id,'goalText': goalText};}
}class DatabaseHelper {static final _databaseName = "myDB.db" ;static final _databaseVersion = 1 ;static final table = 'goal' ;static final columnId = 'id' ;static final columnTitle = 'goalText' ;DatabaseHelper.init();static final DatabaseHelper instance = DatabaseHelper.init();static Database _database;Future<Database> get database async {if (_database != null ) return _database;_database = await _initDatabase();return _database;}_initDatabase() async {String path = join(await getDatabasesPath(), _databaseName);return await openDatabase(path,version: _databaseVersion, onCreate: _onCreate);}Future _onCreate(Database db, int version) async {await db.execute('' 'CREATE TABLE $table ($columnId INTEGER PRIMARY KEY AUTOINCREMENT,$columnTitle TEXT NOT NULL)'' ');
}Future<int> insert(Goal goal) async {Database db = await instance.database;var res = await db.insert(table,goal.toMap());String str=goal.goalText;print("add $str");return res;}Future<List<Map<String, dynamic>>> queryAllRows() async {Database db = await instance.database;var res = await db.query(table);return res;}
}

最后就是关于页面,实在不知道些什么就,很简单写了一些简单的介绍,就是一些文本内容不作过多介绍。

为了使界面代码清晰,将一些格式封装了起来。

const kTempTextStyle = TextStyle(color: Colors.white,fontSize: 100.0,
);const kMessageTextStyle = TextStyle(color: Colors.white,fontSize: 30.0,
);const kButtonTextStyle = TextStyle(fontSize: 30.0,color: Colors.white,
);const kConditionTextStyle = TextStyle(fontSize: 30.0,color: Colors.white,
);const kTextFieldInputDecoration = InputDecoration(filled: true,fillColor: Colors.white,icon: Icon(Icons.location_city,color: Colors.white,), //IconhintText: 'Enter City Name',hintStyle: TextStyle(color: Colors.grey,), //TextStyleborder: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10.0),),borderSide: BorderSide.none,),
);

完工。

Flutter实现天气查询App相关推荐

  1. Flutter实战5 -- 天气查询APP重构之状态管理(ScopedModel)

    0x00 前言 前面四篇文章: Flutter实战1 --- 写一个天气查询的APP Flutter实战2 --- 写一个天气查询的APP FFlutter实战3 --- PC上运行Flutter A ...

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

    使用Flutter编写一个简单的天气查询App Flutter项目目录分析 入口函数 home:主页面 编写天气应用 网络请求 数据解析 布局编写 Flutter里基础的Widget 上 中 下 Fl ...

  3. 预报精准的天气查询APP开发原理是什么

    在互联网还不发达的时候,人们看天气预报只能从电视上获取,而且每晚都要坐等七点半新闻联播后,非常不方便,除此之外央视的天气预报不能将全国所有地区的天气都报道出来,只能报道一些省会城市的天气,所以如果你待 ...

  4. “Rimon天气”Android天气查询软件项目总结

    "Rimon天气"是我在自学Android软件开发一段时间后,以郭霖写的<第一行代码>书中的天气查询软件"酷欧天气"为参考,改写的天气查询app.与 ...

  5. Flutter实战(一)写一个天气查询的APP

    先上效果图: 代码github地址:github.com/koudle/GDG_- 1.创建工程 在Android Studio中,File -> New ->New Flutter Pr ...

  6. 仿网易新闻APP(四)——标题栏之本市天气(百度定位与车联网之天气查询)

    废话不多说,上图: 下面用到的知识有,百度定位及车联网API的使用,当然车联网API看起来高大上,其实我们这里只用来获取车联网中的天气查询功能.其他的功能还有渐变动画及缩放动画,以及定时更新天气及定位 ...

  7. python语音播报计算结果_Python 天气查询到实现语音播放

    import requests #引用requests模块 import pygame # 获取天气 def inquery(self): url = "https://free-api.h ...

  8. vue+axios天气查询——天知道效果展示及源码分析

    使用vue制作城市的天气查询 <!DOCTYPE html> <html lang="en"><head><meta charset=&q ...

  9. vue实现查询多条记录_vue.js 实现天气查询

    效果预览:http://songothao.gitee.io/weather_query_based_on_vuejs/ 项目已上传码云:叁贰壹/vuejs实现天气查询 知乎视频​www.zhihu. ...

最新文章

  1. 三、python中最基础的文件处理汇总
  2. python解析库_Python命令行解析库argparse
  3. According to TLD or attribute directive in tag file, attribute value does not accept any expressions
  4. Hibernate如何一个类映射两个表
  5. Verilog MIPS32 CPU(一)-- PC寄存器
  6. 如何清空一个数组内容
  7. mysql 表空间加密,MySql(8.0)基于docker部署(加密存储表空间)
  8. <一起JS(基础篇)>4.标识符、字符串、Number、波尔值、Null和Undefined
  9. 服务器被攻击 显示503,打开网页后出现503 service unavailable等字样,什么意思
  10. 快速加速计算机的方法,电脑慢的快速解决办法 四种方法电脑速度变快10倍
  11. littleVGL学习笔记9——lv_btn 按钮
  12. 5G通信基础知识学习
  13. Java多态 父类引用指向子类对象
  14. Linux从安装到实战+学校Linux+瑞吉外卖Linux项目部署
  15. poj Best Cow Line
  16. log4j2内容详解
  17. 2022年数维杯国际赛ABCD题思路
  18. 1060 爱丁顿数 (25分)
  19. 打造个人股票监控系统 实时跟踪股票价格走势
  20. Kali实现局域网ARP欺骗和ARP攻击

热门文章

  1. leetcode 971. Flip Binary Tree To Match Preorder Traversal
  2. 如何在互联网公司求职成功
  3. 14个小方法巧除鞋臭脚臭
  4. 【UmiJS 3.x入门】
  5. 怎么设置邮箱收发服务器
  6. 音乐原唱伴唱卡拉ok模式解决!
  7. html5 游戏制作教程,【整理】一步一步学做HTML5游戏教程
  8. 计算python程序运行时长,并以时分秒形式输出
  9. STM32的SG90舵机驱动
  10. 评论式软文怎么写?学会这三招,让你轻松营造出客观的感觉