Flutter实现天气查询App
该项目是一个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相关推荐
- Flutter实战5 -- 天气查询APP重构之状态管理(ScopedModel)
0x00 前言 前面四篇文章: Flutter实战1 --- 写一个天气查询的APP Flutter实战2 --- 写一个天气查询的APP FFlutter实战3 --- PC上运行Flutter A ...
- 使用Flutter编写一个简单的天气查询App
使用Flutter编写一个简单的天气查询App Flutter项目目录分析 入口函数 home:主页面 编写天气应用 网络请求 数据解析 布局编写 Flutter里基础的Widget 上 中 下 Fl ...
- 预报精准的天气查询APP开发原理是什么
在互联网还不发达的时候,人们看天气预报只能从电视上获取,而且每晚都要坐等七点半新闻联播后,非常不方便,除此之外央视的天气预报不能将全国所有地区的天气都报道出来,只能报道一些省会城市的天气,所以如果你待 ...
- “Rimon天气”Android天气查询软件项目总结
"Rimon天气"是我在自学Android软件开发一段时间后,以郭霖写的<第一行代码>书中的天气查询软件"酷欧天气"为参考,改写的天气查询app.与 ...
- Flutter实战(一)写一个天气查询的APP
先上效果图: 代码github地址:github.com/koudle/GDG_- 1.创建工程 在Android Studio中,File -> New ->New Flutter Pr ...
- 仿网易新闻APP(四)——标题栏之本市天气(百度定位与车联网之天气查询)
废话不多说,上图: 下面用到的知识有,百度定位及车联网API的使用,当然车联网API看起来高大上,其实我们这里只用来获取车联网中的天气查询功能.其他的功能还有渐变动画及缩放动画,以及定时更新天气及定位 ...
- python语音播报计算结果_Python 天气查询到实现语音播放
import requests #引用requests模块 import pygame # 获取天气 def inquery(self): url = "https://free-api.h ...
- vue+axios天气查询——天知道效果展示及源码分析
使用vue制作城市的天气查询 <!DOCTYPE html> <html lang="en"><head><meta charset=&q ...
- vue实现查询多条记录_vue.js 实现天气查询
效果预览:http://songothao.gitee.io/weather_query_based_on_vuejs/ 项目已上传码云:叁贰壹/vuejs实现天气查询 知乎视频www.zhihu. ...
最新文章
- 三、python中最基础的文件处理汇总
- python解析库_Python命令行解析库argparse
- According to TLD or attribute directive in tag file, attribute value does not accept any expressions
- Hibernate如何一个类映射两个表
- Verilog MIPS32 CPU(一)-- PC寄存器
- 如何清空一个数组内容
- mysql 表空间加密,MySql(8.0)基于docker部署(加密存储表空间)
- <一起JS(基础篇)>4.标识符、字符串、Number、波尔值、Null和Undefined
- 服务器被攻击 显示503,打开网页后出现503 service unavailable等字样,什么意思
- 快速加速计算机的方法,电脑慢的快速解决办法 四种方法电脑速度变快10倍
- littleVGL学习笔记9——lv_btn 按钮
- 5G通信基础知识学习
- Java多态 父类引用指向子类对象
- Linux从安装到实战+学校Linux+瑞吉外卖Linux项目部署
- poj Best Cow Line
- log4j2内容详解
- 2022年数维杯国际赛ABCD题思路
- 1060 爱丁顿数 (25分)
- 打造个人股票监控系统 实时跟踪股票价格走势
- Kali实现局域网ARP欺骗和ARP攻击