网络资源模板--基于Android Studio 实现的天气预报App

发布于:2025-08-03 ⋅ 阅读:(10) ⋅ 点赞:(0)

目录

一、测试环境说明

二、项目简介

三、项目演示

四、部设计详情(部分)

首页

未来天气

五、项目源码 


一、测试环境说明

电脑环境

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>

五、项目源码 

👇👇👇👇👇快捷方式👇👇👇👇👇