kotlin 编写一个简单的天气预报app(五)增加forcast接口并显示

参考资料

OpenWeatherMap提供了一个/forecast接口,用于获取未来几天的天气预报。你可以使用HTTP GET请求访问该接口,并根据你所在的城市或地理坐标获取相应的天气数据。

以下是一个示例请求的URL和一些常用的参数:

复制代码
URL: http://api.openweathermap.org/data/2.5/forecast

查询参数:

  • q (必需): 城市名称 (e.g. "London,uk") 或城市ID (可在OpenWeatherMap网站上获得) 或地理坐标 (使用纬度和经度, e.g. "37.7749,-122.4194")。
  • appid (必需): 你的OpenWeatherMap API密钥。
    可选参数:
    units: 温度单位 (例如 "metric" 表示摄氏度, "imperial" 表示华氏度)。
    lang: 返回的天气描述语言 (例如 "en" 表示英语)。

从openWeatherMap获取forecast

1.在WeatherService接口中增加请求函数。

getForecastByCityName:此方法与 getWeatherByCityName 方法类似,但它检索预报数据而不是当前天气数据。它还采用城市名称和 API 密钥作为参数,并返回 ForecastResponse 类型的 Call 对象,这是从 API 收到的响应。

kotlin 复制代码
interface WeatherService {

    @GET("weather")
    fun getWeatherByCityName(
        @Query("q") cityName : String,
        @Query("appid") apiKey : String
    ) : Call<WeatherResponse>

    @GET("forecast")
    fun getForecastByCityName(
        @Query("q") cityName : String,
        @Query("appid") apiKey : String
    ) : Call<ForecastResponse>
}

2.编译一个新的ForecastResponse 类,用于解析天气预报的 JSON 数据。它具有以下属性:

  • cod:表示响应 JSON 中的 cod 值的字符串变量。
  • message:表示响应 JSON 中的消息值的整数变量。
  • cnt:表示响应 JSON 中的 cnt 值的整数变量。
  • forecastCellList:ForecastCell 对象的ArrayList,表示响应JSON 中的预测单元格列表。
  • forecastCity:ForecastCity 对象,表示响应 JSON 中的城市详细信息。
    这些属性使用 @SerializedName 进行注释,以指定 JSON 中相应的键。提供默认值是为了初始化目的。
kotlin 复制代码
package com.example.myweather.openWeatherMap

import com.example.myweather.WeatherResponseClouds
import com.example.myweather.WeatherResponseCoord
import com.example.myweather.WeatherResponseWeather
import com.google.gson.annotations.SerializedName

data class ForecastResponse (
    @SerializedName("cod")
    var cod: String = "",
    @SerializedName("message")
    var message: Int = 0,
    @SerializedName("cnt")
    var cnt : Int = 0,
    @SerializedName("list")
    var forecastCellList : ArrayList<ForecastCell>? = null,
    @SerializedName("city")
    var forecastCity: ForecastCity? = null
)


data class ForecastCell (
    @SerializedName("dt")
    val dt: Long,
    @SerializedName("main")
    val main: ForecastMain,
    @SerializedName("weather")
    val weather: List<WeatherResponseWeather>,
    @SerializedName("clouds")
    val clouds: WeatherResponseClouds,
    @SerializedName("wind")
    val wind: ForecastWind,
    @SerializedName("visibility")
    val visibility: Int = 0,
    @SerializedName("pop")
    val pop: Double = 0.0,
    @SerializedName("rain")
    val rain: ForecastRain,
    @SerializedName("snow")
    val snow: ForecastSnow,
    @SerializedName("sys")
    val sys: ForecastSys,
    @SerializedName("dt_txt")
    val dt_txt: String = ""
)

data class ForecastCity(
    @SerializedName("id")
    val id: Int = 0,
    @SerializedName("name")
    val name: String = "",
    @SerializedName("coord")
    val coord: WeatherResponseCoord,
    @SerializedName("country")
    val country: String ="",
    @SerializedName("population")
    val population:Int = 0,
    @SerializedName("timezone")
    val timezone: Int = 0,
    @SerializedName("sunrise")
    val sunrise: Int = 0,
    @SerializedName("sunset")
    val sunset: Int = 0
)

data class ForecastMain(
    @SerializedName("temp")
    val temperature: Double = 0.0,
    @SerializedName("feels_like")
    val feelsLike: Double = 0.0,
    @SerializedName("temp_min")
    val minTemperature: Double = 0.0,
    @SerializedName("temp_max")
    val maxTemperature: Double = 0.0,
    @SerializedName("pressure")
    val pressure: Int = 0,
    @SerializedName("sea_level")
    val seaLevel: Int = 0,
    @SerializedName("grnd_level")
    val groundLevel: Int = 0,
    @SerializedName("humidity")
    val humidity: Int = 0,
    @SerializedName("temp_kf")
    val temperatureKf: Double = 0.0
)

data class ForecastWind(
    @SerializedName("speed")
    val speed: Double = 0.0,
    @SerializedName("deg")
    val degree: Int = 0,
    @SerializedName("gust")
    val gust : Double = 0.0
)

data class ForecastRain(
    @SerializedName("3h")
    val heightInThreeHours: Double = 0.0
)

data class ForecastSnow(
    @SerializedName("3h")
    val heightInThreeHours: Double = 0.0
)

data class ForecastSys(
    @SerializedName("pod")
    val partOfDay: String = ""
)

3.在CustomEvent.kt中增加ForecastResponseEvent事件

kotlin 复制代码
class ForecastResponseEvent(val forecastResponse: ForecastResponse)

4.在RetrofitClient.kt中增加getForecastByCityName函数,用来MainActivity中调用请求接口:

kotlin 复制代码
    fun getForecastByCityName(cityName: String) {
        val call = weatherService.getForecastByCityName(cityName, API_KEY)
        call.enqueue(object : Callback<ForecastResponse> {
            override fun onResponse(call : Call<ForecastResponse>,
                response: Response<ForecastResponse>) {
                if(response.isSuccessful) {
                    val forecastData = response.body()
                    handleForecastData(forecastData)
                } else {
                    handleForecastFailure(response.message())
                }

            }

            override fun onFailure(call: Call<ForecastResponse>, t: Throwable) {
                handleForecastFailure(t.message!!)
            }
        })

5.并且增加了相应函数

  • handleForecastFailure接受消息字符串作为参数并将其与前缀一起打印出来。
  • handleForecastData接受一个ForecastResponse对象作为参数。它检查该对象是否为空,如果不为空,则创建一个对象ForecastResponseEvent并使用 EventBus 发布它。然后它调用该printForecastResponse函数并传入该ForecastResponse对象。
  • printForecastResponse接受一个ForecastResponse对象作为参数,并打印出该对象的各种属性,例如 、cod、message和cnt的大小forecastCellList。它还打印出对象的id和属性。nameforecastCity
kotlin 复制代码
    private fun handleForecastFailure(message: String) {
        println("handleForecastFailure:${message}")
    }

    private fun handleForecastData(forecastData: ForecastResponse?) {
        if(forecastData == null) return

        val forecastResponseEvent = ForecastResponseEvent(forecastData)
        EventBus.getDefault().post(forecastResponseEvent)		//这里发送了forecastResponseEvent

        printForecastResponse(forecastData)
    }

    private  fun printForecastResponse(forecastResponse: ForecastResponse) {
        println("cod:${forecastResponse.cod}")
        println("message:${forecastResponse.message}")
        println("cnt:${forecastResponse.cnt}")
        println("list:${forecastResponse.forecastCellList?.size}")
        println("city id:${forecastResponse.forecastCity?.id} name:${forecastResponse.forecastCity?.name}")
    }

6.在MainActivity中,处理forecastResponseEvent事件:

该函数是一个事件处理程序,在收到onReceiveForecastResponsea 时调用。ForecastResponseEvent它采用事件对象作为参数,其中包含预测响应数据。该函数调用该updateForecastList函数根据接收到的数据更新预测列表。

updateForecastList函数接受一个ForecastResponse对象作为参数。然后,它创建一个SimpleDateFormat对象来格式化预测响应中的日期和时间。该函数初始化一个空的可变列表data来存储格式化的预测数据。

然后,该函数会迭代 的预测单元格列表中的每个单元格forecastResponse。对于每个单元格,它都会创建一个字符串,oneLine其中包含格式化的日期和时间、温度、feel_like、天气主体和天气描述。通过减去常数值并将其转换为整数,将温度从开尔文转换为摄氏度kelvins。

每个oneLine字符串都会添加到data列表中。

最后,该函数创建一个ArrayAdapter以data列表为数据源的适配器,并将其设置为ListViewID 的适配器listViewTodayForcast。这将使用更新的预测数据更新列表视图。

kotlin 复制代码
    @RequiresApi(Build.VERSION_CODES.O)
    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onReceiveForecastResponse(event: ForecastResponseEvent) {
        updateForecastList(event.forecastResponse)
    }
    
    private fun updateForecastList(forecastResponse: ForecastResponse) {
        val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH)
        val data = mutableListOf<String>()
        for (cell in forecastResponse.forecastCellList!!) {
            val oneLine = "${simpleDateFormat.format(cell.dt*1000L)}\n" +
                    "temperature:${cell.main.temperature.minus(kelvins).toInt()}," +
                    "feel_like:${cell.main.feelsLike.minus(kelvins).toInt()},\n" +
                    "weather:${cell.weather.first().main},${cell.weather.first().description}"
            data.add(oneLine)
        }

        val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data)
        findViewById<ListView>(R.id.listViewTodayForcast).adapter = adapter
    }

7.我在主界面中增加了一个ListView用来显示forecast返回的数据

xml 复制代码
    <ListView
        android:id="@+id/listViewTodayForcast"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/textViewWeather"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

8.最后的结果:

相关推荐
阿巴斯甜19 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker20 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952721 小时前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android