Android Studio开发天气预报应用需要完成API调用、UI设计、数据解析等步骤。以下是基于OpenWeatherMap API的实现方法,包含关键代码片段和详细说明。
确保已安装Android Studio最新版本,创建新项目选择Empty Activity模板。在build.gradle
模块文件中添加必要依赖:
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
}
同步项目后,在AndroidManifest.xml
中添加网络权限:
访问OpenWeatherMap官网注册账号,在个人仪表板获取免费API密钥。免费版每小时可调用60次,支持当前天气和5天预报。
创建三个数据类对应API响应结构:
data class WeatherResponse(
val name: String, // 城市名
val main: MainData,
val weather: List,
val dt: Long // 时间戳
)
data class MainData(
val temp: Double,
val feels_like: Double,
val humidity: Int
)
data class WeatherInfo(
val id: Int,
val main: String,
val description: String,
val icon: String
)
预报数据模型单独建立:
data class ForecastResponse(
val list: List
)
data class ForecastItem(
val dt: Long,
val main: MainData,
val weather: List
)
使用Retrofit构建API服务接口:
interface WeatherApiService {
@GET("weather")
suspend fun getCurrentWeather(
@Query("q") city: String,
@Query("units") units: String = "metric",
@Query("appid") apiKey: String = "YOUR_API_KEY"
): Response
@GET("forecast")
suspend fun getForecast(
@Query("q") city: String,
@Query("units") units: String = "metric",
@Query("appid") apiKey: String = "YOUR_API_KEY"
): Response
}
创建Retrofit实例:
object RetrofitClient {
private const val BASE_URL = "https://api.openweathermap.org/data/2.5/"
val instance: WeatherApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(WeatherApiService::class.java)
}
}
创建ViewModel处理业务逻辑:
class WeatherViewModel : ViewModel() {
private val _currentWeather = MutableLiveData()
val currentWeather: LiveData = _currentWeather
private val _forecast = MutableLiveData>()
val forecast: LiveData> = _forecast
suspend fun fetchWeatherData(city: String) {
viewModelScope.launch {
try {
val currentResponse = RetrofitClient.instance.getCurrentWeather(city)
if (currentResponse.isSuccessful) {
_currentWeather.postValue(currentResponse.body())
}
val forecastResponse = RetrofitClient.instance.getForecast(city)
if (forecastResponse.isSuccessful) {
_forecast.postValue(forecastResponse.body()?.list?.take(8)) // 取未来24小时预报
}
} catch (e: Exception) {
// 错误处理
}
}
}
}
activity_main.xml
布局文件包含搜索框和天气显示区域:
创建预报项的RecyclerView布局item_forecast.xml
:
在主Activity中实现数据观察和绑定:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: WeatherViewModel
private lateinit var forecastAdapter: ForecastAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(WeatherViewModel::class.java)
setupRecyclerView()
setupObservers()
setupSearchView()
}
private fun setupRecyclerView() {
forecastRecyclerView.layoutManager = LinearLayoutManager(this, HORIZONTAL, false)
forecastAdapter = ForecastAdapter()
forecastRecyclerView.adapter = forecastAdapter
}
private fun setupObservers() {
viewModel.currentWeather.observe(this) { weather ->
temperatureText.text = "${weather.main.temp}°C"
descriptionText.text = weather.weather[0].description
Glide.with(this)
.load("https://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png")
.into(weatherIcon)
}
viewModel.forecast.observe(this) { items ->
forecastAdapter.submitList(items)
}
}
private fun setupSearchView() {
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
lifecycleScope.launch {
viewModel.fetchWeatherData(query)
}
return true
}
//...其他方法
})
}
}
创建RecyclerView适配器显示预报数据:
class ForecastAdapter : ListAdapter(DiffCallback()) {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val timeText: TextView = view.findViewById(R.id.timeText)
val icon: ImageView = view.findViewById(R.id.forecastIcon)
val tempText: TextView = view.findViewById(R.id.forecastTempText)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_forecast, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.timeText.text = SimpleDateFormat("HH:mm", Locale.getDefault())
.format(Date(item.dt * 1000))
holder.tempText.text = "${item.main.temp}°C"
Glide.with(holder.itemView)
.load("https://openweathermap.org/img/wn/${item.weather[0].icon}.png")
.into(holder.icon)
}
class DiffCallback : DiffUtil.ItemCallback() {
override fun areItemsTheSame(oldItem: ForecastItem, newItem: ForecastItem) = oldItem.dt == newItem.dt
override fun areContentsTheSame(oldItem: ForecastItem, newItem: ForecastItem) = oldItem == newItem
}
}
API返回401错误:检查API密钥是否正确,确保已激活OpenWeatherMap服务
图片加载失败:验证Glide初始化,检查网络权限
数据不更新:确认LiveData观察者已正确注册,检查网络请求是否成功
内存泄漏:在onDestroy中取消所有协程,避免持有Activity引用
通过以上步骤可构建完整的天气预报应用,实际开发中建议添加加载状态提示和错误处理机制提升用户体验。记得将YOUR_API_KEY替换为实际获得的OpenWeatherMap API密钥。