ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android Studio天气预报APP源码解析:ViewPager+SlidingMenu完整毕设工程

Android Studio天气预报APP源码解析:ViewPager+SlidingMenu完整毕设工程 简介一套基于Android Studio开发的天气预报APP毕业设计项目源码及配套文档面向计算机相关专业正在准备毕业设计、课程设计或期末大作业的学生也适合需要Android项目实战练习的开发者。项目曾获导师指导与认可评审得分98分所有源码均已本地编译调试通过可放心运行。压缩包内共666个文件约23.93MB。java文件承载主要业务逻辑xml负责界面布局与配置class为编译产物png为天气图标与界面图片jar/aar为第三方依赖json用于数据解析另有gradle构建脚本、aidl接口、apk安装包等结构清晰可直接导入Android Studio学习与二次开发。目前已有185人浏览学习。整套资料除可运行源码外还附有文档说明能够帮助理解天气预报功能从数据获取到界面展示的完整实现流程无论用于毕业设计参考还是日常项目练习都有较高的实用价值。1. 天气预报APP源码为什么说这是能直接答辩的完整工程如果你正在找一份能在答辩当天拿得出手的AndroidStudio天气预报APP源码这个项目值得先看结构再动手。它不是只跑通几个页面的玩具Demo而是把多城市滑动切换、侧边栏城市管理、天气数据请求、本地城市记忆串成了完整闭环评审分98分源码本地编译过可直接运行适合正在做毕设的计算机专业学生也适合拿来当课程设计或期末大作业。我拆这套项目时最在意的不是页面够不够花哨而是两件事第一网络请求和JSON解析是不是完整走了一遍而不是写死假数据糊弄答辩老师第二项目里引用的ViewPagerIndicator和SlidingMenu这两个第三方库能不能在现在的Android Studio版本里顺利编译。第一件事决定你对代码的理解深度第二件事决定你能不能在一小时内把项目跑到真机上。要提前说明的是这套项目基于旧版support库构建导入新版本Android Studio时需要在gradle.properties里关掉AndroidX这是最常见的翻车点我后面专门用一章讲清楚。2. 项目骨架从aar和aidl文件反推两个核心库的选型逻辑拿到一份毕设源码我习惯先不急着看src目录而是把libs目录和依赖文件过一遍。因为依赖列表能透出这个项目的技术选型和年代感比读代码更快。这份资源的文件列表里有两种关键文件aar库文件和aidl接口文件把它们拆开看整个项目的架构判断就八九不离十了。这套项目里出现频率最高的两个库是ViewPagerIndicator和SlidingMenu。前者负责多城市页面的滑动指示器后者负责侧边栏菜单。这两个都是老牌的第三方开源库在Google官方Material Design还没普及的年代几乎是Android应用做交互展示的标配。毕设选它们很合理实现成本低、视觉上有“多页面联动”的效果、答辩时还能讲清楚原理。2.1 ViewPagerIndicator与SlidingMenu两个旧库撑起的交互骨架ViewPagerIndicator解决的是“用户不知道当前在第几个城市页”的问题。天气APP通常用ViewPager承载多个城市页面左右滑动切换城市这时候如果页面顶部没有指示器用户滑到第三四个页面就迷失了方向感。ViewPagerIndicator的作用就是给ViewPager加一条可跟随滑动的指示线或图标让用户对“当前位置”有明确感知。SlidingMenu则是从屏幕边缘滑出的侧边栏典型用法是把城市管理入口、添加城市、关于页面等二级功能收进去。主界面保持干净只展示天气信息需要切换功能时从左侧滑出菜单。这个交互模型在当年的天气类App里很常见放在毕业设计里属于“功能完整度”的加分项因为答辩老师看到的不只是一个单一页面而是一套有组织的信息架构。这两个库在这份资源里是以aar包形式出现的说明它们已经被编译打包好了。aar和jar的区别值得说一下jar只包含class文件aar里面还带Android资源文件比如布局、清单、图片。直接把aar放进libs目录再在build.gradle里声明依赖就能用不需要自己下载源码重新编译省掉了不少第三方库兼容性折腾。2.2 文件清单逐项拆解aar和aidl各管哪一块文件类型作用ViewPagerIndecatorLibrary-debug.aarAndroid库页面指示器调试版ViewPagerIndecatorLibrary-release.aarAndroid库页面指示器发布版SlidingMenulibrary-debug.aarAndroid库侧边栏菜单调试版SlidingMenulibrary-release.aarAndroid库侧边栏菜单发布版MediaSessionCompat.aidlaidl接口媒体会话兼容层PlaybackStateCompat.aidlaidl接口播放状态兼容层MediaMetadataCompat.aidlaidl接口媒体元数据兼容层ResultReceiver.aidlaidl接口跨进程结果回调RatingCompat.aidlaidl接口媒体评分兼容层一个有用的判断点藏在debug和release两套aar里资源提供方把可调试版本和发布版本都打好了说明这个项目确实经过了完整构建流程不是那种从网上下个残缺工程交差的货。调试时用debug版本能看到库内部的日志输出打正式包时换release版本。再说那几个aidl文件。MediaSessionCompat、PlaybackStateCompat、MetadataCompat这一组都是媒体会话相关的接口乍看跟天气应用没什么关系。实际上它们大概率不是项目主动写的而是旧版android.support.v4库里media-compat模块的附属文件。这给我们透露出一个重要信号这套项目用的是旧支持库而不是AndroidX。这个判断直接决定了后面的导入策略。如果你用新版Android Studio默认新建工程项目会自动启用AndroidX这时候把这份源码导进来编译会报出一堆和support库冲突的错误。正确的做法是在gradle.properties里显式关掉AndroidX让项目保持原生的support库状态。2.3 数据流与模块划分一个天气页面的完整闭环把依赖关系理清之后项目的模块结构就浮出水面了。主界面是一个Activity里面放一个ViewPager每个页面是一个WeatherFragment对应一个城市。侧边栏SlidingMenu承载城市管理入口。数据层不复杂无非是网络请求、JSON解析、SharedPreferences存储三个部分典型的单Activity加多Fragment结构。数据流转路径通常是这样的MainActivity启动后先从SharedPreferences读取已保存的城市列表按城市列表创建对应数量的WeatherFragment再逐个发起网络请求获取天气数据数据回来后通过回调更新对应Fragment的UI。用户滑动ViewPager切换城市页时页面数据已经加载好了不需要重复请求下拉刷新时才会重新请求当前城市的天气。这套结构放在毕业设计里是够用的而且每个环节都能对应到Android课程里的知识点Activity生命周期、Fragment管理、网络编程、数据存储、第三方库集成。答辩老师问任何一个点你都能顺着往下讲不会出现“这里我不太清楚”的尴尬。3. 核心功能落地城市管理、天气解析与多页面切换的写法这一章把三个核心功能拆开写城市列表怎么存、天气数据怎么解析、ViewPager和指示器怎么绑定。这三个点也是后期改造项目时最常动刀子的地方。代码部分以这类毕业设计里最常见、最不容易被问倒的实现路径来写实际工程里可能有细节差异但大方向一致。3.1 城市列表存储SharedPreferences设计的三个关键点城市列表的存储看起来简单但有几个细节没处理好后面会出现重复城市、删除后无法恢复默认值这类小毛病。我这里直接用SharedPreferences存储逗号分隔的字符串而不是数据库理由是毕设阶段数据量极小用SQLite属于过度设计答辩时也增加解释成本。// CityManager.java城市列表的本地存储与管理 public class CityManager { private static final String PREF_NAME weather_pref; private static final String KEY_CITIES cities; private SharedPreferences sp; private static CityManager instance; // 单例保证多个页面访问的是同一份城市列表 private CityManager(Context context) { sp context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); } public static CityManager getInstance(Context context) { if (instance null) { instance new CityManager(context.getApplicationContext()); } return instance; } // 读取城市列表默认给三个常用城市 public ListString getCityList() { String str sp.getString(KEY_CITIES, 北京,上海,广州); return new ArrayList(Arrays.asList(str.split(,))); } // 添加城市去重后再写入 public void addCity(String city) { ListString list getCityList(); if (!list.contains(city)) { list.add(city); save(list); } } // 删除城市删空时保留最后一个 public void removeCity(String city) { ListString list getCityList(); list.remove(city); if (list.isEmpty()) { list.add(北京); } save(list); } private void save(ListString list) { String join TextUtils.join(,, list); sp.edit().putString(KEY_CITIES, join).apply(); } }逻辑说明城市列表用单例管理是因为主页面、添加城市页面、侧边栏都要操作这同一份数据单例能保证数据一致不至于在一个页面加了城市另一个页面看不到。存储格式用逗号分隔的字符串读写都是一行代码的事比序列化对象省事。参数说明默认城市写“北京,上海,广州”你可以换成自己所在地方便到学校演示。删除城市时做了一次为空保护防止用户把城市删光导致ViewPager没有页面可展示。save方法里用apply而不是commit因为apply是异步写盘不会阻塞UI线程。3.2 天气数据拉取HttpURLConnection加JSON解析的完整代码毕业设计里网络请求用什么框架我的习惯是能用原生HttpURLConnection就不用第三方库。OkHttp和Retrofit当然更现代但引入它们意味着答辩时你要多解释一层封装原理而原生写法每一行都是Android课程里教过的东西说出去心里有底。// WeatherFetcher.java天气数据请求与解析 public class WeatherFetcher { // 网络请求必须在子线程执行 public static void fetch(final String city, final Callback callback) { new Thread(new Runnable() { Override public void run() { try { String urlStr https://your-weather-api.com/weather?city URLEncoder.encode(city, UTF-8) keyYOUR_KEY; URL url new URL(urlStr); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(8000); conn.setReadTimeout(8000); conn.setRequestMethod(GET); int code conn.getResponseCode(); if (code 200) { String json readStream(conn.getInputStream()); Weather weather parseJson(json); callback.onSuccess(weather); } else { callback.onError(HTTP code: code); } conn.disconnect(); } catch (Exception e) { callback.onError(e.getMessage()); } } }).start(); } // JSON解析天气数据一般是嵌套结构 private static Weather parseJson(String jsonStr) throws JSONException { JSONObject root new JSONObject(jsonStr); JSONObject data root.getJSONObject(data); Weather weather new Weather(); weather.setCity(data.getString(city)); weather.setTemperature(data.getString(wendu)); JSONArray forecast data.getJSONArray(forecast); if (forecast.length() 0) { JSONObject today forecast.getJSONObject(0); weather.setHigh(today.getString(high)); weather.setLow(today.getString(low)); weather.setType(today.getString(type)); weather.setDate(today.getString(date)); } return weather; } }逻辑说明URLEncoder.encode是中文城市名转义必须做的一步不做的话URL里带中文会直接请求失败。回调接口把结果传回调用方但这里要特别注意回调是在子线程执行的不能在onSuccess里直接操作UI控件需要借助runOnUiThread或Handler切回主线程这是Android线程模型的基本约束。参数说明连接和读取超时都设8秒避免弱网条件下页面卡死。不同天气服务商的JSON结构差异很大parseJson里的路径要按你申请的服务商返回格式调整但套路是固定的先取根对象再取数据对象最后取数组里的当天预报。3.3 ViewPager与指示器绑定手势切换和指示器跟随的实现城市列表有了数据接口有了接下来就是把多个天气页面装进ViewPager并让指示器跟随滑动。这里最容易踩的坑是FragmentManager的版本匹配问题后面避坑章节会细说先看标准写法。// MainActivity.javaViewPager和指示器的绑定 ViewPager mPager findViewById(R.id.view_pager); IndicatorView mIndicator findViewById(R.id.indicator); ListString cityList CityManager.getInstance(this).getCityList(); ListFragment fragments new ArrayList(); for (String city : cityList) { fragments.add(WeatherFragment.newInstance(city)); } // 注意这里的FragmentPagerAdapter构造方法和support库版本强相关 MyPagerAdapter adapter new MyPagerAdapter(getSupportFragmentManager(), fragments); mPager.setAdapter(adapter); mPager.setOffscreenPageLimit(cityList.size() - 1); // 指示器库内部会自动监听ViewPager的滑动事件 mIndicator.setViewPager(mPager);逻辑说明核心就两行mPager.setAdapter把Fragment列表交给ViewPagermIndicator.setViewPager让指示器和ViewPager建立监听关系。指示器库内部会注册OnPageChangeListener自动更新指示位置所以不需要手动写滚动监听。参数说明setOffscreenPageLimit设置预加载页面数默认值是1意味着左右只保留一个页面。如果城市列表超过3个切成非相邻页面时会重新创建Fragment出现短暂空白。我一般会把它设成cityList.size() - 1一次性把所有城市页面都常驻内存切换时零延迟。代价是内存占用高一些但在城市数量不超过10个的前提下完全可接受。4. 导入与运行从Android Studio打开到真机跑通的完整链路下载源码之后最焦虑的阶段就是导入这一步。很多人卡在Gradle同步上进度条转半天然后报错。这套项目的特殊性在于它基于旧support库所以导入策略跟新建一个现代项目完全不同。我按自己的导入顺序写一遍。4.1 版本对齐Gradle插件、SDK和support库的匹配原则首先明确一个判断项目的support库版本决定了compileSdkVersion不能随便拉高。support库28.0.0要求compileSdkVersion至少是28如果你用新电脑装的新版Android Studio默认编译SDK 35旧support库直接编译不过。常见做法是把compileSdkVersion控制在28minSdkVersion设为19targetSdkVersion保持26或28。// app/build.gradle 核心配置 android { compileSdkVersion 28 defaultConfig { applicationId com.example.weather minSdkVersion 19 targetSdkVersion 26 versionCode 1 versionName 1.0 } }逻辑说明这套配置不是随便写的。compileSdkVersion 28对应support库28.0.0的编译环境targetSdkVersion 26是为了避开通话权限、通知权限等运行时权限新规减少真机调试时的权限弹窗干扰。如果你有洁癖想用更高版本后果可能是support库需要整体升级工作量会膨胀。Gradle插件版本上网上经常有人搜“build:gradle:7.0.4下载哪个版本比较号”这类问题其实关键不是Gradle插件号本身而是它和Android Studio大版本、JDK版本的匹配关系。我一般建议用AS 4.2左右的配套插件版本对应Gradle 6.7不要一上来就拉到最新反而容易触发仓库解析问题。提示gradle.properties里必须保证android.useAndroidXfalse否则编译会报support库和AndroidX冲突。这是这套项目导入时最重要的一个开关。4.2 aar依赖引入libs目录与build.gradle配置下载的压缩包里已经带了四个aar文件直接放在app/libs目录下。然后打开app/build.gradle在dependencies块里声明本地依赖。repositories { flatDir { dirs libs } } dependencies { implementation files(libs/ViewPagerIndecatorLibrary-debug.aar) implementation files(libs/SlidingMenulibrary-debug.aar) }逻辑说明flatDir配置告诉Gradle去libs目录下找本地aar包。implementation files是精确指定文件路径如果libs目录下aar文件较多也可以简写成implementation fileTree(dir: libs, include: [*.aar])。两个库在调试阶段用debug版本能输出更详细的日志到打正式包之前再换成release版本。参数说明这里有个细节如果项目里同时存在多个aar包且它们携带了相同的资源文件比如都定义了app_name字符串打包时可能报资源合并冲突。遇到这种问题不用慌后面避坑章节给出排查思路。4.3 真机运行签名、USB调试与首次启动的处理模拟器上跑天气项目最尴尬的问题就是定位。天气服务如果按IP定位模拟器给出的位置经常偏差巨大而且Gradle同步、APK安装的速度也远不如真机。我的习惯是直接用USB真机调试。// app/build.gradle 签名配置 signingConfigs { release { storeFile file(weather.jks) storePassword 123456 keyAlias weather keyPassword 123456 } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile(proguard-android.txt), proguard-rules.pro } }逻辑说明release签名是为了打正式安装包时用。minifyEnabled这里保持false很重要毕设阶段不建议开混淆因为混淆规则没配好的话第三方库的类会被误伤运行时直接崩溃排查起来非常耗时。真机调试的操作步骤就三步手机开启开发者选项和USB调试模式用数据线连电脑Android Studio工具栏选择设备后点击Run。首次安装时如果Android版本高于6.0APP里用到定位和通知的地方会弹运行时权限请求记得点允许。如果APP启动后城市页是空白的优先检查网络权限是否被拒很多人卡在这里。5. 避坑手册编译报错、滑动冲突与数据源失效的排查记录这一章是我实际改这类旧项目时踩过的坑每条都是先给现象再分析原因最后说解决路径。如果你在导入或运行过程中卡住优先对照这一章排查。5.1 坑1MediaSessionCompat类找不到其实是AndroidX横插一脚现象编译报错提示找不到MediaSessionCompat或者相关的aidl接口符号有时还会附带一堆manifest merger错误。原因新版Android Studio创建的工程默认在gradle.properties里开启了android.useAndroidXtrue导致整个项目以AndroidX库为基础。而这套源码用的是旧support库里面的MediaSessionCompat.aidl、PlaybackStateCompat.aidl这些文件就是旧版本的产物跟AndroidX对应的类路径不一致编译时自然找不到。解决打开gradle.properties把android.useAndroidX改成false同时检查build.gradle里没有androidx开头的依赖。如果项目里有其他模块被自动迁移成了AndroidX需要手动改回来或者直接删掉重加。这个坑的典型特征是“换了电脑就编译不过”本质是环境配置差异不是代码问题。5.2 坑2指示器库和AndroidX的Fragment类型冲突现象编译能通过但运行到主界面时崩溃报ClassCastException或者Fragment页面空白不显示。原因ViewPagerIndicator和SlidingMenu这类老库内部持有的Fragment和FragmentManager是基于旧support库的。如果你的工程被AndroidX污染了一部分比如getSupportFragmentManager返回的是AndroidX的FragmentManager而第三方库传进去的是旧support库的Fragment两者类型不一致运行时就会炸。解决这一步没有捷径要么全项目保持旧的support库体系要么把整个工程迁移到AndroidX。迁移到AndroidX的工作量不是改几个import语句那么简单所有Fragment的写法、第三方库的版本都要跟着换。对毕设来说最稳妥的做法是按项目的原始配置导入不要动依赖体系的根基。5.3 坑3天气数据解析崩先看原始响应再找代码问题现象解析天气数据时抛JSONException或者所有字段解析结果是null界面显示空数据。原因天气数据源换地址了、apikey失效了、请求参数有变化、返回结构嵌套层级变了这四个原因里任何一个都能让解析代码崩溃。我遇到最多的情况是学生图省事代码里写死了一个免费接口传到GitHub后被其他人刷爆第二天接口就挂了。解决拿到这种项目第一步不是看解析代码而是把原始响应打出来看。在HttpURLConnection拿到流之后先存一份完整字符串再丢给解析器或者直接在子线程里Log输出前500个字符。看到返回内容是{error:invalid key}这类提示就不用费劲调解析逻辑了问题在请求端。换成自己注册的天气服务商按对方文档改好参数解析路径对应调一下即可。5.4 坑4SlidingMenu和ViewPager互相抢手势现象在天气页面左右滑动时经常误触弹出侧边栏或者反过来想滑出侧边栏却变成了切换城市页。原因SlidingMenu和ViewPager都处理水平方向的触摸事件两个组件在事件拦截上相互竞争。SlidingMenu默认的触摸模式是FULLSCREEN整屏都能触发侧边栏滑出这就把ViewPager的正常滑动空间压缩了。这类问题在真机上特别明显模拟器上因为鼠标操作方式不同反而不容易暴露。解决调整SlidingMenu的触摸模式把整屏触发改成边缘触发。// 设置SlidingMenu触摸模式 SlidingMenu menu getSlidingMenu(); menu.setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN); menu.setTouchModeBehind(SlidingMenu.TOUCHMODE_MARGIN);逻辑说明TOUCHMODE_MARGIN表示只在屏幕边缘大约几十像素的范围内响应侧边栏手势页面主体区域的滑动操作全部交给ViewPager处理。参数的副作用是侧边栏呼出灵敏度下降刚上手可能会觉得“使不上劲”但实际使用一天就适应了。如果更喜欢整屏呼出也可以保留FULLSCREEN模式代价是ViewPager的翻页手势会有延迟。鱼和熊掌不可兼得总得取舍。5.5 坑5Gradle插件版本乱调构建突然失败现象Gradle同步时提示Failed to resolve: com.android.support或者构建到一半直接报错退出网上搜到的解决方案互相矛盾。原因新版Gradle插件对仓库解析策略变了。老项目写在build.gradle里的依赖可能只声明了jcenter仓库而新版本Android Studio默认仓库列表里面没有jcenter或者顺序不对导致旧依赖拉不下来。如果把Gradle插件版本拉到7.0以上一些API的写法也变了老代码里用的方法可能被移除或标记deprecated。解决我一般先把Gradle插件版本固定在一个保守的区间不要追新同时把仓库配置补齐。// settings.gradle 或 build.gradle 里的仓库配置 repositories { google() mavenCentral() jcenter() // 旧依赖可能只发布在jcenter上 }逻辑说明给老项目补仓库配置是生命周期极短但有效的修复方案。jcenter虽然已经是只读状态但旧的依赖包还在上面能正常拉取。如果你遇到Failed to resolve的报错先在仓库列表里补上jcenter再试一次比直接把依赖版本改来改去靠谱得多。6. 把Demo改造成能迭代的产品加定位、换网络库与验证清单源码能跑通只是第一步答辩前后你大概率想给它加点自己的东西避免和同学撞题。我建议从三个方向入手都属于改动范围不大、但答辩时能讲出亮点的小改造。6.1 用OkHttp替换HttpURLConnection原生HttpURLConnection的代码能跑但写成产品级的网络层还是有点勉强。OkHttp的优势是连接池复用、自动重试、拦截器机制替换成本很低。把WeatherFetcher里的new Thread换成OkHttp的异步回调代码量反而更少。要注意的是用了OkHttp意味着要引入依赖ApiService和网络封装的组织方式也要顺便理一下别把所有请求都堆在Activity里。6.2 接入定位权限与自动刷新天气APP不加定位就少了灵魂。改造思路是申请ACCESS_COARSE_LOCATION权限拿到经纬度后用逆地理编码转城市名再把城市名写进CityManager。自动刷新可以用AlarmManager定时任务每隔半小时请求一次当前城市天气并更新UI。这一步能讲的东西很多比如定位权限运行时申请流程、AlarmManager的省电策略都是答辩老师喜欢追问的点。6.3 改造后的验证清单验证项预期结果首次安装启动弹定位权限请求授权后自动定位到当前城市切换城市页指示器跟随滑动页面数据与城市名一致下拉刷新当前城市天气数据更新UI无闪烁侧边栏添加城市新城市页出现在ViewPager末尾删除后正常移除锁屏半小时后解锁天气数据自动刷新时间戳更新做完这三个改造这份源码就不再是照搬的Demo而是一个有个人痕迹的完整作品。那以后我接手任何一份毕业设计源码都强制自己先看依赖文件再动手改代码特别是gradle.properties里的AndroidX开关和libs目录下的aar列表——这两个地方决定了这个项目的“体质”方向错了后面全白干。希望你下载这套源码后别只做到“跑通”就收手按上面的验证清单过一遍把每一步的为什么吃透这才对得起这份接近满分的资源。希望帮到你。本文还有配套的精品资源点击获取
返回列表