├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── coolweather │ │ └── android │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── litepal.xml │ ├── java │ │ └── com │ │ │ └── coolweather │ │ │ └── android │ │ │ ├── db │ │ │ ├── City.java │ │ │ ├── County.java │ │ │ └── Province.java │ │ │ ├── gson │ │ │ ├── AQI.java │ │ │ ├── Basic.java │ │ │ ├── DailyForecast.java │ │ │ ├── Now.java │ │ │ ├── Suggestion.java │ │ │ └── Weather.java │ │ │ ├── service │ │ │ └── AutoUpdateWeatherService.java │ │ │ ├── ui │ │ │ ├── MainActivity.java │ │ │ ├── WeatherActivity.java │ │ │ ├── fragment │ │ │ │ └── ChooseAreaFragment.java │ │ │ └── widget │ │ │ │ └── TimeoutProgressBar.java │ │ │ └── util │ │ │ ├── BitmapHelper.java │ │ │ ├── HttpUtil.java │ │ │ ├── Utility.java │ │ │ └── listener │ │ │ └── TimeOutListener.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xxhdpi │ │ ├── cloudy1.jpg │ │ ├── cloudy2.jpg │ │ ├── cloudy3.jpg │ │ ├── ic_back.png │ │ ├── ic_forward.png │ │ ├── location.png │ │ ├── overcast1.jpg │ │ ├── panda.png │ │ ├── rain1.jpg │ │ ├── rain2.jpg │ │ ├── settings.png │ │ ├── snow1.jpg │ │ ├── snow2.jpg │ │ ├── sunny1.jpg │ │ ├── sunny2.jpg │ │ ├── thunder1.jpg │ │ ├── thunder2.jpg │ │ └── timeout_progress_bg.png │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_weather.xml │ │ ├── aqi.xml │ │ ├── choose_area.xml │ │ ├── forcast.xml │ │ ├── forecast_item.xml │ │ ├── nav_header.xml │ │ ├── now.xml │ │ ├── suggestion.xml │ │ ├── timeout_progress_bar.xml │ │ └── weather_title.xml │ │ ├── menu │ │ └── nav_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_weather.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── coolweather │ └── android │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coolweather 2 | 我的第一个完整的安卓项目:酷欧天气 3 | 4 | ## 项目需求分析 5 | 1. 可以罗列全国所有的省、市、县 -- (已实现) 6 | 2. 可以查看全国任意城市的天气信息 -- (已实现) 7 | 3. 可以自由切换城市,去查看其他城市的天气信息 -- (已实现) 8 | 4. 可以手动更新以及后台自动更新天气信息 -- (已实现) 9 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "com.coolweather.android" 7 | minSdkVersion 22 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | targetCompatibility 1.8 21 | sourceCompatibility 1.8 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | // 此库添加了对操作栏用户界面设计模式的支持。此库包含对 Material Design 用户界面实现的支持 28 | implementation 'com.android.support:appcompat-v7:26.1.0' 29 | 30 | implementation 'com.squareup.okhttp3:okhttp:3.11.0' // 网络请求库 31 | implementation 'org.litepal.android:core:2.0.0' // SQLite数据库框架 32 | implementation 'com.google.code.gson:gson:2.8.5' // 解析JSON格式数据的库 33 | implementation 'com.github.bumptech.glide:glide:3.7.0' // 优秀的第三方图片加载库 34 | 35 | implementation 'com.android.support:design:26.1.0' // 添加 Material Design library 36 | implementation 'de.hdodenhof:circleimageview:2.1.0' // 添加 显示圆形图片【第三方开源库】 37 | implementation 'com.android.support:palette-v7:26.1.0' // Android官方提供的提取图片颜色的调色板库 38 | 39 | // 添加 ButterKnife 第三方库 40 | implementation 'com.jakewharton:butterknife:8.8.1' 41 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 42 | 43 | testImplementation 'junit:junit:4.12' 44 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 45 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 46 | } 47 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/coolweather/android/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.coolweather.android", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/assets/litepal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/db/City.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.db; 2 | 3 | import org.litepal.crud.LitePalSupport; 4 | 5 | /** 6 | * 市表的model,市的实体类 7 | */ 8 | public class City extends LitePalSupport{ 9 | private int id; 10 | 11 | private String cityName; 12 | 13 | private int cityCode; 14 | 15 | private int provinceCode; 16 | 17 | public int getId() { 18 | return id; 19 | } 20 | 21 | public void setId(int id) { 22 | this.id = id; 23 | } 24 | 25 | public String getCityName() { 26 | return cityName; 27 | } 28 | 29 | public void setCityName(String cityName) { 30 | this.cityName = cityName; 31 | } 32 | 33 | public int getCityCode() { 34 | return cityCode; 35 | } 36 | 37 | public void setCityCode(int cityCode) { 38 | this.cityCode = cityCode; 39 | } 40 | 41 | public int getProvinceCode() { 42 | return provinceCode; 43 | } 44 | 45 | public void setProvinceCode(int provinceCode) { 46 | this.provinceCode = provinceCode; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/db/County.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.db; 2 | 3 | import org.litepal.crud.LitePalSupport; 4 | 5 | public class County extends LitePalSupport { 6 | private int id; 7 | 8 | private String countyName; 9 | 10 | private int countyCode; 11 | 12 | private int cityCode; 13 | 14 | /** 天气代码:用于查询天气信息 */ 15 | private String weatherId; 16 | 17 | public int getId() { 18 | return id; 19 | } 20 | 21 | public void setId(int id) { 22 | this.id = id; 23 | } 24 | 25 | public String getCountyName() { 26 | return countyName; 27 | } 28 | 29 | public void setCountyName(String countyName) { 30 | this.countyName = countyName; 31 | } 32 | 33 | public int getCountyCode() { 34 | return countyCode; 35 | } 36 | 37 | public void setCountyCode(int countyCode) { 38 | this.countyCode = countyCode; 39 | } 40 | 41 | public int getCityCode() { 42 | return cityCode; 43 | } 44 | 45 | public void setCityCode(int cityCode) { 46 | this.cityCode = cityCode; 47 | } 48 | 49 | public String getWeatherId() { 50 | return weatherId; 51 | } 52 | 53 | public void setWeatherId(String weatherId) { 54 | this.weatherId = weatherId; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/db/Province.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.db; 2 | 3 | import org.litepal.crud.DataSupport; 4 | import org.litepal.crud.LitePalSupport; 5 | 6 | /** 7 | * 省份表的model,省份实体类 8 | * @author Administrator 9 | */ 10 | public class Province extends LitePalSupport{ 11 | private int id; 12 | 13 | private String provinceName; 14 | 15 | private int provinceCode; 16 | 17 | public int getId() { 18 | return id; 19 | } 20 | 21 | public void setId(int id) { 22 | this.id = id; 23 | } 24 | 25 | public String getProvinceName() { 26 | return provinceName; 27 | } 28 | 29 | public void setProvinceName(String name) { 30 | this.provinceName = name; 31 | } 32 | 33 | public int getProvinceCode() { 34 | return provinceCode; 35 | } 36 | 37 | public void setProvinceCode(int code) { 38 | this.provinceCode = code; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/gson/AQI.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.gson; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * 空气质量指数 7 | */ 8 | public class AQI { 9 | public City city; 10 | 11 | public class City { 12 | public String aqi; 13 | 14 | public String pm25; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/gson/Basic.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.gson; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * 城市基本信息 7 | */ 8 | public class Basic { 9 | /** 城市名 */ 10 | @SerializedName("city") 11 | public String cityName; 12 | 13 | /** 天气ID */ 14 | @SerializedName("id") 15 | public String weatherId; 16 | 17 | public Update update; 18 | 19 | public class Update { 20 | /** 天气的更新时间(当地时间) */ 21 | @SerializedName("loc") 22 | public String updateTime; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/gson/DailyForecast.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.gson; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | /** 5 | * 天气情况预测 6 | */ 7 | public class DailyForecast { 8 | /** 日期 */ 9 | public String date; 10 | 11 | /** 温度情况 */ 12 | @SerializedName("tmp") 13 | public Temperature temperature; 14 | 15 | /** 天气情况 */ 16 | @SerializedName("cond") 17 | public More more; 18 | 19 | public class More { 20 | /** 天气描述 */ 21 | @SerializedName("txt_d") 22 | public String info; 23 | } 24 | 25 | public class Temperature { 26 | public String max; 27 | public String min; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/gson/Now.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.gson; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | /** 5 | * 当天的综合天气情况 6 | */ 7 | public class Now { 8 | /** 温度 */ 9 | @SerializedName("tmp") 10 | public String temperature; 11 | 12 | /** 天气情况 */ 13 | @SerializedName("cond") 14 | public More more; 15 | 16 | public class More { 17 | /** 天气描述 */ 18 | @SerializedName("txt") 19 | public String info; 20 | 21 | /** 天气代码 */ 22 | public String code; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/gson/Suggestion.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.gson; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | /** 5 | * 依据天气情况,给出的日常生活建议 6 | */ 7 | public class Suggestion { 8 | /** 天气舒适度描述 */ 9 | @SerializedName("comf") 10 | public Comfort comfort; 11 | 12 | /** 洗车建议 */ 13 | @SerializedName("cw") 14 | public CarWash carWash; 15 | 16 | /** 运动建议 */ 17 | public Sport sport; 18 | 19 | public class Comfort { 20 | @SerializedName("txt") 21 | public String info; 22 | } 23 | 24 | public class CarWash { 25 | @SerializedName("txt") 26 | public String info; 27 | } 28 | 29 | public class Sport { 30 | @SerializedName("txt") 31 | public String info; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/gson/Weather.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.gson; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.List; 6 | import java.util.concurrent.BlockingDeque; 7 | 8 | /** 9 | * 天气情况实体类 10 | **/ 11 | public class Weather { 12 | /** 13 | * 和风天气接口返回数据的状态码: 14 | * ok 数据正常 15 | * invalid key 错误的key,请检查你的key是否输入以及是否输入有误 16 | * unknown location 未知或错误城市/地区 17 | * no data for this location 该城市/地区没有你所请求的数据 18 | * no more requests 超过访问次数,需要等到当月最后一天24点(免费用户为当天24点)后进行访问次数的重置或升级你的访问量 19 | * param invalid 参数错误,请检查你传递的参数是否正确 20 | * too fast 超过限定的QPM,请参考QPM说明 21 | * dead 无响应或超时,接口服务异常请联系我们 22 | * permission denied 无访问权限,你没有购买你所访问的这部分服务 23 | * sign error 签名错误,请参考签名算法 24 | **/ 25 | public String status; 26 | 27 | public Basic basic; 28 | 29 | public AQI aqi; 30 | 31 | public Now now; 32 | 33 | public Suggestion suggestion; 34 | 35 | /** 未来几天天气情况预测 */ 36 | @SerializedName("daily_forecast") 37 | public List dailyForecastList; 38 | 39 | @Override 40 | public String toString() { 41 | StringBuilder builder = new StringBuilder(); 42 | builder.append("heWeather:[{\n"); 43 | builder.append("status:" +status+"\n"); 44 | builder.append("basic:{\n cityName:"+basic.cityName+"\n weather_id:"+basic.weatherId+ 45 | "\n updateTime:"+basic.update.updateTime+"\n}\n"); 46 | builder.append("aqi:{\n"+" aqi:"+aqi.city.aqi+"\n pm2.5:"+aqi.city.pm25+"\n}\n"); 47 | builder.append("now:{\n 温度:"+now.temperature+"℃"+"\n" 48 | +" 天气:"+ now.more.info + "\n" 49 | +" 天气代码:"+ now.more.code +"\n}\n"); 50 | 51 | builder.append("daily_forecast:[\n"); 52 | for (DailyForecast dailyForecast : dailyForecastList) { 53 | builder.append(" {\n"); 54 | builder.append(" date:" + dailyForecast.date+"\n"); 55 | builder.append(" 天气:" + dailyForecast.more.info + "\n"); 56 | builder.append(" 最高温度:" + dailyForecast.temperature.max + "℃\n"); 57 | builder.append(" 最低温度:" + dailyForecast.temperature.min + "℃\n"); 58 | builder.append(" }\n"); 59 | } 60 | builder.append("}\n"); 61 | 62 | builder.append("suggestion:{\n"); 63 | builder.append(" 舒适度:"+suggestion.comfort.info+"\n"); 64 | builder.append(" 运动建议:"+suggestion.sport.info+"\n"); 65 | builder.append(" 洗车建议:"+suggestion.carWash.info+"\n"); 66 | builder.append("}\n"); 67 | 68 | builder.append("}]"); 69 | 70 | return builder.toString(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/service/AutoUpdateWeatherService.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.service; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.PendingIntent; 5 | import android.app.Service; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.SharedPreferences; 9 | import android.os.IBinder; 10 | import android.os.SystemClock; 11 | import android.preference.PreferenceManager; 12 | import android.support.v4.app.AlarmManagerCompat; 13 | import android.util.Log; 14 | 15 | import com.coolweather.android.gson.Weather; 16 | import com.coolweather.android.util.HttpUtil; 17 | import com.coolweather.android.util.Utility; 18 | 19 | import java.io.IOException; 20 | 21 | import okhttp3.Call; 22 | import okhttp3.Callback; 23 | import okhttp3.Response; 24 | 25 | public class AutoUpdateWeatherService extends Service { 26 | private long lastTime; 27 | private long currentTime; 28 | @Override 29 | public void onCreate() { 30 | super.onCreate(); 31 | lastTime = SystemClock.elapsedRealtime(); 32 | } 33 | 34 | @Override 35 | public IBinder onBind(Intent intent) { 36 | return null; 37 | } 38 | 39 | @Override 40 | public int onStartCommand(Intent intent, int flags, int startId) { 41 | currentTime = SystemClock.elapsedRealtime(); 42 | Log.d("onAutoUpdateService", "开始进行一次定时任务,时间间隔:"+((currentTime-lastTime)/1000.0)+"s"); 43 | updateWeather(); 44 | updateBingPic(); 45 | setAlarmTask(); 46 | return super.onStartCommand(intent, flags, startId); 47 | } 48 | 49 | /** 设置定时任务 */ 50 | private void setAlarmTask() { 51 | AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 52 | // 定时任务触发事件间隔,暂设定时间间隔3小时 53 | int intervalTime = 3 * 3600 * 1000; 54 | long triggerAtTime = SystemClock.elapsedRealtime() + intervalTime; 55 | Intent intent = new Intent(this, AutoUpdateWeatherService.class); 56 | PendingIntent pi = PendingIntent.getService(this,0,intent,0); 57 | alarmManager.cancel(pi); 58 | 59 | lastTime = currentTime; 60 | // 设定定时任务 61 | alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi); 62 | } 63 | 64 | private void updateBingPic() { 65 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 66 | String weatherId = sp.getString("bing_pic", null); 67 | if (weatherId != null) { 68 | String bingPicUrl = "http://guolin.tech/api/bing_pic"; 69 | HttpUtil.sendOkHttpRequest(bingPicUrl, new Callback() { 70 | @Override 71 | public void onFailure(Call call, IOException e) { 72 | Log.e("onUpdateBingPic", "网络连接异常"); 73 | e.printStackTrace(); 74 | } 75 | 76 | @Override 77 | public void onResponse(Call call, Response response) throws IOException { 78 | String bingPicLink = response.body().string(); 79 | // 缓存Bing每日一图片的下载链接 80 | sp.edit().putString("bing_pic",bingPicLink).apply(); 81 | } 82 | }); 83 | } 84 | } 85 | 86 | private void updateWeather() { 87 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 88 | String weatherId = sp.getString("weather_id", null); 89 | if (weatherId != null) { 90 | String weatherUrl = "http://guolin.tech/api/weather?cityid=" + weatherId 91 | + "&key=c72d9f8149ac436da648ac0e43211edd"; 92 | HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() { 93 | @Override 94 | public void onFailure(Call call, IOException e) { 95 | Log.w("onUpdateWeather", "网络连接异常"); 96 | e.printStackTrace(); 97 | } 98 | 99 | @Override 100 | public void onResponse(Call call, Response response) throws IOException { 101 | String weatherInfo = response.body().string(); 102 | Weather weather = Utility.handleWeatherResponse(weatherInfo); 103 | if (weather != null & "ok".equals(weather.status)) { 104 | // 缓存天气信息 105 | sp.edit().putString("heWeather", weatherInfo).apply(); 106 | } 107 | } 108 | }); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.ui; 2 | 3 | import android.content.Intent; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.os.Bundle; 8 | 9 | import com.coolweather.android.R; 10 | import com.coolweather.android.gson.Weather; 11 | import com.coolweather.android.util.Utility; 12 | 13 | public class MainActivity extends AppCompatActivity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_main); 19 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 20 | // 有缓存的天气信息,则直接跳转到天气显示界面 21 | String weatherInfo = sp.getString("heWeather", null); 22 | if ( weatherInfo != null) { 23 | Weather weather = Utility.handleWeatherResponse(weatherInfo); 24 | if (weather != null) { 25 | Intent intent = new Intent(this, WeatherActivity.class); 26 | intent.putExtra("weather_id", weather.basic.weatherId); 27 | startActivity(intent); 28 | finish(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/ui/WeatherActivity.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.ui; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.Color; 10 | import android.os.Build; 11 | import android.os.Bundle; 12 | import android.preference.PreferenceManager; 13 | import android.support.annotation.Nullable; 14 | import android.support.design.widget.NavigationView; 15 | import android.support.v4.app.FragmentManager; 16 | import android.support.v4.app.FragmentTransaction; 17 | import android.support.v4.content.ContextCompat; 18 | import android.support.v4.widget.DrawerLayout; 19 | import android.support.v4.widget.SwipeRefreshLayout; 20 | import android.support.v7.app.AppCompatActivity; 21 | import android.support.v7.graphics.Palette; 22 | import android.util.Log; 23 | import android.view.Gravity; 24 | import android.view.LayoutInflater; 25 | import android.view.MotionEvent; 26 | import android.view.View; 27 | import android.widget.Button; 28 | import android.widget.FrameLayout; 29 | import android.widget.ImageView; 30 | import android.widget.LinearLayout; 31 | import android.widget.TextView; 32 | import android.widget.Toast; 33 | 34 | import com.bumptech.glide.Glide; 35 | import com.bumptech.glide.load.resource.bitmap.CenterCrop; 36 | import com.coolweather.android.R; 37 | import com.coolweather.android.gson.DailyForecast; 38 | import com.coolweather.android.gson.Weather; 39 | import com.coolweather.android.service.AutoUpdateWeatherService; 40 | import com.coolweather.android.ui.fragment.ChooseAreaFragment; 41 | import com.coolweather.android.ui.widget.TimeoutProgressBar; 42 | import com.coolweather.android.util.BitmapHelper; 43 | import com.coolweather.android.util.HttpUtil; 44 | import com.coolweather.android.util.Utility; 45 | 46 | import java.io.IOException; 47 | import java.util.Random; 48 | 49 | import okhttp3.Call; 50 | import okhttp3.Callback; 51 | import okhttp3.Response; 52 | 53 | public class WeatherActivity extends AppCompatActivity { 54 | public final Context mContext = WeatherActivity.this; 55 | public final String TAG = "onWeatherActivity"; 56 | 57 | private String mWeatherId; 58 | 59 | private LinearLayout weatherLayout; 60 | 61 | TextView areaTitleTv; 62 | 63 | TextView nowTimeTv; 64 | 65 | TextView temperatureTv; 66 | 67 | TextView weatherInfoTv; 68 | 69 | TextView aqiTv; 70 | 71 | TextView pm25Tv; 72 | 73 | TextView comfortTv; 74 | 75 | TextView carWashTv; 76 | 77 | TextView sportTv; 78 | 79 | LinearLayout forecastLayout; 80 | 81 | public SwipeRefreshLayout swipeRefreshLayout; 82 | 83 | private SharedPreferences sp; 84 | 85 | private ImageView bingPicImg; 86 | 87 | private static final String BingPicAddress = "http://guolin.tech/api/bing_pic"; 88 | 89 | public DrawerLayout drawerLayout; 90 | public FrameLayout sideLayout; 91 | public NavigationView mNavView; 92 | 93 | private TimeoutProgressBar mProgressBar; 94 | 95 | private int picResId; 96 | private Bitmap weatherPic; 97 | 98 | @Override 99 | protected void onCreate(@Nullable Bundle savedInstanceState) { 100 | super.onCreate(savedInstanceState); 101 | if (Build.VERSION.SDK_INT >= 21) { 102 | // 将背景图和状态栏融合到一起 103 | View decorView = getWindow().getDecorView(); 104 | decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 105 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 106 | getWindow().setStatusBarColor(Color.TRANSPARENT); 107 | } 108 | setContentView(R.layout.activity_weather); 109 | 110 | initData(); 111 | 112 | initEvent(); 113 | 114 | // 加载天气信息前,先隐藏天气界面布局,优化视觉效果 115 | weatherLayout.setVisibility(View.INVISIBLE); 116 | 117 | //loadBgPic(); 118 | 119 | loadWeatherInfo(); 120 | 121 | weatherLayout.setVisibility(View.VISIBLE); 122 | 123 | } 124 | 125 | @Override 126 | public void onWindowFocusChanged(boolean hasFocus) { 127 | super.onWindowFocusChanged(hasFocus); 128 | if (hasFocus) { 129 | Log.w(TAG, "获得焦点"); 130 | setTemperatureTextColor(); 131 | } 132 | } 133 | 134 | 135 | /** 136 | * 设置显示温度文本颜色 137 | */ 138 | private void setTemperatureTextColor() { 139 | new Thread(() -> { 140 | // 默认字体颜色为纯白色 141 | int color = 0xffffffff; 142 | 143 | int[] coordinate = new int[10]; 144 | coordinate = getViewCoordinate(temperatureTv); 145 | 146 | if (weatherPic == null) { 147 | weatherPic = BitmapFactory.decodeResource(getResources(), picResId); 148 | View decorview = getWindow().getDecorView(); 149 | int picWidth = decorview.getWidth(); 150 | int picHeight = decorview.getHeight(); 151 | Log.d(TAG, "图片缩放宽度:" + picWidth + " 图片缩放高度:" + picHeight); 152 | // 设定缩放图片的宽高和手机屏幕宽高尺寸一致 153 | weatherPic = BitmapHelper.resizePicture(weatherPic, picWidth, picHeight); 154 | } 155 | if (weatherPic != null) { 156 | // 分别获取view左上角、右上角、右下角、左下角、中心点的坐标对应的颜色值 157 | int ltColor = weatherPic.getPixel(coordinate[0], coordinate[1]); 158 | 159 | int rtColor = weatherPic.getPixel(coordinate[2], coordinate[3]); 160 | 161 | int rbColor = weatherPic.getPixel(coordinate[4], coordinate[5]); 162 | 163 | int lbColor = weatherPic.getPixel(coordinate[6], coordinate[7]); 164 | 165 | int centerColor = weatherPic.getPixel(coordinate[8], coordinate[9]); 166 | 167 | // 又5个点对应的颜色值,这里取均值权重 weight = 0.2f 168 | float weight = 0.15f; 169 | 170 | int blue = (int) ((ltColor & 0xff) * weight + (rtColor & 0xff) * weight 171 | + (rbColor & 0xff) * weight + (lbColor & 0xff) * weight + (centerColor & 0xff) * 0.4f + 0.5f); 172 | 173 | int green = (int) (((ltColor >> 8) & 0xff) * weight + ((rtColor >> 8) & 0xff) * weight 174 | + ((rbColor >> 8) & 0xff) * weight + ((lbColor >> 8) & 0xff) * weight 175 | + ((centerColor >> 8) & 0xff) * 0.4f + 0.5f); 176 | 177 | int red = (int) (((ltColor >> 16) & 0xff) * weight + ((rtColor >> 16) & 0xff) * weight 178 | + ((rbColor >> 16) & 0xff) * weight + ((lbColor >> 16) & 0xff) * weight 179 | + ((centerColor >> 16) & 0xff) * 0.4f + 0.5f); 180 | 181 | int alpha = (int) ((ltColor >>> 24) * weight + (rtColor >>> 24) * weight 182 | + (rbColor >>> 24) * weight + (lbColor >>> 24) * weight 183 | + (centerColor >>> 24) * 0.4f + 0.5f); 184 | Log.d(TAG, "取色:alpha:" + alpha + " red:" + red + " green:" + green + " blue:" + blue); 185 | 186 | // 计算补色 187 | blue = 255 - blue; 188 | green = 255 - green; 189 | red = 255 - red; 190 | // alpha = 255 - alpha; 191 | Log.d(TAG, "补色:alpha:" + alpha + " red:" + red + " green:" + green + " blue:" + blue); 192 | // 合并颜色值 193 | color = (alpha << 24) | (red << 16) | (green << 8) | blue; 194 | Log.d(TAG, "合并的颜色值:" + Integer.toHexString(color)); 195 | } else { 196 | Log.w(TAG, "bitmap为null,默认设置view文本颜色为纯白色"); 197 | } 198 | // 设置显示温度文本字体颜色 199 | int finalColor = color; 200 | runOnUiThread(() -> { 201 | temperatureTv.setTextColor(finalColor); 202 | }); 203 | }).start(); 204 | } 205 | 206 | private int[] getViewCoordinate(View view) { 207 | int[] coordinate = new int[10]; 208 | int[] topLeftCorner = new int[2]; 209 | view.getLocationOnScreen(topLeftCorner); 210 | int width = view.getWidth(); 211 | int height = view.getHeight(); 212 | // 保存view的左上角坐标 213 | coordinate[0] = topLeftCorner[0]; 214 | coordinate[1] = topLeftCorner[1]; 215 | 216 | // 保存view的右上角的坐标 217 | coordinate[2] = coordinate[0] + width; 218 | coordinate[3] = coordinate[1]; 219 | 220 | // 保存view的右下角坐标 221 | coordinate[4] = coordinate[2]; 222 | coordinate[5] = coordinate[3] + height; 223 | 224 | // 保存view的左下角坐标 225 | coordinate[6] = coordinate[0]; 226 | coordinate[7] = coordinate[5]; 227 | 228 | // 保存view的中心点的坐标 229 | coordinate[8] = coordinate[0] + width / 2; 230 | coordinate[9] = coordinate[1] + height / 2; 231 | StringBuilder builder = new StringBuilder(); 232 | builder.append("左上角坐标:(" + coordinate[0] + "," + coordinate[1] + ")\n"); 233 | builder.append("右上角坐标:(" + coordinate[2] + "," + coordinate[3] + ")\n"); 234 | builder.append("右下角坐标:(" + coordinate[4] + "," + coordinate[5] + ")\n"); 235 | builder.append("左下角坐标:(" + coordinate[6] + "," + coordinate[7] + ")\n"); 236 | builder.append("中心点坐标:(" + coordinate[8] + "," + coordinate[9] + ")"); 237 | Log.i(TAG, builder.toString()); 238 | return coordinate; 239 | } 240 | 241 | @Override 242 | protected void onResume() { 243 | super.onResume(); 244 | } 245 | 246 | @Override 247 | public boolean dispatchTouchEvent(MotionEvent ev) { 248 | switch (ev.getAction()) { 249 | case MotionEvent.ACTION_DOWN: 250 | Log.d("onWeatherActivity", "执行dispatchTouchEvent方法的手指按下"); 251 | Log.d(TAG, "点击点坐标:(" + ev.getX() + "," + ev.getY() + ")"); 252 | //mProgressBar.resetProgressBar(); 253 | mProgressBar.resetProgressBar(true); 254 | break; 255 | case MotionEvent.ACTION_MOVE: 256 | Log.d("onWeatherActivity", "执行dispatchTouchEvent方法的手指滑动"); 257 | break; 258 | default: 259 | break; 260 | } 261 | return super.dispatchTouchEvent(ev); 262 | } 263 | 264 | /** 265 | * 加载天气信息 266 | */ 267 | private void loadWeatherInfo() { 268 | String weatherInfo = sp.getString("heWeather", null); 269 | if (weatherInfo != null) { 270 | // sp里有缓存的天气信息数据 271 | Weather weather = Utility.handleWeatherResponse(weatherInfo); 272 | if (mWeatherId.equals(weather.basic.weatherId)) { 273 | //且请求的weatherId与sp里缓存的一致,则直接显示缓存的天气信息 274 | // 加载天气背景图片 275 | loadWeatherBgPic(weather.now.more.code); 276 | // 显示天气情况 277 | showWeatherInfo(weather); 278 | } else { 279 | // 请求的天气id和缓存里的不一致,则需重新请求网络来更新天气信息 280 | requestWeather(mWeatherId); 281 | } 282 | } else { 283 | // sp里没有缓存的天气信息数据,需请求网络来获取天气信息 284 | requestWeather(mWeatherId); 285 | } 286 | } 287 | 288 | /** 289 | * 根据天气情况加载对应天气背景图片 290 | * 291 | * @param code 天气代码
292 | * 天气代码对照
293 | * 100 晴天
294 | * 101~103 多云
295 | * 104 阴天
296 | * 300~399 雨天
297 | * 400~499 雪天
298 | * 其他 非上述天气情况
299 | */ 300 | private void loadWeatherBgPic(String code) { 301 | 302 | String picResName = getPicResName(code); 303 | if ("other".equals(picResName)) { 304 | // 非上述天气情况,则加载默认天气背景图片:Bing每日一图 305 | loadDefaultBgPic(); 306 | } else { 307 | // 加载对应的天气背景图片 308 | picResId = getResources().getIdentifier(picResName, "drawable", getPackageName()); 309 | Log.d("天气背景图片资源id", "" + picResId); 310 | if (picResId != 0) { 311 | Glide.with(mContext).load(picResId).into(bingPicImg); 312 | } 313 | } 314 | } 315 | 316 | private String getPicResName(String code) { 317 | // 初始默认设定为非上述天气情况 318 | String name = "other"; 319 | if (code.startsWith("1")) { 320 | if ("100".equals(code)) { 321 | name = "sunny" + getPicRandomIndex(1, 2); 322 | } else if ("104".equals(code)) { 323 | // 加载阴天天气背景图片 324 | name = "overcast" + getPicRandomIndex(1, 1); 325 | } else { 326 | // 加载多云天气背景图片 327 | name = "cloudy" + getPicRandomIndex(1, 3); 328 | } 329 | } else if (code.startsWith("3")) { 330 | // 加载雨天天气背景图片 331 | name = "rain" + getPicRandomIndex(1, 2); 332 | } else if (code.startsWith("4")) { 333 | // 加载雪天天气背景图片 334 | name = "snow" + getPicRandomIndex(1, 2); 335 | } 336 | Log.d("天气背景图片名称", name); 337 | return name; 338 | } 339 | 340 | private int getPicRandomIndex(int startIndex, int endIndex) { 341 | return new Random().nextInt(endIndex - startIndex + 1) + startIndex; 342 | } 343 | 344 | /** 345 | * 加载缓存的背景图片 346 | */ 347 | private void loadDefaultBgPic() { 348 | String bingPicLink = sp.getString("bing_pic", null); 349 | Log.d("onLoadBgPic", "bing每日背景图链接地址:" + bingPicLink); 350 | if (bingPicLink != null) { 351 | // 有图片链接缓存,就直接读取缓存并加载图片显示出来 352 | Log.d("onLoadBgPic", "有图片链接缓存"); 353 | Glide.with(mContext).load(bingPicLink).into(bingPicImg); 354 | } else { 355 | requestBingPic(BingPicAddress); 356 | } 357 | } 358 | 359 | private void initData() { 360 | weatherLayout = findViewById(R.id.weather_layout); 361 | areaTitleTv = findViewById(R.id.title_area); 362 | nowTimeTv = findViewById(R.id.now_time); 363 | temperatureTv = findViewById(R.id.degree_text); 364 | weatherInfoTv = findViewById(R.id.weather_info_text); 365 | aqiTv = findViewById(R.id.aqi); 366 | pm25Tv = findViewById(R.id.pm25); 367 | forecastLayout = findViewById(R.id.forecast_layout); 368 | comfortTv = findViewById(R.id.comfort_text); 369 | carWashTv = findViewById(R.id.car_wash_text); 370 | sportTv = findViewById(R.id.sport_text); 371 | 372 | bingPicImg = findViewById(R.id.bing_pic_img); 373 | 374 | swipeRefreshLayout = findViewById(R.id.swipe_refresh); 375 | swipeRefreshLayout.setColorSchemeColors(ContextCompat.getColor(this, R.color.colorPrimary)); 376 | 377 | drawerLayout = findViewById(R.id.drawer_layout); 378 | mNavView = findViewById(R.id.nav_view); 379 | sideLayout = findViewById(R.id.side_layout); 380 | mProgressBar = findViewById(R.id.timeout_progress_bar); 381 | sp = PreferenceManager.getDefaultSharedPreferences(this); 382 | 383 | 384 | mWeatherId = getIntent().getStringExtra("weather_id"); 385 | Log.d("onInitData", "天气id:" + mWeatherId); 386 | 387 | } 388 | 389 | private void initEvent() { 390 | addListenerEvent(); 391 | Log.d("onInitEvent()方法", "开始启动超时等待任务"); 392 | // 开始超时等待任务 393 | Log.d(TAG, "开始属性动画"); 394 | mProgressBar.setTimeOut(10); 395 | // mProgressBar.startTimeOut(); 396 | mProgressBar.customValueAnimator(); 397 | } 398 | 399 | /** 400 | * 事件监听方法 401 | */ 402 | private void addListenerEvent() { 403 | /* 添加标题栏展开侧滑栏按钮的点击事件 */ 404 | Button switchDrawerBtn = findViewById(R.id.drawer_switch); 405 | switchDrawerBtn.setOnClickListener(view -> { 406 | // lambda表达式 407 | drawerLayout.openDrawer(Gravity.START, true); 408 | }); 409 | 410 | /* 添加下拉刷新事件监听 */ 411 | swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 412 | @Override 413 | public void onRefresh() { 414 | Log.d("onRefresh", "天气id:" + mWeatherId); 415 | // 手动下拉刷新天气信息 416 | requestWeather(mWeatherId); 417 | // 数据加载完毕,下拉刷新结束 418 | swipeRefreshLayout.setRefreshing(false); 419 | } 420 | }); 421 | 422 | /* 添加点击系统后退按钮事件监听 */ 423 | 424 | 425 | /* 设置滑动菜单的item点击事件响应 */ 426 | mNavView.setCheckedItem(R.id.nav_location); 427 | mNavView.setNavigationItemSelectedListener(item -> { 428 | switch (item.getItemId()) { 429 | // 点击"选择地址"item 430 | case R.id.nav_location: 431 | // 隐藏滑动菜单 432 | mNavView.setVisibility(View.INVISIBLE); 433 | if (sideLayout.getChildCount() < 2) { 434 | // 添加区域选择界面(fragment view) 435 | FragmentManager fragmentManager = getSupportFragmentManager(); 436 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 437 | transaction.add(R.id.side_layout, new ChooseAreaFragment(), "frag_view"); 438 | transaction.commit(); 439 | } else { 440 | // 显示区域选择界面 441 | FragmentManager fragmentManager = getSupportFragmentManager(); 442 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 443 | transaction.show(fragmentManager.findFragmentByTag("frag_view")); 444 | transaction.commit(); 445 | } 446 | return true; 447 | 448 | // 点击"设置"item 449 | case R.id.nav_settings: 450 | Toast.makeText(WeatherActivity.this, 451 | "点击设置菜单选项", Toast.LENGTH_SHORT) 452 | .show(); 453 | return true; 454 | default: 455 | return false; 456 | } 457 | 458 | }); 459 | 460 | } 461 | 462 | /** 463 | * 请求网络获取背景图片链接地址 464 | **/ 465 | private void requestBingPic(String address) { 466 | // 通过网络请求图片链接地址 467 | HttpUtil.sendOkHttpRequest(address, new Callback() { 468 | @Override 469 | public void onFailure(Call call, IOException e) { 470 | Log.w("onLoadBingPic", "请求bing每日背景图片失败:"); 471 | } 472 | 473 | @Override 474 | public void onResponse(Call call, Response response) throws IOException { 475 | String bingPicLink = response.body().string(); 476 | Log.d("onLoadBingPic", "请求的bing每日背景图链接地址:" + bingPicLink); 477 | // 缓存bing每日一图链接地址 478 | SharedPreferences.Editor editor = sp.edit(); 479 | editor.putString("bing_pic", bingPicLink); 480 | editor.apply(); 481 | // 加载Bing背景图片 482 | runOnUiThread(new Runnable() { 483 | @Override 484 | public void run() { 485 | Glide.with(mContext).load(bingPicLink).into(bingPicImg); 486 | } 487 | }); 488 | } 489 | }); 490 | 491 | } 492 | 493 | /** 494 | * 根据天气 id 请求城市天气信息 495 | */ 496 | public void requestWeather(String weatherId) { 497 | // requestBingPic(BingPicAddress); 498 | ProgressDialog progressDialog = Utility.showProgressDialog(this); 499 | String address = "http://guolin.tech/api/weather?cityid=" + weatherId 500 | + "&key=c72d9f8149ac436da648ac0e43211edd"; 501 | HttpUtil.sendOkHttpRequest(address, new Callback() { 502 | @Override 503 | public void onFailure(Call call, IOException e) { 504 | Log.w("onWeatherActivity", "网络通讯异常"); 505 | runOnUiThread(() -> { 506 | Utility.closeProgressDialog(progressDialog); 507 | Toast.makeText(mContext, "网络通讯异常,获取天气信息失败", 508 | Toast.LENGTH_LONG).show(); 509 | }); 510 | } 511 | 512 | @Override 513 | public void onResponse(Call call, Response response) throws IOException { 514 | String responseText = response.body().string(); 515 | if (responseText == null) { 516 | throw new NullPointerException("请求天气信息的响应消息为空"); 517 | } 518 | Log.d("onRequestWeather", "天气信息:" + responseText); 519 | // 当前WeatherActivity界面也要保存weatherId 520 | mWeatherId = weatherId; 521 | // 使用SharedPreferences来缓存天气信息数据 522 | SharedPreferences.Editor editor = sp.edit(); 523 | editor.putString("heWeather", responseText); 524 | editor.putString("weather_id", weatherId); 525 | editor.apply(); 526 | Weather weather = Utility.handleWeatherResponse(responseText); 527 | runOnUiThread(new Runnable() { 528 | @Override 529 | public void run() { 530 | Utility.closeProgressDialog(progressDialog); 531 | if (weather != null && "ok".equals(weather.status)) { 532 | // 加载天气背景图片 533 | loadWeatherBgPic(weather.now.more.code); 534 | showWeatherInfo(weather); 535 | } else { 536 | Log.w("onWeatherActivity", "天气信息json数据异常"); 537 | Toast.makeText(WeatherActivity.this, "获取天气信息失败", 538 | Toast.LENGTH_LONG).show(); 539 | } 540 | } 541 | }); 542 | } 543 | }); 544 | 545 | } 546 | 547 | /** 548 | * 处理并展示Weather实体类中的数据 549 | */ 550 | private void showWeatherInfo(Weather weather) { 551 | 552 | Log.d("onWeatherActivity", "天气实体类:\n" + weather.toString()); 553 | // 显示区域标题 554 | areaTitleTv.setText(weather.basic.cityName); 555 | 556 | // 显示当前天气更新的时间(本地时间) 557 | // weather.basic.update.updateTime 格式:2018-10-29 19:46 558 | String updateTime = weather.basic.update.updateTime.split(" ")[1]; 559 | nowTimeTv.setText("更新时间\n" + updateTime); 560 | 561 | // 显示当前天气的气温 562 | temperatureTv.setText(weather.now.temperature + "℃"); 563 | 564 | // 显示当前天气情况 565 | weatherInfoTv.setText(weather.now.more.info); 566 | 567 | // 显示未来几天天气情况 568 | showForecastInfo(weather); 569 | 570 | if (weather.aqi != null) { 571 | /* 显示空气质量情况 */ 572 | aqiTv.setText(weather.aqi.city.aqi); // 空气质量指数 573 | pm25Tv.setText(weather.aqi.city.pm25); // 显示PM2.5指数 574 | } 575 | 576 | /* 显示生活建议 */ 577 | comfortTv.setText("【舒适度】:" + weather.suggestion.comfort.info); // 天气舒适度 578 | carWashTv.setText("【洗车建议】:" + weather.suggestion.carWash.info); // 洗车建议 579 | sportTv.setText("【运动建议】:" + weather.suggestion.sport.info); // 运动建议 580 | 581 | // 加载完背景图片后,再显示天气界面布局 582 | weatherLayout.setVisibility(View.VISIBLE); 583 | 584 | Intent intent = new Intent(this, AutoUpdateWeatherService.class); 585 | startService(intent); 586 | } 587 | 588 | /** 589 | * 显示天气预报信息 590 | **/ 591 | private void showForecastInfo(Weather weather) { 592 | forecastLayout.removeAllViews(); 593 | 594 | /*Palette palette = Palette.from(BitmapFactory.decodeResource(getResources(), picResId)).generate(); 595 | // 获取输入图片中亮丽的颜色,获取失败则默认颜色为纯白色 596 | int bgColor1 = palette.getVibrantColor(0xffffffff); 597 | int bgColor2 = palette.getLightMutedColor(0xffffffff); 598 | int bgColor3 = palette.getLightVibrantColor(0xffffffff);*/ 599 | 600 | for (DailyForecast dailyForecast : weather.dailyForecastList) { 601 | View view = LayoutInflater.from(this).inflate(R.layout.forecast_item, forecastLayout, false); 602 | TextView dateTv = view.findViewById(R.id.date_text); 603 | TextView infoTv = view.findViewById(R.id.info_text); 604 | TextView tmpMaxTv = view.findViewById(R.id.tmp_max_text); 605 | TextView tmpMinTv = view.findViewById(R.id.tmp_min_text); 606 | 607 | // 显示天气预报的预报日期 608 | dateTv.setText(dailyForecast.date); 609 | 610 | // 显示预报天气情况 611 | String info = dailyForecast.more.info; 612 | infoTv.setText(info); 613 | // infoTv.setTextColor(bgColor1); 614 | 615 | // 显示当天最高气温 616 | tmpMaxTv.setText("最高气温\n" + dailyForecast.temperature.max + "℃"); 617 | // tmpMaxTv.setTextColor(bgColor2); 618 | // 显示当天最低气温 619 | tmpMinTv.setText("最低气温\n" + dailyForecast.temperature.min + "℃"); 620 | // tmpMaxTv.setTextColor(bgColor3); 621 | 622 | // 添加天气预报信息的 view 到天气预报界面容器内 623 | forecastLayout.addView(view); 624 | } 625 | } 626 | 627 | } 628 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/ui/fragment/ChooseAreaFragment.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.ui.fragment; 2 | 3 | import android.app.Activity; 4 | import android.app.Dialog; 5 | import android.app.ProgressDialog; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.app.Fragment; 10 | import android.support.v4.app.FragmentManager; 11 | import android.support.v4.app.FragmentTransaction; 12 | import android.util.Log; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.AdapterView; 17 | import android.widget.ArrayAdapter; 18 | import android.widget.Button; 19 | import android.widget.ListView; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | 23 | import com.coolweather.android.R; 24 | import com.coolweather.android.db.City; 25 | import com.coolweather.android.db.County; 26 | import com.coolweather.android.db.Province; 27 | import com.coolweather.android.ui.MainActivity; 28 | import com.coolweather.android.ui.WeatherActivity; 29 | import com.coolweather.android.util.HttpUtil; 30 | import com.coolweather.android.util.Utility; 31 | 32 | import org.litepal.LitePal; 33 | 34 | import java.io.IOException; 35 | import java.util.ArrayList; 36 | import java.util.List; 37 | import java.util.logging.Level; 38 | 39 | import okhttp3.Call; 40 | import okhttp3.Callback; 41 | import okhttp3.Response; 42 | 43 | /** 44 | * 显示省、市、县区域信息 45 | */ 46 | public class ChooseAreaFragment extends Fragment { 47 | private Activity mActivity; 48 | 49 | private Button backBtn; 50 | 51 | private TextView titleTv; 52 | 53 | private ListView areaListView; 54 | 55 | private ArrayAdapter adapter; 56 | 57 | /** 58 | * 省级列表数据model 59 | */ 60 | private List provinceList; 61 | /** 62 | * 市级列表数据model 63 | */ 64 | private List cityList; 65 | /** 66 | * 县级列表数据model 67 | */ 68 | private List countyList; 69 | 70 | List dataList; 71 | 72 | /** 73 | * 选中的省份 74 | */ 75 | private Province selectedProvince; 76 | 77 | /** 78 | * 选中的城市 79 | */ 80 | private City selectedCity; 81 | 82 | /** 83 | * 当前区域列表的层级 84 | */ 85 | private int currentLevel; 86 | 87 | public static final int LEVEL_PROVINCE = 0; 88 | public static final int LEVEL_CITY = 1; 89 | public static final int LEVEL_COUNTY = 2; 90 | 91 | 92 | @Nullable 93 | @Override 94 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 95 | View view = inflater.inflate(R.layout.choose_area, container, false); 96 | initData(view); 97 | return view; 98 | } 99 | 100 | @Override 101 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 102 | super.onActivityCreated(savedInstanceState); 103 | initEvent(); 104 | queryProvinces(); 105 | } 106 | 107 | private void initData(View view) { 108 | mActivity = getActivity(); 109 | 110 | backBtn = view.findViewById(R.id.back_button); 111 | titleTv = view.findViewById(R.id.title_text); 112 | areaListView = view.findViewById(R.id.area_list_view); 113 | dataList = new ArrayList<>(); 114 | adapter = new ArrayAdapter(getContext(), android.R.layout.simple_list_item_1, dataList); 115 | areaListView.setAdapter(adapter); 116 | } 117 | 118 | private void initEvent() { 119 | /* 区域列表item的点击事件处理 */ 120 | areaListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 121 | @Override 122 | public void onItemClick(AdapterView adapterView, View view, int position, long id) { 123 | switch (currentLevel) { 124 | case LEVEL_PROVINCE: 125 | selectedProvince = provinceList.get(position); 126 | queryCities(); 127 | break; 128 | case LEVEL_CITY: 129 | selectedCity = cityList.get(position); 130 | queryCounties(); 131 | break; 132 | case LEVEL_COUNTY: 133 | Activity activity = getActivity(); 134 | String weatheId = countyList.get(position).getWeatherId(); 135 | if (activity instanceof MainActivity) { 136 | /* 启动weather activity ,显示选定的区域的天气情况 */ 137 | Intent intent = new Intent(getActivity(), WeatherActivity.class); 138 | intent.putExtra("weather_id", weatheId); 139 | mActivity.startActivity(intent); 140 | mActivity.finish(); 141 | } else if (activity instanceof WeatherActivity) { 142 | WeatherActivity weatherActivity = (WeatherActivity) activity; 143 | 144 | // 移除选择地址的fragment view 145 | weatherActivity.sideLayout.removeViewAt(1); 146 | // 恢复显示滑动菜单 147 | weatherActivity.mNavView.setVisibility(View.VISIBLE); 148 | // 关闭侧滑栏 149 | weatherActivity.drawerLayout.closeDrawers(); 150 | 151 | // 显示新选择的地区的天气信息 152 | weatherActivity.swipeRefreshLayout.setRefreshing(true); 153 | weatherActivity.requestWeather(weatheId); 154 | weatherActivity.swipeRefreshLayout.setRefreshing(false); 155 | } 156 | break; 157 | default: 158 | break; 159 | } 160 | } 161 | }); 162 | 163 | /* 标题栏 返回按钮 的点击事件处理 */ 164 | backBtn.setOnClickListener((view) -> { 165 | /* 166 | * TODO:每次返回到上级区域列表都需要重新查询数据库,影响性能, 167 | * TODO: 可用一数据结构保存各区域列表信息,减少查询数据库的次数 168 | */ 169 | if (currentLevel == LEVEL_COUNTY) { 170 | // 返回到市级列表 171 | queryCities(); 172 | } else if (currentLevel == LEVEL_CITY) { 173 | // 返回省级列表 174 | queryProvinces(); 175 | } else if (currentLevel == LEVEL_PROVINCE) { 176 | WeatherActivity weatherActivity = (WeatherActivity) getActivity(); 177 | // 隐藏区域选择fragment view 界面 178 | FragmentManager fragmentManager = weatherActivity.getSupportFragmentManager(); 179 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 180 | transaction.hide(fragmentManager.findFragmentByTag("frag_view")); 181 | transaction.commit(); 182 | // 返回到滑动菜单 183 | weatherActivity.mNavView.setVisibility(View.VISIBLE); 184 | } 185 | }); 186 | 187 | } 188 | 189 | /** 190 | * 查询全国所有的省,优先从数据库中查询,如果没有查询到再去服务器上查询 191 | */ 192 | private void queryProvinces() { 193 | titleTv.setText(R.string.province_title); 194 | if (getActivity() instanceof MainActivity) { 195 | backBtn.setVisibility(View.GONE); 196 | } 197 | provinceList = LitePal.findAll(Province.class); 198 | if (provinceList.size() > 0) { 199 | // 本地数据库有省份数据 200 | dataList.clear(); 201 | for (Province province : provinceList) { 202 | dataList.add(province.getProvinceName()); 203 | } 204 | // underlying data 发生改变,则通知刷新ListView列表信息 205 | adapter.notifyDataSetChanged(); 206 | // 选中item初始化置为第一项 207 | areaListView.setSelection(0); 208 | currentLevel = LEVEL_PROVINCE; 209 | } else { 210 | // 本地数据库无省份数据,则网络请求服务器获取省份数据 211 | 212 | String address = "http://guolin.tech/api/china"; // 远程服务器省份区域的接口 213 | queryFromServer(address, "province"); 214 | 215 | } 216 | 217 | } 218 | 219 | /** 220 | * 查询选中的省内的所有市,优先从数据库中查询,如果没有查询到再去服务器上查询 221 | */ 222 | private void queryCities() { 223 | titleTv.setText(selectedProvince.getProvinceName()); 224 | backBtn.setVisibility(View.VISIBLE); 225 | int provinceCode = selectedProvince.getProvinceCode(); 226 | cityList = LitePal.where("provinceCode = ?", "" + provinceCode) 227 | .find(City.class); 228 | if (cityList.size() > 0) { 229 | dataList.clear(); 230 | for (City city : cityList) { 231 | dataList.add(city.getCityName()); 232 | } 233 | adapter.notifyDataSetChanged(); 234 | areaListView.setSelection(0); 235 | currentLevel = LEVEL_CITY; 236 | } else { 237 | String address = "http://guolin.tech/api/china/" + provinceCode; 238 | queryFromServer(address, "city"); 239 | } 240 | } 241 | 242 | /** 243 | * 查询选中的市内所有的县,优先从数据库中查询,如果没有查询到再去服务器上查询 244 | */ 245 | private void queryCounties() { 246 | titleTv.setText(selectedCity.getCityName()); 247 | int cityCode = selectedCity.getCityCode(); 248 | countyList = LitePal.where("cityCode = ?", "" + cityCode) 249 | .find(County.class); 250 | if (countyList.size() > 0) { 251 | dataList.clear(); 252 | for (County county : countyList) { 253 | dataList.add(county.getCountyName()); 254 | } 255 | adapter.notifyDataSetChanged(); 256 | areaListView.setSelection(0); 257 | currentLevel = LEVEL_COUNTY; 258 | } else { 259 | int provinceCode = selectedCity.getProvinceCode(); 260 | String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode; 261 | queryFromServer(address, "county"); 262 | } 263 | } 264 | 265 | /** 266 | * 根据传入的地址和类型从服务器上查询省市县的数据 267 | */ 268 | private void queryFromServer(String address, final String type) { 269 | final ProgressDialog progressDialog = Utility.showProgressDialog(mActivity); 270 | // 发送网络请求 271 | HttpUtil.sendOkHttpRequest(address, new Callback() { 272 | @Override 273 | public void onFailure(Call call, IOException e) { 274 | Log.d("onChooseAreaFragment", "网络请求失败"); 275 | // UI的变化,需要在UI线程上进行处理 276 | mActivity.runOnUiThread(new Runnable() { 277 | @Override 278 | public void run() { 279 | Utility.closeProgressDialog(progressDialog); 280 | Toast.makeText(getContext(), "网络请求失败", Toast.LENGTH_LONG).show(); 281 | } 282 | }); 283 | } 284 | 285 | @Override 286 | public void onResponse(Call call, Response response) throws IOException { 287 | Log.d("onChooseAreaFragment", "当前线程:" + Thread.currentThread().toString()); 288 | String responseText = response.body().string(); // json格式的省份数据 289 | boolean result = false; 290 | switch (type) { 291 | case "province": 292 | result = Utility.handleProvinceResponse(responseText); 293 | break; 294 | case "city": 295 | result = Utility.handleCityResponse(responseText, selectedProvince.getProvinceCode()); 296 | break; 297 | case "county": 298 | result = Utility.handleCountyResponse(responseText, selectedCity.getCityCode()); 299 | break; 300 | default: 301 | break; 302 | } 303 | if (!result) { 304 | Log.d("onChooseAreaFragment", "解析json数据异常"); 305 | // UI的变化,需要在UI线程上进行处理 306 | mActivity.runOnUiThread(new Runnable() { 307 | @Override 308 | public void run() { 309 | Utility.closeProgressDialog(progressDialog); 310 | Toast.makeText(mActivity, "解析数据异常", Toast.LENGTH_LONG).show(); 311 | } 312 | }); 313 | } else { 314 | Log.d("onChooseAreaFragment", "解析json数据成功"); 315 | Log.d("onChooseAreaFragment", "mActivity信息:" + mActivity.toString()); 316 | mActivity.runOnUiThread(new Runnable() { 317 | // 查询区域信息涉及到UI操作,需要从子线程切换到主线程上来处理 318 | @Override 319 | public void run() { 320 | Utility.closeProgressDialog(progressDialog); 321 | switch (type) { 322 | case "province": 323 | queryProvinces(); 324 | break; 325 | case "city": 326 | queryCities(); 327 | break; 328 | case "county": 329 | queryCounties(); 330 | break; 331 | default: 332 | break; 333 | } 334 | } 335 | }); 336 | } 337 | } 338 | }); 339 | } 340 | 341 | } 342 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/ui/widget/TimeoutProgressBar.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.ui.widget; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.app.Activity; 6 | import android.content.Context; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.util.AttributeSet; 10 | import android.util.DisplayMetrics; 11 | import android.util.Log; 12 | import android.view.Display; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.widget.FrameLayout; 16 | import android.widget.ImageView; 17 | 18 | import com.coolweather.android.R; 19 | 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.LinkedBlockingQueue; 22 | import java.util.concurrent.ThreadPoolExecutor; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | /** 26 | * 自定义超时进度条,用于界面操作超时,返回到上一步Activity 或 Fragment 27 | **/ 28 | public class TimeoutProgressBar extends FrameLayout { 29 | /** 30 | * 超时时长(单位:秒) 31 | */ 32 | private int mTimeOut; 33 | 34 | public boolean isTimeOuted() { 35 | return isTimeOuted; 36 | } 37 | 38 | private boolean isTimeOuted; 39 | 40 | /** 41 | * 屏幕水平方向尺寸(单位:pixel) 42 | */ 43 | private int mWidthSize; 44 | 45 | /** 重置进度条位置状态的标志 */ 46 | private volatile boolean isReset; 47 | 48 | private Context mContext; 49 | 50 | private ImageView mProgressBarIv; 51 | 52 | private View view; 53 | 54 | ValueAnimator mValueAnimator; 55 | 56 | public TimeoutProgressBar(@NonNull Context context, int timeOut) { 57 | super(context); 58 | Log.d("在超时进度条", "构造方法1被执行"); 59 | init(context, timeOut); 60 | } 61 | 62 | public TimeoutProgressBar(@NonNull Context context, @Nullable AttributeSet attrs) { 63 | super(context, attrs); 64 | Log.d("在超时进度条", "构造方法2被执行"); 65 | init(context, 60); 66 | } 67 | 68 | public TimeoutProgressBar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 69 | super(context, attrs, defStyleAttr); 70 | Log.d("在超时进度条", "构造方法3被执行"); 71 | init(context, 60); 72 | } 73 | 74 | private void init(Context context, int timeOut) { 75 | mContext = context; 76 | mTimeOut = timeOut; 77 | Display display = ((Activity) mContext).getWindowManager().getDefaultDisplay(); 78 | DisplayMetrics displayMetrics = new DisplayMetrics(); 79 | display.getMetrics(displayMetrics); 80 | // 屏幕水平方向尺寸(单位:pixel) 81 | mWidthSize = displayMetrics.widthPixels; 82 | isReset = false; 83 | initView(); 84 | } 85 | 86 | private void initView() { 87 | view = LayoutInflater.from(mContext).inflate(R.layout.timeout_progress_bar, this, true); 88 | Log.d("View是", view.toString()); 89 | mProgressBarIv = view.findViewById(R.id.progress_bar_image); 90 | Log.d("on超时进度条initView()方法", mProgressBarIv.toString()); 91 | } 92 | 93 | /** 94 | * 开始超时等待任务 95 | */ 96 | public void startTimeOut() { 97 | Log.d("mWidthSize值:", mWidthSize + ""); 98 | /* 每秒宽度增长的步长 *//* 99 | int speed = mWidthSize / mTimeOut; 100 | Log.d("speed值:", "" + speed);*/ 101 | defineValueAnimator(); 102 | // 启动属性动画 103 | mValueAnimator.start(); 104 | } 105 | 106 | /** 107 | * 属性动画,实现进度条随时间自增长 108 | */ 109 | private void defineValueAnimator() { 110 | mValueAnimator = ValueAnimator.ofInt(1, mWidthSize); 111 | mValueAnimator.setDuration(mTimeOut * 1000); 112 | // 设置动画重复次数:设定值 + 1 113 | mValueAnimator.setRepeatCount(0); 114 | // 设置重复播放动画模式 115 | mValueAnimator.setRepeatMode(ValueAnimator.RESTART); 116 | 117 | mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 118 | @Override 119 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 120 | int width = (Integer) valueAnimator.getAnimatedValue(); 121 | mProgressBarIv.getLayoutParams().width = mWidthSize - width; 122 | mProgressBarIv.requestLayout(); 123 | } 124 | }); 125 | 126 | mValueAnimator.addListener(new Animator.AnimatorListener() { 127 | @Override 128 | public void onAnimationStart(Animator animator) { 129 | isTimeOuted = false; 130 | isReset = false; 131 | } 132 | 133 | @Override 134 | public void onAnimationEnd(Animator animator) { 135 | if (isReset) { 136 | Log.d("onTimeOutProgressBar","执行属性动画结束回调方法"); 137 | return; 138 | } 139 | Log.d("onTimeOutProgressBar","执行属性动画结束回调方法(动画结束)"); 140 | isTimeOuted = true; 141 | setVisibility(INVISIBLE); 142 | } 143 | 144 | @Override 145 | public void onAnimationCancel(Animator animator) { 146 | Log.d("onTimeOutProgressBar","执行属性动画取消回调方法"); 147 | isReset = true; 148 | //mProgressBarIv.requestLayout(); 149 | //mValueAnimator.start(); 150 | } 151 | 152 | @Override 153 | public void onAnimationRepeat(Animator animator) { 154 | 155 | } 156 | }); 157 | } 158 | 159 | 160 | /** 161 | * 自定义属性动画,实现进度条随时间自增长 162 | */ 163 | public void customValueAnimator() { 164 | /* 每秒宽度增长的步长 */ 165 | int speed = mWidthSize / mTimeOut; 166 | Log.d("speed值:", "" + speed); 167 | Runnable updateProgressTask = new Runnable() { 168 | @Override 169 | public void run() { 170 | try { 171 | int curWidth = mWidthSize; 172 | isTimeOuted = false; 173 | // 当curWidth == 0 时,表明进度条长度已达最大值,长度变化结束 174 | while (curWidth > 0) { 175 | if (isReset) { 176 | isReset = false; 177 | curWidth = mWidthSize; 178 | mProgressBarIv.getLayoutParams().width = mWidthSize; 179 | ((Activity) mContext).runOnUiThread(new Runnable() { 180 | @Override 181 | public void run() { 182 | // 指定控件的属性值发送改变后,需要请求重绘布局 183 | mProgressBarIv.requestLayout(); 184 | } 185 | }); 186 | continue; 187 | } 188 | // 每秒curWidth减小speed,即进度条每秒增长speed大小的像素 189 | Thread.sleep(1000); 190 | curWidth -= speed; 191 | if (curWidth < 0) { 192 | curWidth = 0; 193 | } 194 | mProgressBarIv.getLayoutParams().width = curWidth; 195 | ((Activity) mContext).runOnUiThread(new Runnable() { 196 | @Override 197 | public void run() { 198 | // 指定控件的属性值发送改变后,需要请求重绘布局 199 | mProgressBarIv.requestLayout(); 200 | if (mProgressBarIv.getLayoutParams().width == 0) { 201 | isTimeOuted = true; 202 | setVisibility(INVISIBLE); 203 | } 204 | } 205 | }); 206 | } 207 | } catch (InterruptedException e) { 208 | e.printStackTrace(); 209 | } 210 | } 211 | }; 212 | // 使用线程池来管理线程的生命周期和系统资源的占用 213 | ExecutorService singleThreadPool = new ThreadPoolExecutor(1,1,0, 214 | TimeUnit.MILLISECONDS,new LinkedBlockingQueue()); 215 | singleThreadPool.execute(updateProgressTask); 216 | singleThreadPool.shutdown(); 217 | // new Thread(updateProgressTask).start(); 218 | } 219 | 220 | public int getTimeOut() { 221 | return mTimeOut; 222 | } 223 | 224 | public void setTimeOut(int mTimeOut) { 225 | this.mTimeOut = mTimeOut; 226 | } 227 | 228 | /** 重置属性动画状态 */ 229 | public void resetProgressBar() { 230 | if (mValueAnimator.isRunning()) { 231 | Log.d("onTimeOutProgressBar","执行重置动画"); 232 | mValueAnimator.cancel(); 233 | mValueAnimator.start(); 234 | } 235 | 236 | } 237 | 238 | /** 239 | * 重置进度条状态 240 | * @param isReset true:重置 false:不重置 241 | **/ 242 | public void resetProgressBar(Boolean isReset) { 243 | this.isReset = isReset; 244 | } 245 | 246 | 247 | } -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/util/BitmapHelper.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.util; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Matrix; 6 | 7 | import java.io.ByteArrayInputStream; 8 | import java.io.ByteArrayOutputStream; 9 | 10 | public class BitmapHelper { 11 | /** 压缩图片大小 */ 12 | private Bitmap comp(Bitmap image) { 13 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 14 | image.compress(Bitmap.CompressFormat.JPEG, 100, baos); 15 | if (baos.toByteArray().length / 1024 > 1024) { 16 | //判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出 17 | baos.reset();//重置baos即清空baos 18 | image.compress(Bitmap.CompressFormat.JPEG, 50, baos); 19 | //这里压缩50%,把压缩后的数据存放到baos中 20 | } 21 | ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray()); 22 | BitmapFactory.Options newOpts = new BitmapFactory.Options(); 23 | //开始读入图片,此时把options.inJustDecodeBounds 设回true了 24 | newOpts.inJustDecodeBounds = true; 25 | Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts); 26 | newOpts.inJustDecodeBounds = false; 27 | int w = newOpts.outWidth; 28 | int h = newOpts.outHeight; 29 | //现在主流手机比较多是800*500分辨率,所以高和宽我们设置为 30 | float hh = 800f;//这里设置高度为800f 31 | float ww = 500f;//这里设置宽度为500f 32 | 33 | //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可,scaling=1 表示不缩放 34 | int scaling = 1; 35 | if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放 36 | scaling = (int) (newOpts.outWidth / ww); 37 | } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放 38 | scaling = (int) (newOpts.outHeight / hh); 39 | } 40 | if (scaling <= 0) { 41 | scaling = 1; 42 | } 43 | newOpts.inSampleSize = scaling;//设置缩放比例 44 | //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了 45 | isBm = new ByteArrayInputStream(baos.toByteArray()); 46 | bitmap = BitmapFactory.decodeStream(isBm, null, newOpts); 47 | return bitmap;//压缩好比例大小后再进行质量压缩 48 | } 49 | 50 | /** 51 | * 调整图片分辨率 52 | */ 53 | public static Bitmap resizePicture(Bitmap bitmap ,int newWidth, int newHeight) { 54 | if (bitmap == null) { 55 | return null; 56 | } 57 | if (newWidth <= 0 || newHeight <= 0) { 58 | return bitmap; 59 | } 60 | 61 | int picWidth = bitmap.getWidth(); 62 | int picHeight = bitmap.getHeight(); 63 | 64 | float scaleWidth = (float) newWidth / picWidth; 65 | float scaleHeight = (float) newHeight / picHeight; 66 | 67 | Matrix matrix = new Matrix(); 68 | matrix.postScale(scaleWidth,scaleHeight); 69 | 70 | return Bitmap.createBitmap(bitmap, 0, 0, picWidth, picHeight, matrix, true); 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/util/HttpUtil.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.util; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import okhttp3.OkHttpClient; 6 | import okhttp3.Request; 7 | 8 | public class HttpUtil { 9 | /** 连接超时时长 */ 10 | public final static int CONNECT_TIMEOUT =5; 11 | /** 读超时时长 */ 12 | public final static int READ_TIMEOUT=10; 13 | /** 写超时时长 */ 14 | public final static int WRITE_TIMEOUT=6; 15 | 16 | /** 发送一条网络请求,并返回响应数据 */ 17 | public static void sendOkHttpRequest(String address, okhttp3.Callback callback) { 18 | OkHttpClient client = new OkHttpClient.Builder() 19 | .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) 20 | .readTimeout(READ_TIMEOUT,TimeUnit.SECONDS) 21 | .writeTimeout(WRITE_TIMEOUT,TimeUnit.SECONDS) 22 | .build(); 23 | Request request = new Request.Builder() 24 | .url(address) 25 | .build(); 26 | client.newCall(request).enqueue(callback); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/util/Utility.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.util; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.text.TextUtils; 6 | import android.util.Log; 7 | 8 | import com.coolweather.android.R; 9 | import com.coolweather.android.db.City; 10 | import com.coolweather.android.db.County; 11 | import com.coolweather.android.db.Province; 12 | import com.coolweather.android.gson.Weather; 13 | import com.google.gson.Gson; 14 | 15 | import org.json.JSONArray; 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | 19 | public class Utility { 20 | /** 21 | * 解析服务器返回的省级数据(JSON格式),并保持到数据库 22 | */ 23 | public static boolean handleProvinceResponse(String response) { 24 | if (!TextUtils.isEmpty(response)) { 25 | Log.d(Utility.class.getSimpleName(), "返回的省级信息:" + response); 26 | try { 27 | /* 解析JSON格式数据 */ 28 | JSONArray allProvinces = new JSONArray(response); 29 | JSONObject provinceObject; 30 | Province province; 31 | for (int i = 0; i < allProvinces.length(); i++) { 32 | provinceObject = allProvinces.getJSONObject(i); 33 | province = new Province(); 34 | province.setProvinceName(provinceObject.getString("name")); 35 | province.setProvinceCode(provinceObject.getInt("id")); 36 | province.save(); 37 | } 38 | return true; 39 | } catch (JSONException e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | /** 47 | * 解析服务器返回的市级数据(JSON格式) 48 | */ 49 | public static boolean handleCityResponse(String response, int provinceCode) { 50 | if (!TextUtils.isEmpty(response)) { 51 | try { 52 | Log.d(Utility.class.getSimpleName(), "返回的市级信息:" + response); 53 | /* 解析JSON格式数据 */ 54 | JSONArray allCities = new JSONArray(response); 55 | JSONObject cityObject; 56 | City city; 57 | for (int i = 0; i < allCities.length(); i++) { 58 | cityObject = allCities.getJSONObject(i); 59 | city = new City(); 60 | city.setCityCode(cityObject.getInt("id")); 61 | city.setCityName(cityObject.getString("name")); 62 | city.setProvinceCode(provinceCode); 63 | city.save(); 64 | } 65 | return true; 66 | } catch (JSONException e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | return false; 71 | } 72 | 73 | /** 74 | * 解析服务器返回的县级数据(JSON格式) 75 | */ 76 | public static boolean handleCountyResponse(String response, int cityCode) { 77 | if (!TextUtils.isEmpty(response)) { 78 | try { 79 | Log.d(Utility.class.getSimpleName(), "返回的县级信息:" + response); 80 | JSONObject countyObject; 81 | County county; 82 | /* 解析JSON格式数据 */ 83 | JSONArray allCounties = new JSONArray(response); 84 | for (int i = 0; i < allCounties.length(); i++) { 85 | countyObject = allCounties.getJSONObject(i); 86 | county = new County(); 87 | county.setCountyCode(countyObject.getInt("id")); 88 | county.setCountyName(countyObject.getString("name")); 89 | county.setWeatherId(countyObject.getString("weather_id")); 90 | county.setCityCode(cityCode); 91 | county.save(); 92 | } 93 | return true; 94 | } catch (JSONException e) { 95 | e.printStackTrace(); 96 | } 97 | } 98 | return false; 99 | } 100 | 101 | /** 102 | * 解析服务器返回的天气信息数据(JSON格式) 103 | */ 104 | public static Weather handleWeatherResponse(String response) { 105 | try { 106 | JSONObject jsonObject = new JSONObject(response); 107 | JSONArray jsonArray = jsonObject.getJSONArray("HeWeather"); 108 | String weatherContent = jsonArray.getJSONObject(0).toString(); 109 | return new Gson().fromJson(weatherContent, Weather.class); 110 | } catch (JSONException e) { 111 | Log.e("onUtility", "解析weather json 失败"); 112 | e.printStackTrace(); 113 | } 114 | return null; 115 | } 116 | 117 | /** 118 | * 关闭进度对话框 119 | */ 120 | public static void closeProgressDialog(ProgressDialog dialog) { 121 | if (dialog != null) { 122 | dialog.dismiss(); 123 | } 124 | } 125 | 126 | /** 127 | * 网络请求数据时显示进度对话框 128 | */ 129 | public static ProgressDialog showProgressDialog(Context context) { 130 | ProgressDialog progressDialog = new ProgressDialog(context,R.style.styleDialog); 131 | progressDialog.setTitle("网络请求"); 132 | progressDialog.setMessage("正在加载数据..."); 133 | progressDialog.setCanceledOnTouchOutside(false); 134 | progressDialog.show(); 135 | return progressDialog; 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/java/com/coolweather/android/util/listener/TimeOutListener.java: -------------------------------------------------------------------------------- 1 | package com.coolweather.android.util.listener; 2 | 3 | public interface TimeOutListener { 4 | /** 超时操作处理 */ 5 | void timeOut(); 6 | 7 | /** 未超时操作处理 */ 8 | void unTimeOut(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/cloudy1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/cloudy1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/cloudy2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/cloudy2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/cloudy3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/cloudy3.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/ic_forward.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/location.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/overcast1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/overcast1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/panda.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/rain1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/rain1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/rain2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/rain2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/settings.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/snow1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/snow1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/snow2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/snow2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/sunny1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/sunny1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/sunny2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/sunny2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/thunder1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/thunder1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/thunder2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/thunder2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/timeout_progress_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyneat/coolweather/8677ca152608abaa3a8b8f3c587b2ed95dcbe5e9/app/src/main/res/drawable-xxhdpi/timeout_progress_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_weather.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | 17 | 18 | 19 | 24 | 25 | 26 | 30 | 31 | 32 | 36 | 37 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 66 | 67 | 68 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/res/layout/aqi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 15 | 16 | 19 | 20 | 25 | 26 | 31 | 32 | 39 | 40 | 47 | 48 | 49 | 50 | 51 | 52 | 57 | 62 | 69 | 70 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/res/layout/choose_area.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 11 | 18 | 19 |