目录
一、测试环境说明
电脑环境
Windows 11
编写语言
JAVA
开发软件
Android Studio (2020)
开发软件只要大于等于测试版本即可(近几年官网直接下载也可以),若是版本低于测试版本请自行测试。项目需要根据你的软件自行适配
二、项目简介
该项目简介来自网络,具体内容需要自行测试
本项目是基于Android平台的天气应用开发,采用Java语言和MVVM架构模式实现。系统集成和风天气API提供实时气象数据,具备定位服务、城市管理、多日预报等核心功能。
首页展示当前温度、湿度、风速等关键指标,未来天气页面提供三日预报可视化,城市收藏模块支持个性化地点管理,城市添加功能实现全国城市检索。
技术层面采用RecyclerView实现高效列表渲染,Gson处理JSON数据,SharedPreferences持久化用户偏好,权限管理模块保障定位功能合规性。
界面设计遵循Material Design规范,适配多种屏幕尺寸,通过异步加载优化用户体验。系统具有数据响应快、界面简洁、操作流畅等特点,为日常生活出行提供精准气象服务。
该项目由编程乐学团队介入,优化布局完善功能
三、项目演示
网络资源模板--基于Android studio 天气预报App
四、部设计详情(部分)
首页
1. 页面结构
页面采用垂直线性布局,上方是当前位置展示区域,点击可选择城市;中间是滚动显示的天气详情区,包含天气图标、温度、风速等;
底部是“查看未来天气”按钮。整体层级清晰,内容居中排布,注重视觉对称与信息聚焦。
2. 使用到的技术
页面使用了原生安卓组件如LinearLayout、NestedScrollView与TextView,结合位置权限管理、定位服务和实时天气接口实现功能。
数据层使用本地数据库与SDK相结合,同时支持权限判断、动态定位和页面跳转逻辑。
3. 页面详细介绍
页面展示的是用户当前城市的实时天气情况,包括温度、风向、风速、湿度与降水量等关键数据。
支持点击切换城市并自动刷新数据,同时按钮可跳转查看该城市未来天气。页面提供动态数据展示与良好的用户交互设计。
package com.app.weather.ui;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.Address;
import android.location.Location;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.app.weather.data.WeatherDataSource;
import com.app.weather.dialog.LocatingDialog;
import com.app.weather.utils.LocationHelper;
import com.hjq.permissions.OnPermissionCallback;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
import com.qweather.sdk.bean.base.Code;
import com.qweather.sdk.bean.weather.WeatherNowBean;
import com.qweather.sdk.view.QWeather;
import com.app.weather.Mapp;
import com.app.weather.R;
import com.app.weather.bean.City;
import com.app.weather.data.CityDataSource;
import com.app.weather.utils.SizeUtils;
import com.app.weather.utils.ToastUtils;
import com.app.weather.utils.WeatherUtils;
import java.util.List;
public class MainActivity extends BaseActivity {
private static final int REQUEST_CHOOSE_CITY = 1;
private LinearLayout positionLinearLayout;//位置布局控件
private TextView positionTextView;//位置文本控件
private TextView futureTextView;//未来天气按钮控件
private LinearLayout contentLinearLayout;//内容布局控件
private ImageView weatherImageView;//天气图片控件
private TextView weatherTextView;//天气文本控件
private TextView temperatureTextView;//温度文本控件
private TextView windDirTextView;//风向文本控件
private TextView windSpeedTextView;//风速文本控件
private TextView humidityTextView;//湿度文本控件
private TextView precipTextView;//降水量文本控件
private LocatingDialog locatingDialog;//定位加载对话框
private CityDataSource cityDataSource;//城市数据源
private WeatherDataSource weatherDataSource;//天气数据源
private LocationHelper locationHelper;//定位助手
private City locationCity;//定位的城市
private City lastCity;//最近一次操作的城市
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
setView();
getWeatherNow();
}
/**
* 后一个页面回传的处理
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_CHOOSE_CITY: {
if (resultCode == RESULT_OK) {
String locationId = data.getStringExtra("city");
cityDataSource.saveLastCity(locationId);
City city = cityDataSource.selectOne(locationId);
//刷新数据
notifyCityWeather(city);
} else if (resultCode == RESULT_CANCELED) {
getWeatherNow();
}
}
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
dismissLocatingDialog();
if (locationHelper != null) {
locationHelper.destory();
locationHelper = null;
}
}
/**
* 初始化数据
*/
private void initData() {
cityDataSource = Mapp.getInstance().getCityDataSource();
weatherDataSource = Mapp.getInstance().getWeatherDataSource();
}
/**
* 初始化控件
*/
private void initView() {
positionLinearLayout = findViewById(R.id.position_linearLayout);
positionTextView = findViewById(R.id.position_textView);
futureTextView = findViewById(R.id.future_textView);
contentLinearLayout = findViewById(R.id.content_linearLayout);
weatherImageView = findViewById(R.id.weather_imageView);
weatherTextView = findViewById(R.id.weather_textView);
temperatureTextView = findViewById(R.id.temperature_textView);
windDirTextView = findViewById(R.id.windDir_textView);
windSpeedTextView = findViewById(R.id.windSpeed_textView);
humidityTextView = findViewById(R.id.humidity_textView);
precipTextView = findViewById(R.id.precip_textView);
}
/**
* 设置控件
*/
private void setView() {
positionLinearLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = CityCollectionActivity.buildIntent(MainActivity.this);
startActivityForResult(intent, REQUEST_CHOOSE_CITY);
}
});
}
/**
* 根据城市,刷新控件和天气数据
*/
private void notifyCityWeather(@NonNull City city) {
//如果最近操作的城市和当前传入的城市市同一个,则不做操作,减少天气数据的请求次数
if (city.equals(lastCity)) {
return;
}
lastCity = city;
setCityView(city);
getWeatherNow(city);
}
/**
* 设置城市相关控件
*/
private void setCityView(@NonNull City city) {
positionTextView.setText(city.getLocationNameZH());
futureTextView.setVisibility(View.VISIBLE);
futureTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = WeatherFutureActivity.buildIntent(MainActivity.this, city.getLocationID());
startActivity(intent);
}
});
}
/**
* 检查定位权限是否授权,已授权则开启定位,未授权则使用默认城市
*/
private void checkPermissionToLocation() {
XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).request(new OnPermissionCallback() {
@Override
public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
//如果用户选中模糊定位,allGranted是false,但是也应该去定位,所以不需要判断allGranted
startLocation();
}
@Override
public void onDenied(@NonNull List<String> permissions, boolean doNotAskAgain) {
OnPermissionCallback.super.onDenied(permissions, doNotAskAgain);
getDefaultCityWeatherNow();
}
});
}
/**
* 开始定位
*/
private void startLocation() {
showLocatingDialog();
if (locationHelper == null) {
locationHelper = new LocationHelper();
}
locationHelper.startLocation(new LocationHelper.OnResultListener() {
@Override
public void onSuccess(@NonNull Location location) {
dismissLocatingDialog();
Address address = LocationHelper.getAddress(location);
if (address == null) {
ToastUtils.showShort("未找到定位地址信息");
getDefaultCityWeatherNow();
} else {
City city = cityDataSource.selectOneByNameZH(address.getAdminArea(), address.getLocality(), address.getSubLocality());
if (city == null) {
ToastUtils.showShort("未找到有效的定位地址信息");
getDefaultCityWeatherNow();
} else {
locationCity = city;
notifyCityWeather(city);
}
}
}
@Override
public void onFailure(@NonNull String msg) {
dismissLocatingDialog();
ToastUtils.showShort(msg);
getDefaultCityWeatherNow();
}
});
}
/**
* 获取默认城市并获取天气数据,将该城市作为定位城市,防止未找到收藏城市时重新定位
*/
private void getDefaultCityWeatherNow() {
City city = cityDataSource.selectList(null).get(0);
locationCity = city;
notifyCityWeather(city);
}
/**
* 获取实时天气
*/
private void getWeatherNow() {
//获取最近操作的城市
String locationId = cityDataSource.getLastCity();
//检查该城市是否在收藏夹中,没有就获取收藏列表的第一个数据
if (!TextUtils.isEmpty(locationId)) {
List<String> collections = cityDataSource.selectCollections();
if (!collections.contains(locationId)) {
locationId = collections.size() > 0 ? collections.get(0) : null;
}
}
//根据id获取城市对象
City city = TextUtils.isEmpty(locationId) ? null : cityDataSource.selectOne(locationId);
//如果收藏的城市不存在,则使用定位的城市,如果没有定位的城市,则开启定位
if (city == null && locationCity != null) {
city = locationCity;
}
if (city == null) {
checkPermissionToLocation();
} else {
notifyCityWeather(city);
}
}
/**
* 获取实时天气
*/
private void getWeatherNow(@NonNull City city) {
showLoadingDialog();
weatherDataSource.getWeatherNow(this, city.getLocationID(), new QWeather.OnResultWeatherNowListener() {
@Override
public void onSuccess(WeatherNowBean weatherBean) {
contentLinearLayout.setVisibility(View.VISIBLE);
dismissLoadingDialog();
if (weatherBean.getCode() == Code.OK) {
WeatherNowBean.NowBaseBean nowBaseBean = weatherBean.getNow();
//设置天气动画图标
int icon = WeatherUtils.getIcon(nowBaseBean.getIcon());
weatherImageView.setImageResource(icon);
//设置天气名称
weatherTextView.setText(nowBaseBean.getText());
//设置温度
SpannableStringBuilder temperatureBuilder = new SpannableStringBuilder(nowBaseBean.getTemp() + "°C");
temperatureBuilder.setSpan(new AbsoluteSizeSpan(SizeUtils.sp2px(30)), nowBaseBean.getTemp().length(), temperatureBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
temperatureTextView.setText(temperatureBuilder);
//设置风向
windDirTextView.setText(nowBaseBean.getWindDir());
//设置风速
windSpeedTextView.setText(nowBaseBean.getWindSpeed() + "km/h");
//设置相对湿度
humidityTextView.setText(nowBaseBean.getHumidity() + "%");
//设置降水量
precipTextView.setText(nowBaseBean.getPrecip() + "mm");
} else {
ToastUtils.showShort(weatherBean.getCode().getTxt());
}
}
@Override
public void onError(Throwable e) {
contentLinearLayout.setVisibility(View.INVISIBLE);
dismissLoadingDialog();
ToastUtils.showShort(e.getMessage());
}
});
}
/**
* 显示定位加载对话框
*/
private void showLocatingDialog() {
if (locatingDialog == null) {
locatingDialog = new LocatingDialog(this);
locatingDialog.setCancelable(false);
locatingDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if (locationHelper != null) {
locationHelper.stopLocation();
}
}
});
}
if (!locatingDialog.isShowing()) {
locatingDialog.show();
}
}
/**
* 关闭定位加载对话框
*/
private void dismissLocatingDialog() {
if (locatingDialog != null && locatingDialog.isShowing()) {
locatingDialog.dismiss();
}
}
}
未来天气
1. 页面结构
该页面采用纵向线性布局,顶部为一个包含标题的工具栏,主要用于展示页面名称及返回功能。
工具栏下方是一个垂直方向的可滑动列表,列表用于展示未来几天的天气数据。整个页面整体结构简洁明了,适合信息清晰展示。
2. 使用到的技术
页面基于原生安卓开发,使用了Material Design组件、RecyclerView列表控件、AppBarLayout作为标题栏容器,以及天气数据接口SDK实现天气数据的异步加载和展示。
同时还结合工具类实现列表间距及提示信息的统一处理。
3. 页面详细介绍
页面功能为展示某个地区未来的天气预报,用户通过传入地理位置标识启动页面,页面自动请求对应数据并加载展示。
页面支持数据加载提示,错误信息反馈,返回键导航等交互设计,提升用户体验与易用性。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<!-- 标题栏-->
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.Weather.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.Weather.PopupOverlay"
app:title="未来天气" />
</com.google.android.material.appbar.AppBarLayout>
<!-- 列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="30dp" />
</LinearLayout>
五、项目源码
👇👇👇👇👇快捷方式👇👇👇👇👇