Flutter实现天气查询App

Posted JackySei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter实现天气查询App相关的知识,希望对你有一定的参考价值。

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

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

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

void main() 
  runApp(MyApp());


class MyApp extends StatefulWidget
  @override
  State<StatefulWidget> createState() 
    return _MyApp();
  


class _MyApp extends State<MyApp>
  @override
  Widget build(BuildContext context) 
    // TODO: implement build
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        title: "天气app",
       //theme: ThemeData.dark(),
        home: WelcomePage()
    );
  


class WelcomePage extends StatefulWidget
  @override
  State<StatefulWidget> createState() 
    // TODO: implement createState
    return _WelcomePage();
  


class _WelcomePage extends State<WelcomePage>
  @override
  Widget 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 build
    Future.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=您申请的KEY
  Future<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;
  @override
  State<StatefulWidget> createState() 
    // TODO: implement createState
    return _HomePageState();
  


class _HomePageState extends State<AppHome>

  int _currentIndex=0;
  List<Widget> _widgets=List();
  @override
  void initState() 
    super.initState();
    _widgets.add(LocationScreen(locationWeather: widget.locationWeather,));
    _widgets.add(NewsPage());
    _widgets.add(MyPage());
  

  @override
  Widget build(BuildContext context) 
    // TODO: implement build
    return 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;

  @override
  void 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";
        
      

    );
  

  @override
  Widget 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;
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(title: Text("选择城市"), backgroundColor: Color(0xff343434),),
      bod

以上是关于Flutter实现天气查询App的主要内容,如果未能解决你的问题,请参考以下文章

Flutter实现天气查询App

Flutter实现天气查询App

文件 `Runner.app/Frameworks/Runner.xcworkspace` 没有此位置的当前文件类型

Flutter 调用高德地图APP实现位置搜索路线规划逆地理编码

Flutter 调用高德地图APP实现位置搜索路线规划逆地理编码

flutter APP 极光推送引入后 权限问题