提交文件

main
hjhzg 4 months ago
parent 8bebdcfb54
commit 34761a2c7e

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="selectedTabId" value="Android Vitals" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-04-26T03:56:00.783649800Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=XWWGOZSCIRHYYHYL" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

@ -0,0 +1,61 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.21" />
</component>
</project>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings" defaultProject="true" />
</project>

@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

@ -0,0 +1,135 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "com.example.myapplication"
compileSdk = 35
defaultConfig {
applicationId = "com.example.myapplication"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
// 配置lint选项
lint {
// 使用警告而不是错误
abortOnError = false
// 创建基准
baseline = file("lint-baseline.xml")
}
// 添加解决重复类的配置
configurations.all {
resolutionStrategy {
// 防止依赖冲突
failOnVersionConflict()
// 优先使用本地依赖
preferProjectModules()
}
// 排除META-INF中可能重复的文件
exclude(group = "META-INF", module = "androidx.appcompat_appcompat.version")
}
// 设置Packaging选项以处理重复文件
packaging {
resources {
// 排除所有META-INF下的版本信息防止冲突
excludes.add("META-INF/*.version")
excludes.add("META-INF/androidx.*")
excludes.add("META-INF/proguard/*")
excludes.add("META-INF/DEPENDENCIES")
excludes.add("META-INF/LICENSE")
excludes.add("META-INF/LICENSE.txt")
excludes.add("META-INF/license.txt")
excludes.add("META-INF/NOTICE")
excludes.add("META-INF/NOTICE.txt")
excludes.add("META-INF/notice.txt")
excludes.add("META-INF/*.kotlin_module")
// 更精确地排除导致冲突的文件
excludes.add("META-INF/androidx.appcompat_appcompat.version")
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
// AppCompat依赖 - 只保留一个
implementation("androidx.appcompat:appcompat:1.6.1") {
// 排除掉可能冲突的模块
exclude(group = "androidx.appcompat", module = "appcompat-resources")
}
// 不再使用本地AppCompat依赖因为可能缺少必要的依赖项
// implementation(files("libs/appcompat-1.6.1.aar"))
// 添加AppCompat的依赖项
implementation("androidx.annotation:annotation:1.7.1")
implementation("androidx.core:core:1.12.0")
implementation("androidx.fragment:fragment:1.6.2")
// ConstraintLayout依赖
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// 添加CardView依赖
implementation("androidx.cardview:cardview:1.0.0")
// 添加RecyclerView依赖
implementation("androidx.recyclerview:recyclerview:1.3.2")
// 添加OkHttp库用于网络请求 - 只保留一个
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// 不再使用本地OkHttp依赖
// implementation(files("libs/okhttp-4.12.0.jar"))
// 添加Okio库OkHttp的依赖
implementation("com.squareup.okio:okio:2.10.0")
// 不再使用本地Okio依赖
// implementation(files("libs/okio-2.10.0.jar"))
// 添加Gson库用于JSON解析
implementation("com.google.code.gson:gson:2.10.1")
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,24 @@
package com.example.myapplication
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.myapplication", appContext.packageName)
}
}

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MinimalistWeather"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31">
<activity
android:name=".WeatherActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:exported="false"
android:label="设置"
android:launchMode="singleTop"
android:configChanges="uiMode|orientation|screenSize"
android:theme="@style/Theme.MinimalistWeather"
android:parentActivityName=".WeatherActivity" />
<activity
android:name=".CityManagerActivity"
android:exported="false"
android:label="城市管理"
android:parentActivityName=".WeatherActivity" />
<activity
android:name=".OkhttpActivity"
android:exported="false"
android:label="OkHttp天气查询" />
<activity
android:name=".MainActivity"
android:exported="false"
android:label="@string/app_name"
android:theme="@style/Theme.MyApplication.Compose">
</activity>
</application>
</manifest>

@ -0,0 +1,40 @@
# 晴天背景设置说明
我们已经将您提供的中国风景图片转换为Android应用的背景资源现在当天气为晴天时应用会自动显示这个中国风景背景并且添加了半透明效果以确保数据清晰可见。
## 已完成的修改
1. 创建了`china_landscape.xml`文件使用XML绘制的方式模拟了您提供的中国风景图片
2. 创建了`sunny_china_landscape.xml`文件,引用中国风景图片并添加半透明白色覆盖层
3. 修改了`Util.java`中的天气背景映射,将晴天背景设置为新创建的中国风景图片
## 如何替换为实际图片
如果您想使用实际的图片而不是XML绘制的模拟图请按照以下步骤操作
1. 将您提供的图片文件重命名为`china_landscape.jpg`或`china_landscape.png`
2. 将图片文件放入`app/src/main/res/drawable`目录
3. 删除我们创建的`china_landscape.xml`文件(可选)
项目会自动使用您提供的实际图片文件而不是XML绘制的模拟图。
## 半透明效果
我们在`sunny_china_landscape.xml`文件中设置了44%透明的白色覆盖层,以确保天气数据能够清晰显示。如果您认为透明度不够或太多,可以修改以下代码中的透明度值:
```xml
<solid android:color="#70FFFFFF" /> <!-- 44%透明白色 -->
```
透明度值范围为00-FF其中
- 00表示完全透明
- FF表示完全不透明
- 70表示约44%的透明度
## 测试方法
当天气为晴天时,应用会自动显示这个背景。您可以通过查询晴天的城市来测试,比如北京。
## 注意事项
Android资源命名规则要求文件名只能包含小写字母(a-z)、数字(0-9)和下划线(_),不能使用点(.)、空格或大写字母。

@ -0,0 +1,252 @@
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.myapplication.model.City;
import com.example.myapplication.utils.CityManager;
import com.example.myapplication.utils.SettingsManager;
import java.util.ArrayList;
import java.util.List;
/**
*
*
*/
public class CityManagerActivity extends AppCompatActivity {
private static final String TAG = "CityManagerActivity";
private EditText etNewCity;
private Button btnAddCity;
private RecyclerView rvCities;
private CityAdapter cityAdapter;
private List<City> cityList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
// 应用主题设置
SettingsManager.applyThemeSetting(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_city_manager);
// 设置工具栏
Toolbar toolbar = findViewById(R.id.city_manager_toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.city_favorites);
}
// 初始化控件
initViews();
// 设置监听器
setupListeners();
// 加载城市列表
loadCities();
}
/**
*
*/
private void initViews() {
etNewCity = findViewById(R.id.et_new_city);
btnAddCity = findViewById(R.id.btn_add_city);
rvCities = findViewById(R.id.rv_cities);
// 设置RecyclerView
rvCities.setLayoutManager(new LinearLayoutManager(this));
cityAdapter = new CityAdapter();
rvCities.setAdapter(cityAdapter);
}
/**
*
*/
private void setupListeners() {
btnAddCity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String cityName = etNewCity.getText().toString().trim();
if (cityName.isEmpty()) {
Toast.makeText(CityManagerActivity.this, "请输入城市名称", Toast.LENGTH_SHORT).show();
return;
}
// 添加城市到收藏列表
boolean added = CityManager.addCity(CityManagerActivity.this, cityName, false);
if (added) {
Toast.makeText(CityManagerActivity.this,
getString(R.string.city_added_to_favorites, cityName),
Toast.LENGTH_SHORT).show();
etNewCity.setText("");
// 刷新城市列表
loadCities();
} else {
Toast.makeText(CityManagerActivity.this,
getString(R.string.city_already_in_favorites, cityName),
Toast.LENGTH_SHORT).show();
}
}
});
}
/**
*
*/
private void loadCities() {
cityList = CityManager.getAllCities(this);
cityAdapter.notifyDataSetChanged();
}
/**
*
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
// 返回主活动并刷新数据(使用平滑过渡)
Intent intent = new Intent(this, WeatherActivity.class);
intent.putExtra("refresh_settings", true);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(intent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
*
*/
@Override
public void onBackPressed() {
super.onBackPressed();
// 返回主活动并刷新数据(使用平滑过渡)
Intent intent = new Intent(this, WeatherActivity.class);
intent.putExtra("refresh_settings", true);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(intent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish();
}
/**
*
*/
private class CityAdapter extends RecyclerView.Adapter<CityAdapter.CityViewHolder> {
@NonNull
@Override
public CityViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_city, parent, false);
return new CityViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CityViewHolder holder, int position) {
City city = cityList.get(position);
holder.tvCityName.setText(city.getName());
// 检查是否为暗黑模式
boolean isDarkMode = SettingsManager.isDarkModeEnabled(CityManagerActivity.this);
// 设置选中状态
if (city.isSelected()) {
holder.tvCityName.setTextColor(getResources().getColor(R.color.colorPrimary));
holder.tvCurrentCity.setVisibility(View.VISIBLE);
} else {
// 根据模式选择文字颜色
if (isDarkMode) {
holder.tvCityName.setTextColor(getResources().getColor(android.R.color.white));
} else {
holder.tvCityName.setTextColor(getResources().getColor(android.R.color.black));
}
holder.tvCurrentCity.setVisibility(View.GONE);
}
// 设置点击事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 设置当前城市
CityManager.setCurrentCity(CityManagerActivity.this, city.getName());
// 刷新列表
loadCities();
Toast.makeText(CityManagerActivity.this,
getString(R.string.set_as_current_city, city.getName()),
Toast.LENGTH_SHORT).show();
}
});
// 设置删除按钮点击事件
holder.btnDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 检查是否是最后一个城市
if (cityList.size() <= 1) {
Toast.makeText(CityManagerActivity.this,
R.string.must_keep_one_city,
Toast.LENGTH_SHORT).show();
return;
}
// 删除城市
CityManager.deleteCity(CityManagerActivity.this, city.getName());
// 刷新列表
loadCities();
Toast.makeText(CityManagerActivity.this,
getString(R.string.city_deleted, city.getName()),
Toast.LENGTH_SHORT).show();
}
});
}
@Override
public int getItemCount() {
return cityList.size();
}
/**
* ViewHolder
*/
class CityViewHolder extends RecyclerView.ViewHolder {
TextView tvCityName;
TextView tvCurrentCity;
ImageButton btnDelete;
CityViewHolder(@NonNull View itemView) {
super(itemView);
tvCityName = itemView.findViewById(R.id.tv_city_name);
tvCurrentCity = itemView.findViewById(R.id.tv_current_city);
btnDelete = itemView.findViewById(R.id.btn_delete_city);
}
}
}
}

@ -0,0 +1,49 @@
package com.example.myapplication
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.myapplication.ui.theme.MyApplicationTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
// 使用Surface作为容器而不是Scaffold
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android", Modifier.padding(16.dp))
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "你好, $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
MyApplicationTheme {
Greeting("Android")
}
}

@ -0,0 +1,302 @@
package com.example.myapplication;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.myapplication.model.Forecast;
import com.example.myapplication.utils.Util;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkhttpActivity extends AppCompatActivity implements View.OnClickListener {
private EditText etCity;
private Button btnRealTime;
private Button btnForecast;
private TextView tvResult;
// API KEY用于访问高德地图天气API
private static final String API_KEY = "919f247fcef78d48763f563b1a194f48";
// 实时天气和天气预报URL模板
private static final String REAL_TIME_URL_TEMPLATE =
"https://restapi.amap.com/v3/weather/weatherInfo?city=CITY&key=" + API_KEY + "&extensions=base";
private static final String FORECAST_URL_TEMPLATE =
"https://restapi.amap.com/v3/weather/weatherInfo?city=CITY&key=" + API_KEY + "&extensions=all";
// OkHttpClient实例
private OkHttpClient client;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_okhttp);
// 初始化OkHttpClient
client = new OkHttpClient();
// 初始化UI控件
initViews();
// 设置点击事件监听器
setupListeners();
}
/**
* UI
*/
private void initViews() {
etCity = findViewById(R.id.et_city);
btnRealTime = findViewById(R.id.btn_real_time);
btnForecast = findViewById(R.id.btn_forecast);
tvResult = findViewById(R.id.tv_result);
}
/**
*
*/
private void setupListeners() {
btnRealTime.setOnClickListener(this);
btnForecast.setOnClickListener(this);
}
@Override
public void onClick(View v) {
String cityName = etCity.getText().toString().trim();
if (cityName.isEmpty()) {
Toast.makeText(this, "请输入城市名称", Toast.LENGTH_SHORT).show();
return;
}
// 获取城市编码
String cityCode = Util.getCityCode(cityName);
if (v.getId() == R.id.btn_real_time) {
// 获取实时天气
getRealTimeWeather(cityCode);
} else if (v.getId() == R.id.btn_forecast) {
// 获取天气预报
getForcastWeather(cityCode);
}
}
/**
*
* @param city
*/
private void getRealTimeWeather(String city) {
// 构建URL替换CITY占位符
String url = REAL_TIME_URL_TEMPLATE.replace("CITY", city);
// 创建请求
Request request = new Request.Builder().url(url).build();
// 创建Call对象
Call call = client.newCall(request);
// 异步执行请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(OkhttpActivity.this, "网络请求失败", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 解析响应数据
String json = response.body().string();
try {
JSONObject object = new JSONObject(json);
String status = object.getString("status");
if ("1".equals(status)) {
JSONArray lives = object.getJSONArray("lives");
if (lives.length() > 0) {
JSONObject liveData = lives.getJSONObject(0);
final String city = liveData.getString("city");
final String weather = liveData.getString("weather");
final int temperature = liveData.getInt("temperature");
final String windDirection = liveData.getString("winddirection");
final String windPower = liveData.getString("windpower");
final int humidity = liveData.getInt("humidity");
final String reportTime = liveData.getString("reporttime");
// 构建结果字符串
final StringBuilder resultBuilder = new StringBuilder();
resultBuilder.append("城市:").append(city).append("\n");
resultBuilder.append("天气:").append(weather).append("\n");
resultBuilder.append("温度:").append(temperature).append("°C\n");
resultBuilder.append("风向:").append(windDirection).append("\n");
resultBuilder.append("风力:").append(windPower).append("级\n");
resultBuilder.append("湿度:").append(humidity).append("%\n");
resultBuilder.append("发布时间:").append(reportTime);
// 在UI线程更新结果
runOnUiThread(new Runnable() {
@Override
public void run() {
tvResult.setText(resultBuilder.toString());
}
});
}
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(OkhttpActivity.this, "获取天气信息失败", Toast.LENGTH_SHORT).show();
}
});
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
/**
*
* @param city
*/
private void getForcastWeather(String city) {
// 构建URL替换CITY占位符
String url = FORECAST_URL_TEMPLATE.replace("CITY", city);
// 创建请求
final Request request = new Request.Builder().url(url).build();
// 创建Call对象
Call call = client.newCall(request);
// 异步执行请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(OkhttpActivity.this, "网络请求失败", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 解析响应数据
String json = response.body().string();
try {
JSONObject object = new JSONObject(json);
String status = object.getString("status");
if ("1".equals(status)) {
JSONArray forecasts = object.getJSONArray("forecasts");
if (forecasts.length() > 0) {
JSONObject forecastData = forecasts.getJSONObject(0);
JSONArray casts = forecastData.getJSONArray("casts");
// 存储解析后的天气预报
final List<Forecast> forecastList = new ArrayList<>();
for (int i = 0; i < casts.length(); i++) {
JSONObject castData = casts.getJSONObject(i);
String date = castData.getString("date");
String dayWeather = castData.getString("dayweather");
String nightWeather = castData.getString("nightweather");
int dayTemp = castData.getInt("daytemp");
int nightTemp = castData.getInt("nighttemp");
String dayWind = castData.getString("daywind");
String nightWind = castData.getString("nightwind");
String dayPower = castData.getString("daypower");
String nightPower = castData.getString("nightpower");
// 计算星期,这里简化处理,使用索引表示
String week;
if (i == 0) {
week = "今天";
} else if (i == 1) {
week = "明天";
} else if (i == 2) {
week = "后天";
} else {
week = "第" + (i+1) + "天";
}
Forecast forecast = new Forecast(
date,
week,
dayWeather,
nightWeather,
dayTemp,
nightTemp,
dayWind,
nightWind,
dayPower,
nightPower
);
forecastList.add(forecast);
}
// 构建结果字符串
final StringBuilder resultBuilder = new StringBuilder();
resultBuilder.append("城市:").append(forecastData.getString("city")).append("\n\n");
for (Forecast fc : forecastList) {
resultBuilder.append("日期:").append(fc.getDate()).append(" ").append(fc.getWeek()).append("\n");
resultBuilder.append("白天天气:").append(fc.getDayWeather()).append("\n");
resultBuilder.append("夜间天气:").append(fc.getNightWeather()).append("\n");
resultBuilder.append("温度范围:").append(fc.getTemperatureRange()).append("\n");
resultBuilder.append("风向:白天 ").append(fc.getDayWind()).append(",夜间 ").append(fc.getNightWind()).append("\n");
resultBuilder.append("风力:白天 ").append(fc.getDayPower()).append("级,夜间 ").append(fc.getNightPower()).append("级\n\n");
}
// 在UI线程更新结果
runOnUiThread(new Runnable() {
@Override
public void run() {
tvResult.setText(resultBuilder.toString());
}
});
}
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(OkhttpActivity.this, "获取天气预报失败", Toast.LENGTH_SHORT).show();
}
});
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
}

@ -0,0 +1,245 @@
package com.example.myapplication;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.MenuItem;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.widget.Toolbar;
import com.example.myapplication.utils.SettingsManager;
/**
*
*
*/
public class SettingsActivity extends AppCompatActivity {
private static final String TAG = "SettingsActivity";
// SharedPreferences 键值
public static final String PREFS_NAME = "WeatherSettings";
public static final String KEY_TEMP_UNIT = "temperature_unit";
public static final String KEY_THEME_MODE = "theme_mode";
// 温度单位常量
public static final String TEMP_UNIT_CELSIUS = "celsius";
public static final String TEMP_UNIT_FAHRENHEIT = "fahrenheit";
private RadioGroup tempUnitGroup;
private RadioButton celsiusRadio;
private RadioButton fahrenheitRadio;
private SwitchCompat themeSwitch; // 使用SwitchCompat而不是Switch
private SharedPreferences preferences;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 使用默认主题,不做任何更改
super.onCreate(savedInstanceState);
try {
setContentView(R.layout.activity_settings);
Log.d(TAG, "设置页面开始初始化");
// 初始化SharedPreferences
preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
// 设置工具栏
setupToolbar();
// 初始化控件
initViews();
// 加载已保存的设置
loadSavedSettings();
// 设置监听器
setupListeners();
Log.d(TAG, "设置页面初始化完成");
} catch (Exception e) {
Log.e(TAG, "初始化设置页面时出错: " + e.getMessage(), e);
Toast.makeText(this, "初始化设置页面时出错,请重试", Toast.LENGTH_SHORT).show();
finish(); // 如果初始化失败,直接关闭页面
}
}
/**
*
*/
private void setupToolbar() {
try {
Toolbar toolbar = findViewById(R.id.settings_toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("设置");
}
} catch (Exception e) {
Log.e(TAG, "设置工具栏失败: " + e.getMessage());
}
}
/**
*
*/
private void initViews() {
try {
tempUnitGroup = findViewById(R.id.temp_unit_group);
celsiusRadio = findViewById(R.id.celsius_radio);
fahrenheitRadio = findViewById(R.id.fahrenheit_radio);
themeSwitch = findViewById(R.id.theme_switch);
} catch (Exception e) {
Log.e(TAG, "初始化控件失败: " + e.getMessage());
throw e; // 重新抛出异常,因为这是致命错误
}
}
/**
*
*/
private void loadSavedSettings() {
try {
// 加载温度单位设置
String tempUnit = preferences.getString(KEY_TEMP_UNIT, TEMP_UNIT_CELSIUS);
if (TEMP_UNIT_CELSIUS.equals(tempUnit)) {
celsiusRadio.setChecked(true);
} else {
fahrenheitRadio.setChecked(true);
}
// 加载主题模式设置
boolean isDarkMode = preferences.getBoolean(KEY_THEME_MODE, false);
themeSwitch.setChecked(isDarkMode);
} catch (Exception e) {
Log.e(TAG, "加载设置失败: " + e.getMessage());
}
}
/**
*
*/
private void setupListeners() {
// 温度单位切换
setupTempUnitListener();
// 主题模式切换
setupThemeListener();
}
/**
*
*/
private void setupTempUnitListener() {
try {
tempUnitGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
try {
SharedPreferences.Editor editor = preferences.edit();
if (checkedId == R.id.celsius_radio) {
editor.putString(KEY_TEMP_UNIT, TEMP_UNIT_CELSIUS);
Log.d(TAG, "温度单位已切换为摄氏度");
} else if (checkedId == R.id.fahrenheit_radio) {
editor.putString(KEY_TEMP_UNIT, TEMP_UNIT_FAHRENHEIT);
Log.d(TAG, "温度单位已切换为华氏度");
}
editor.apply();
Toast.makeText(SettingsActivity.this, "温度单位设置已保存", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e(TAG, "保存温度单位设置失败: " + e.getMessage());
}
}
});
} catch (Exception e) {
Log.e(TAG, "设置温度单位监听器失败: " + e.getMessage());
}
}
/**
*
*/
private void setupThemeListener() {
try {
themeSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
try {
Log.d(TAG, "主题开关切换: " + (isChecked ? "暗黑模式" : "明亮模式"));
// 保存设置
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean(KEY_THEME_MODE, isChecked);
editor.apply();
Toast.makeText(SettingsActivity.this,
"主题已设置为" + (isChecked ? "暗黑模式" : "明亮模式"),
Toast.LENGTH_SHORT).show();
// 延迟应用主题避免UI闪烁
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
try {
// 应用新主题
SettingsManager.applyThemeSetting(SettingsActivity.this);
// 稍微延迟后返回主页并传递设置更新标志
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(SettingsActivity.this, WeatherActivity.class);
intent.putExtra("refresh_settings", true);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
}
}, 300); // 延迟300毫秒让用户感知到切换
} catch (Exception e) {
Log.e(TAG, "应用主题设置失败: " + e.getMessage());
}
}
}, 100); // 延迟100毫秒执行主题切换
} catch (Exception e) {
Log.e(TAG, "保存主题设置失败: " + e.getMessage());
Toast.makeText(SettingsActivity.this, "保存主题设置失败", Toast.LENGTH_SHORT).show();
}
}
});
} catch (Exception e) {
Log.e(TAG, "设置主题切换监听器失败: " + e.getMessage());
}
}
/**
*
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
*
* super
*/
@Override
public void onBackPressed() {
// 在此处可以添加返回前的特殊处理
super.onBackPressed();
}
}

@ -0,0 +1,54 @@
package com.example.myapplication.model;
/**
*
*/
public class City {
private int id; // 城市ID
private String name; // 城市名称
private boolean isSelected; // 是否选中
// 构造方法
public City() {
}
public City(int id, String name, boolean isSelected) {
this.id = id;
this.name = name;
this.isSelected = isSelected;
}
// getter和setter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
@Override
public String toString() {
return "City{" +
"id=" + id +
", name='" + name + '\'' +
", isSelected=" + isSelected +
'}';
}
}

@ -0,0 +1,141 @@
package com.example.myapplication.model;
/**
*
*/
public class Forecast {
private String date; // 预报日期
private String week; // 星期
private String dayWeather; // 白天天气
private String nightWeather; // 夜间天气
private int dayTemperature; // 白天温度
private int nightTemperature; // 夜间温度
private String dayWind; // 白天风向
private String nightWind; // 夜间风向
private String dayPower; // 白天风力
private String nightPower; // 夜间风力
// 构造方法
public Forecast() {
}
public Forecast(String date, String week, String dayWeather, String nightWeather,
int dayTemperature, int nightTemperature, String dayWind,
String nightWind, String dayPower, String nightPower) {
this.date = date;
this.week = week;
this.dayWeather = dayWeather;
this.nightWeather = nightWeather;
this.dayTemperature = dayTemperature;
this.nightTemperature = nightTemperature;
this.dayWind = dayWind;
this.nightWind = nightWind;
this.dayPower = dayPower;
this.nightPower = nightPower;
}
// getter和setter方法
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getWeek() {
return week;
}
public void setWeek(String week) {
this.week = week;
}
public String getDayWeather() {
return dayWeather;
}
public void setDayWeather(String dayWeather) {
this.dayWeather = dayWeather;
}
public String getNightWeather() {
return nightWeather;
}
public void setNightWeather(String nightWeather) {
this.nightWeather = nightWeather;
}
public int getDayTemperature() {
return dayTemperature;
}
public void setDayTemperature(int dayTemperature) {
this.dayTemperature = dayTemperature;
}
public int getNightTemperature() {
return nightTemperature;
}
public void setNightTemperature(int nightTemperature) {
this.nightTemperature = nightTemperature;
}
public String getDayWind() {
return dayWind;
}
public void setDayWind(String dayWind) {
this.dayWind = dayWind;
}
public String getNightWind() {
return nightWind;
}
public void setNightWind(String nightWind) {
this.nightWind = nightWind;
}
public String getDayPower() {
return dayPower;
}
public void setDayPower(String dayPower) {
this.dayPower = dayPower;
}
public String getNightPower() {
return nightPower;
}
public void setNightPower(String nightPower) {
this.nightPower = nightPower;
}
@Override
public String toString() {
return "Forecast{" +
"date='" + date + '\'' +
", week='" + week + '\'' +
", dayWeather='" + dayWeather + '\'' +
", nightWeather='" + nightWeather + '\'' +
", dayTemperature=" + dayTemperature +
", nightTemperature=" + nightTemperature +
", dayWind='" + dayWind + '\'' +
", nightWind='" + nightWind + '\'' +
", dayPower='" + dayPower + '\'' +
", nightPower='" + nightPower + '\'' +
'}';
}
/**
*
* @return "25°C ~ 18°C"
*/
public String getTemperatureRange() {
return dayTemperature + "°C ~ " + nightTemperature + "°C";
}
}

@ -0,0 +1,88 @@
package com.example.myapplication.model;
/**
*
*/
public class WeatherAlert {
private String title; // 预警标题
private String content; // 预警内容
private String level; // 预警级别 (红色,橙色,黄色,蓝色)
private String type; // 预警类型
private String publishTime; // 发布时间
private String city; // 所属城市
// 构造方法
public WeatherAlert() {
}
public WeatherAlert(String title, String content, String level, String type,
String publishTime, String city) {
this.title = title;
this.content = content;
this.level = level;
this.type = type;
this.publishTime = publishTime;
this.city = city;
}
// getter和setter方法
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getPublishTime() {
return publishTime;
}
public void setPublishTime(String publishTime) {
this.publishTime = publishTime;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "WeatherAlert{" +
"title='" + title + '\'' +
", content='" + content + '\'' +
", level='" + level + '\'' +
", type='" + type + '\'' +
", publishTime='" + publishTime + '\'' +
", city='" + city + '\'' +
'}';
}
}

@ -0,0 +1,100 @@
package com.example.myapplication.model;
/**
*
*/
public class WeatherInfo {
private String city; // 城市名称
private String weather; // 天气状况
private int temperature; // 温度
private String windDirection; // 风向
private String windPower; // 风力
private int humidity; // 湿度
private String reportTime; // 发布时间
// 构造方法
public WeatherInfo() {
}
public WeatherInfo(String city, String weather, int temperature,
String windDirection, String windPower, int humidity,
String reportTime) {
this.city = city;
this.weather = weather;
this.temperature = temperature;
this.windDirection = windDirection;
this.windPower = windPower;
this.humidity = humidity;
this.reportTime = reportTime;
}
// getter和setter方法
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getWeather() {
return weather;
}
public void setWeather(String weather) {
this.weather = weather;
}
public int getTemperature() {
return temperature;
}
public void setTemperature(int temperature) {
this.temperature = temperature;
}
public String getWindDirection() {
return windDirection;
}
public void setWindDirection(String windDirection) {
this.windDirection = windDirection;
}
public String getWindPower() {
return windPower;
}
public void setWindPower(String windPower) {
this.windPower = windPower;
}
public int getHumidity() {
return humidity;
}
public void setHumidity(int humidity) {
this.humidity = humidity;
}
public String getReportTime() {
return reportTime;
}
public void setReportTime(String reportTime) {
this.reportTime = reportTime;
}
@Override
public String toString() {
return "WeatherInfo{" +
"city='" + city + '\'' +
", weather='" + weather + '\'' +
", temperature=" + temperature +
", windDirection='" + windDirection + '\'' +
", windPower='" + windPower + '\'' +
", humidity=" + humidity +
", reportTime='" + reportTime + '\'' +
'}';
}
}

@ -0,0 +1,190 @@
package com.example.myapplication.service;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.example.myapplication.model.WeatherAlert;
import com.example.myapplication.utils.HttpUtils;
import com.example.myapplication.utils.WeatherApiConstants;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
*
*/
public class AlertService {
private static final String TAG = "AlertService";
// 创建一个单线程执行器,用于网络请求
private static final Executor executor = Executors.newSingleThreadExecutor();
// 创建一个主线程Handler用于更新UI
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
/**
*
* @param cityName
* @param callback
*/
public static void getWeatherAlerts(final String cityName, final AlertCallback callback) {
Log.d(TAG, "开始获取天气预警: " + cityName);
executor.execute(new Runnable() {
@Override
public void run() {
try {
String url = buildAlertUrl(cityName);
Log.d(TAG, "预警请求URL: " + url);
if (url == null) {
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onError("构建预警URL失败");
}
});
return;
}
String jsonResponse = HttpUtils.sendHttpRequest(url);
if (jsonResponse == null || jsonResponse.isEmpty()) {
Log.e(TAG, "返回的预警JSON为空");
// 模拟空结果,因为天气预警可能没有数据
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onSuccess(new ArrayList<>());
}
});
return;
}
Log.d(TAG, "获取的预警JSON: " + (jsonResponse.length() > 200 ? jsonResponse.substring(0, 200) + "..." : jsonResponse));
final List<WeatherAlert> result = parseAlerts(jsonResponse, cityName);
mainHandler.post(new Runnable() {
@Override
public void run() {
if (result != null) {
Log.d(TAG, "成功解析天气预警,数量: " + result.size());
callback.onSuccess(result);
} else {
Log.e(TAG, "解析天气预警失败");
callback.onError("获取天气预警信息失败");
}
}
});
} catch (final Exception e) {
Log.e(TAG, "获取天气预警异常: " + e.getMessage(), e);
e.printStackTrace();
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onError("网络请求错误: " + e.getMessage());
}
});
}
}
});
}
/**
* JSON
* @param jsonData JSON
* @param cityName
* @return
*/
private static List<WeatherAlert> parseAlerts(String jsonData, String cityName) {
if (jsonData == null || jsonData.isEmpty()) {
Log.e(TAG, "解析天气预警数据失败: JSON数据为空");
return new ArrayList<>();
}
try {
List<WeatherAlert> alertList = new ArrayList<>();
JSONObject jsonObject = new JSONObject(jsonData);
String status = jsonObject.getString("status");
if ("1".equals(status)) {
// 检查是否存在alerts节点
if (jsonObject.has("alerts") && !jsonObject.isNull("alerts")) {
JSONArray alerts = jsonObject.getJSONArray("alerts");
for (int i = 0; i < alerts.length(); i++) {
JSONObject alertObj = alerts.getJSONObject(i);
String title = alertObj.optString("title", "");
String content = alertObj.optString("content", "");
String level = alertObj.optString("level", "");
String type = alertObj.optString("type", "");
String publishTime = alertObj.optString("pub_time", "");
WeatherAlert alert = new WeatherAlert(
title, content, level, type, publishTime, cityName);
alertList.add(alert);
Log.d(TAG, "解析到预警: " + alert.toString());
}
} else {
Log.d(TAG, "当前没有预警信息");
}
} else {
Log.e(TAG, "获取预警信息失败,状态码: " + status);
}
return alertList;
} catch (JSONException e) {
Log.e(TAG, "解析预警JSON失败: " + e.getMessage(), e);
e.printStackTrace();
return null;
}
}
/**
* URL
* @param cityName
* @return URL
*/
private static String buildAlertUrl(String cityName) {
StringBuilder urlBuilder = new StringBuilder(WeatherApiConstants.WEATHER_ALERT_URL);
try {
// 获取城市编码
String cityCode = com.example.myapplication.utils.Util.getCityCode(cityName);
Log.d(TAG, "预警API城市: " + cityName + ", 城市编码: " + cityCode);
urlBuilder.append("?").append(WeatherApiConstants.PARAM_KEY).append("=")
.append(WeatherApiConstants.AMAP_WEB_API_KEY);
urlBuilder.append("&").append(WeatherApiConstants.PARAM_CITY).append("=")
.append(java.net.URLEncoder.encode(cityCode, "UTF-8"));
// 返回JSON格式数据
urlBuilder.append("&").append(WeatherApiConstants.PARAM_OUTPUT).append("=")
.append(WeatherApiConstants.VALUE_OUTPUT_JSON);
String finalUrl = urlBuilder.toString();
Log.d(TAG, "构建的预警URL: " + finalUrl);
return finalUrl;
} catch (Exception e) {
Log.e(TAG, "构建预警URL时出错: " + e.getMessage(), e);
e.printStackTrace();
return null;
}
}
/**
*
*/
public interface AlertCallback {
void onSuccess(List<WeatherAlert> alertList);
void onError(String errorMsg);
}
}

@ -0,0 +1,223 @@
package com.example.myapplication.service;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.example.myapplication.model.Forecast;
import com.example.myapplication.utils.HttpUtils;
import com.example.myapplication.utils.WeatherApiConstants;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
*
*/
public class ForecastService {
private static final String TAG = "ForecastService";
// 创建一个单线程执行器,用于网络请求
private static final Executor executor = Executors.newSingleThreadExecutor();
// 创建一个主线程Handler用于更新UI
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
/**
*
* @param cityName
* @param callback
*/
public static void getWeatherForecast(final String cityName, final ForecastCallback callback) {
Log.d(TAG, "开始获取天气预报: " + cityName);
executor.execute(new Runnable() {
@Override
public void run() {
try {
String url = HttpUtils.buildWeatherUrl(cityName, true);
Log.d(TAG, "请求URL: " + url);
if (url == null) {
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onError("构建URL失败");
}
});
return;
}
String jsonResponse = HttpUtils.sendHttpRequest(url);
if (jsonResponse == null || jsonResponse.isEmpty()) {
Log.e(TAG, "返回的JSON为空尝试使用备用数据");
// 尝试使用备用方案获取数据
jsonResponse = HttpUtils.useBackupForecastApi(cityName);
if (jsonResponse == null || jsonResponse.isEmpty()) {
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onError("获取天气预报失败:所有渠道均无响应");
}
});
return;
} else {
Log.d(TAG, "成功从备用渠道获取天气预报数据");
}
}
Log.d(TAG, "获取的JSON: " + (jsonResponse.length() > 200 ? jsonResponse.substring(0, 200) + "..." : jsonResponse));
final List<Forecast> result = parseForecastWeather(jsonResponse);
mainHandler.post(new Runnable() {
@Override
public void run() {
if (result != null && !result.isEmpty()) {
Log.d(TAG, "成功解析天气预报,共 " + result.size() + " 条");
callback.onSuccess(result);
} else {
Log.e(TAG, "解析天气预报失败或结果为空");
callback.onError("解析天气预报失败");
}
}
});
} catch (final Exception e) {
Log.e(TAG, "获取天气预报异常: " + e.getMessage(), e);
e.printStackTrace();
// 尝试使用备用方案
try {
Log.d(TAG, "尝试使用备用方案获取天气预报");
final String backupResponse = HttpUtils.useBackupForecastApi(cityName);
if (backupResponse != null && !backupResponse.isEmpty()) {
final List<Forecast> backupResult = parseForecastWeather(backupResponse);
mainHandler.post(new Runnable() {
@Override
public void run() {
if (backupResult != null && !backupResult.isEmpty()) {
Log.d(TAG, "成功从备用方案获取天气预报数据");
callback.onSuccess(backupResult);
} else {
callback.onError("所有获取天气预报的尝试均失败");
}
}
});
return;
}
} catch (Exception backupEx) {
Log.e(TAG, "备用方案也失败: " + backupEx.getMessage(), backupEx);
}
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onError("网络请求错误: " + e.getMessage());
}
});
}
}
});
}
/**
* JSON
* @param jsonData JSON
* @return
*/
private static List<Forecast> parseForecastWeather(String jsonData) {
List<Forecast> forecastList = new ArrayList<>();
if (jsonData == null || jsonData.isEmpty()) {
return forecastList;
}
try {
JSONObject jsonObject = new JSONObject(jsonData);
String status = jsonObject.getString("status");
if ("1".equals(status)) {
JSONArray forecasts = jsonObject.getJSONArray("forecasts");
if (forecasts.length() > 0) {
JSONObject forecastData = forecasts.getJSONObject(0);
JSONArray casts = forecastData.getJSONArray("casts");
// 获取当前日期,用于计算星期几
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
String[] weekDays = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
for (int i = 0; i < casts.length(); i++) {
JSONObject castData = casts.getJSONObject(i);
String date = castData.getString("date");
String dayWeather = castData.getString("dayweather");
String nightWeather = castData.getString("nightweather");
int dayTemp = castData.getInt("daytemp");
int nightTemp = castData.getInt("nighttemp");
String dayWind = castData.getString("daywind");
String nightWind = castData.getString("nightwind");
String dayPower = castData.getString("daypower");
String nightPower = castData.getString("nightpower");
// 计算星期几
String week;
try {
Date forecastDate = sdf.parse(date);
calendar.setTime(forecastDate);
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
week = weekDays[dayOfWeek];
} catch (Exception e) {
// 解析日期失败,使用默认值
week = i == 0 ? "今天" : i == 1 ? "明天" : i == 2 ? "后天" : "未知";
}
Forecast forecast = new Forecast(
date,
week,
dayWeather,
nightWeather,
dayTemp,
nightTemp,
dayWind,
nightWind,
dayPower,
nightPower
);
forecastList.add(forecast);
}
}
} else {
// 返回错误信息
System.out.println("API返回错误状态码" + status);
if (jsonObject.has("info")) {
System.out.println("错误信息:" + jsonObject.getString("info"));
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return forecastList;
}
/**
*
*/
public interface ForecastCallback {
void onSuccess(List<Forecast> forecastList);
void onError(String errorMsg);
}
}

@ -0,0 +1,295 @@
package com.example.myapplication.service;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.example.myapplication.model.WeatherInfo;
import com.example.myapplication.utils.HttpUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
*
*/
public class WeatherService {
private static final String TAG = "WeatherService";
// 创建一个单线程执行器,用于网络请求
private static final Executor executor = Executors.newSingleThreadExecutor();
// 创建一个主线程Handler用于更新UI
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
/**
*
* @param cityName
* @param callback
*/
public static void getCurrentWeather(final String cityName, final WeatherCallback callback) {
Log.d(TAG, "开始获取当前天气: " + cityName);
executor.execute(new Runnable() {
@Override
public void run() {
try {
String url = HttpUtils.buildWeatherUrl(cityName, false);
Log.d(TAG, "请求URL: " + url);
if (url == null) {
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onError("构建URL失败");
}
});
return;
}
String jsonResponse = HttpUtils.sendHttpRequest(url);
if (jsonResponse == null || jsonResponse.isEmpty()) {
Log.e(TAG, "返回的JSON为空尝试使用备用数据");
// 尝试使用备用方案获取数据
jsonResponse = HttpUtils.useBackupWeatherApi(cityName);
if (jsonResponse == null || jsonResponse.isEmpty()) {
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onError("获取数据失败,所有渠道均无响应");
}
});
return;
} else {
Log.d(TAG, "成功从备用渠道获取数据");
}
}
Log.d(TAG, "获取的JSON: " + (jsonResponse.length() > 200 ? jsonResponse.substring(0, 200) + "..." : jsonResponse));
final WeatherInfo result = parseCurrentWeather(jsonResponse);
mainHandler.post(new Runnable() {
@Override
public void run() {
if (result != null) {
Log.d(TAG, "成功解析天气: " + result.toString());
callback.onSuccess(result);
} else {
Log.e(TAG, "解析天气失败");
callback.onError("获取天气信息失败");
}
}
});
} catch (final Exception e) {
Log.e(TAG, "获取当前天气异常: " + e.getMessage(), e);
e.printStackTrace();
// 尝试使用备用方案
try {
Log.d(TAG, "尝试使用备用方案获取天气数据");
final String backupResponse = HttpUtils.useBackupWeatherApi(cityName);
if (backupResponse != null && !backupResponse.isEmpty()) {
final WeatherInfo backupResult = parseCurrentWeather(backupResponse);
mainHandler.post(new Runnable() {
@Override
public void run() {
if (backupResult != null) {
Log.d(TAG, "成功从备用方案获取天气数据");
callback.onSuccess(backupResult);
} else {
callback.onError("所有获取天气数据的尝试均失败");
}
}
});
return;
}
} catch (Exception backupEx) {
Log.e(TAG, "备用方案也失败: " + backupEx.getMessage(), backupEx);
}
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onError("网络请求错误: " + e.getMessage());
}
});
}
}
});
}
/**
*
* @param cityName
* @param callback
*/
public static void getWeatherForecast(final String cityName, final ForecastCallback callback) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
String url = HttpUtils.buildWeatherUrl(cityName, true);
String jsonResponse = HttpUtils.sendHttpRequest(url);
final List<WeatherInfo> result = parseForecastWeather(jsonResponse);
mainHandler.post(new Runnable() {
@Override
public void run() {
if (result != null && !result.isEmpty()) {
callback.onSuccess(result);
} else {
callback.onError("获取天气预报失败");
}
}
});
} catch (final Exception e) {
e.printStackTrace();
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onError("网络请求错误: " + e.getMessage());
}
});
}
}
});
}
/**
* JSON
* @param jsonData JSON
* @return
*/
private static WeatherInfo parseCurrentWeather(String jsonData) {
if (jsonData == null || jsonData.isEmpty()) {
Log.e(TAG, "解析当前天气数据失败: JSON数据为空");
return null;
}
try {
JSONObject jsonObject = new JSONObject(jsonData);
String status = jsonObject.getString("status");
if ("1".equals(status)) {
JSONArray lives = jsonObject.getJSONArray("lives");
if (lives.length() > 0) {
JSONObject liveData = lives.getJSONObject(0);
String city = liveData.getString("city");
String weather = liveData.getString("weather");
int temperature = liveData.getInt("temperature");
String windDirection = liveData.getString("winddirection");
String windPower = liveData.getString("windpower");
int humidity = liveData.getInt("humidity");
String reportTime = liveData.getString("reporttime");
Log.d(TAG, "成功解析城市: " + city + ", 天气: " + weather +
", 温度: " + temperature + "°C, 风向: " + windDirection +
", 风力: " + windPower + ", 湿度: " + humidity + "%, 报告时间: " + reportTime);
return new WeatherInfo(city, weather, temperature,
windDirection, windPower, humidity, reportTime);
} else {
Log.e(TAG, "lives数组为空");
}
} else {
// 返回错误信息
Log.e(TAG, "API返回错误状态码: " + status);
if (jsonObject.has("info")) {
Log.e(TAG, "错误信息: " + jsonObject.getString("info"));
}
}
} catch (JSONException e) {
Log.e(TAG, "解析JSON时出错: " + e.getMessage(), e);
e.printStackTrace();
}
return null;
}
/**
* JSON
* @param jsonData JSON
* @return
*/
private static List<WeatherInfo> parseForecastWeather(String jsonData) {
List<WeatherInfo> forecastList = new ArrayList<>();
if (jsonData == null || jsonData.isEmpty()) {
return forecastList;
}
try {
JSONObject jsonObject = new JSONObject(jsonData);
String status = jsonObject.getString("status");
if ("1".equals(status)) {
JSONArray forecasts = jsonObject.getJSONArray("forecasts");
if (forecasts.length() > 0) {
JSONObject forecastData = forecasts.getJSONObject(0);
JSONArray casts = forecastData.getJSONArray("casts");
for (int i = 0; i < casts.length(); i++) {
JSONObject castData = casts.getJSONObject(i);
String date = castData.getString("date");
String dayWeather = castData.getString("dayweather");
String nightWeather = castData.getString("nightweather");
int dayTemp = castData.getInt("daytemp");
int nightTemp = castData.getInt("nighttemp");
String dayWind = castData.getString("daywind");
String nightWind = castData.getString("nightwind");
String dayPower = castData.getString("daypower");
String nightPower = castData.getString("nightpower");
// 使用白天的天气信息作为主要显示
WeatherInfo forecast = new WeatherInfo(
forecastData.getString("city"),
dayWeather,
dayTemp, // 只使用白天温度
dayWind,
dayPower,
0, // 预报中没有湿度数据
date
);
forecastList.add(forecast);
}
}
} else {
// 返回错误信息
System.out.println("API返回错误状态码" + status);
if (jsonObject.has("info")) {
System.out.println("错误信息:" + jsonObject.getString("info"));
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return forecastList;
}
/**
*
*/
public interface WeatherCallback {
void onSuccess(WeatherInfo weatherInfo);
void onError(String errorMsg);
}
/**
*
*/
public interface ForecastCallback {
void onSuccess(List<WeatherInfo> forecastList);
void onError(String errorMsg);
}
}

@ -0,0 +1,135 @@
package com.example.myapplication.ui;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.example.myapplication.R;
import com.example.myapplication.model.WeatherAlert;
import com.example.myapplication.utils.SettingsManager;
import java.util.List;
/**
*
*/
public class AlertAdapter extends RecyclerView.Adapter<AlertAdapter.AlertViewHolder> {
private Context context;
private List<WeatherAlert> alertList;
public AlertAdapter(Context context, List<WeatherAlert> alertList) {
this.context = context;
this.alertList = alertList;
}
@NonNull
@Override
public AlertViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_alert, parent, false);
return new AlertViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull AlertViewHolder holder, int position) {
WeatherAlert alert = alertList.get(position);
// 设置预警标题
holder.tvAlertTitle.setText(alert.getTitle());
// 设置预警内容
holder.tvAlertContent.setText(alert.getContent());
// 设置预警时间
holder.tvAlertTime.setText(alert.getPublishTime());
// 设置预警级别和图标
String level = alert.getLevel();
setAlertLevelIndicator(holder, level);
// 设置点击事件
holder.itemView.setOnClickListener(v -> {
Toast.makeText(context, alert.getTitle() + "\n" + alert.getContent(),
Toast.LENGTH_LONG).show();
});
}
/**
*
*/
private void setAlertLevelIndicator(AlertViewHolder holder, String level) {
int colorRes;
int iconRes;
if (level.contains("红")) {
colorRes = R.color.alert_red;
iconRes = R.drawable.ic_alert_red;
} else if (level.contains("橙")) {
colorRes = R.color.alert_orange;
iconRes = R.drawable.ic_alert_orange;
} else if (level.contains("黄")) {
colorRes = R.color.alert_yellow;
iconRes = R.drawable.ic_alert_yellow;
} else if (level.contains("蓝")) {
colorRes = R.color.alert_blue;
iconRes = R.drawable.ic_alert_blue;
} else {
colorRes = R.color.alert_default;
iconRes = R.drawable.ic_alert_default;
}
// 根据是否为暗黑模式获取适当的颜色
int color = SettingsManager.getDarkModeColor(context, colorRes);
// 将颜色转换为80%不透明度(与主预警容器保持一致)
int alpha = 204; // Alpha值为204表示80%不透明度
int red = android.graphics.Color.red(color);
int green = android.graphics.Color.green(color);
int blue = android.graphics.Color.blue(color);
int semiTransparentColor = android.graphics.Color.argb(alpha, red, green, blue);
holder.vAlertLevel.setBackgroundColor(semiTransparentColor);
// 设置图标(如果有图标资源)
if (iconRes != 0) {
holder.ivAlertIcon.setImageResource(iconRes);
holder.ivAlertIcon.setVisibility(View.VISIBLE);
} else {
holder.ivAlertIcon.setVisibility(View.GONE);
}
}
@Override
public int getItemCount() {
return alertList == null ? 0 : alertList.size();
}
/**
* ViewHolder
*/
public static class AlertViewHolder extends RecyclerView.ViewHolder {
View vAlertLevel; // 预警级别颜色指示器
ImageView ivAlertIcon; // 预警图标
TextView tvAlertTitle; // 预警标题
TextView tvAlertContent; // 预警内容
TextView tvAlertTime; // 预警时间
public AlertViewHolder(@NonNull View itemView) {
super(itemView);
vAlertLevel = itemView.findViewById(R.id.vAlertLevel);
ivAlertIcon = itemView.findViewById(R.id.ivAlertIcon);
tvAlertTitle = itemView.findViewById(R.id.tvAlertTitle);
tvAlertContent = itemView.findViewById(R.id.tvAlertContent);
tvAlertTime = itemView.findViewById(R.id.tvAlertTime);
}
}
}

@ -0,0 +1,11 @@
package com.example.myapplication.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

@ -0,0 +1,58 @@
package com.example.myapplication.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun MyApplicationTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

@ -0,0 +1,34 @@
package com.example.myapplication.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

@ -0,0 +1,84 @@
package com.example.myapplication.utils;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* API
* API
*/
public class APITester {
private static final String TAG = "APITester";
/**
* API访
* @return
*/
public static boolean testWeatherAPI() {
try {
Log.d(TAG, "开始测试天气API");
Log.d(TAG, "使用的API密钥: " + WeatherApiConstants.AMAP_WEB_API_KEY);
// 构建URL
String url = HttpUtils.buildWeatherUrl("北京", false);
if (url == null) {
Log.e(TAG, "URL构建失败");
return false;
}
Log.d(TAG, "测试URL: " + url);
// 发送请求
String response = HttpUtils.sendHttpRequest(url);
if (response == null || response.isEmpty()) {
Log.e(TAG, "API请求失败无响应");
return false;
}
Log.d(TAG, "API返回数据: " + response);
// 解析响应,检查状态码
try {
JSONObject jsonObject = new JSONObject(response);
String status = jsonObject.getString("status");
if ("1".equals(status)) {
Log.d(TAG, "API请求成功状态码: " + status);
// 验证返回的live数据
if (jsonObject.has("lives")) {
JSONArray lives = jsonObject.getJSONArray("lives");
if (lives.length() > 0) {
JSONObject liveData = lives.getJSONObject(0);
String city = liveData.getString("city");
String weather = liveData.getString("weather");
Log.d(TAG, "获取到城市: " + city + ", 天气: " + weather);
return true;
} else {
Log.e(TAG, "lives数组为空");
}
} else {
Log.e(TAG, "返回数据中不包含lives字段");
}
} else {
Log.e(TAG, "API请求返回错误状态码: " + status);
if (jsonObject.has("info")) {
Log.e(TAG, "错误信息: " + jsonObject.getString("info"));
}
}
} catch (JSONException e) {
Log.e(TAG, "解析JSON数据失败: " + e.getMessage(), e);
return false;
}
return false;
} catch (Exception e) {
Log.e(TAG, "API测试异常: " + e.getMessage(), e);
return false;
}
}
}

@ -0,0 +1,212 @@
package com.example.myapplication.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
*
*
*/
public class BackgroundManager {
private static final String TAG = "BackgroundManager";
// SharedPreferences 键值
private static final String PREFS_NAME = "BackgroundSettings";
private static final String KEY_BG_PREFIX = "background_weather_";
// 支持的天气类型
public static final String WEATHER_TYPE_SUNNY = "晴";
public static final String WEATHER_TYPE_CLOUDY = "阴";
public static final String WEATHER_TYPE_RAINY = "雨";
public static final String WEATHER_TYPE_SNOWY = "雪";
public static final String WEATHER_TYPE_FOGGY = "雾";
public static final String WEATHER_TYPE_DEFAULT = "default";
/**
*
* @param context
* @param weatherType
* @param imageUri Uri
* @return
*/
public static boolean saveBackgroundImage(Context context, String weatherType, Uri imageUri) {
try {
// 确保天气类型不为空
if (weatherType == null || weatherType.isEmpty()) {
Log.e(TAG, "天气类型不能为空");
return false;
}
// 获取标准化的天气类型
String normalizedWeatherType = normalizeWeatherType(weatherType);
// 获取输入流
InputStream inputStream = context.getContentResolver().openInputStream(imageUri);
if (inputStream == null) {
Log.e(TAG, "无法打开图片流");
return false;
}
// 读取图片并压缩
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
inputStream.close();
// 压缩图片避免内存问题
if (bitmap.getWidth() > 1920 || bitmap.getHeight() > 1080) {
float ratio = Math.min(1920f / bitmap.getWidth(), 1080f / bitmap.getHeight());
int width = Math.round(bitmap.getWidth() * ratio);
int height = Math.round(bitmap.getHeight() * ratio);
bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
}
// 保存到内部存储
String filename = getFilename(normalizedWeatherType);
FileOutputStream outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
outputStream.close();
// 记录使用自定义背景
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(KEY_BG_PREFIX + normalizedWeatherType, true);
editor.apply();
Log.d(TAG, "背景图片已保存: " + normalizedWeatherType);
return true;
} catch (Exception e) {
Log.e(TAG, "保存背景图片失败: " + e.getMessage(), e);
return false;
}
}
/**
*
* @param context
* @param weatherType
* @return null
*/
public static Bitmap getBackgroundImage(Context context, String weatherType) {
try {
// 获取标准化的天气类型
String normalizedWeatherType = normalizeWeatherType(weatherType);
// 检查是否有自定义背景
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
boolean hasCustomBg = prefs.getBoolean(KEY_BG_PREFIX + normalizedWeatherType, false);
if (!hasCustomBg) {
Log.d(TAG, "该天气类型没有自定义背景: " + normalizedWeatherType);
return null;
}
// 从文件读取图片
String filename = getFilename(normalizedWeatherType);
File file = new File(context.getFilesDir(), filename);
if (!file.exists()) {
Log.e(TAG, "背景图片文件不存在: " + filename);
return null;
}
FileInputStream inputStream = new FileInputStream(file);
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
inputStream.close();
return bitmap;
} catch (IOException e) {
Log.e(TAG, "获取背景图片失败: " + e.getMessage(), e);
return null;
}
}
/**
*
* @param context
* @param weatherType
* @return
*/
public static boolean resetBackground(Context context, String weatherType) {
try {
// 获取标准化的天气类型
String normalizedWeatherType = normalizeWeatherType(weatherType);
// 删除记录
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.remove(KEY_BG_PREFIX + normalizedWeatherType);
editor.apply();
// 删除文件
String filename = getFilename(normalizedWeatherType);
File file = new File(context.getFilesDir(), filename);
if (file.exists()) {
boolean deleted = file.delete();
Log.d(TAG, "背景图片已删除: " + deleted);
return deleted;
}
return true;
} catch (Exception e) {
Log.e(TAG, "重置背景图片失败: " + e.getMessage(), e);
return false;
}
}
/**
*
* @param context
* @param weatherType
* @return
*/
public static boolean hasCustomBackground(Context context, String weatherType) {
// 获取标准化的天气类型
String normalizedWeatherType = normalizeWeatherType(weatherType);
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
return prefs.getBoolean(KEY_BG_PREFIX + normalizedWeatherType, false);
}
/**
*
* @param weatherType
* @return
*/
private static String getFilename(String weatherType) {
return "bg_weather_" + weatherType.replace(" ", "_").toLowerCase() + ".jpg";
}
/**
*
* @param weatherType
* @return
*/
private static String normalizeWeatherType(String weatherType) {
if (weatherType == null || weatherType.isEmpty()) {
return WEATHER_TYPE_DEFAULT;
}
if (weatherType.contains(WEATHER_TYPE_SUNNY)) {
return WEATHER_TYPE_SUNNY;
} else if (weatherType.contains(WEATHER_TYPE_CLOUDY)) {
return WEATHER_TYPE_CLOUDY;
} else if (weatherType.contains(WEATHER_TYPE_RAINY)) {
return WEATHER_TYPE_RAINY;
} else if (weatherType.contains(WEATHER_TYPE_SNOWY)) {
return WEATHER_TYPE_SNOWY;
} else if (weatherType.contains(WEATHER_TYPE_FOGGY)) {
return WEATHER_TYPE_FOGGY;
} else {
return WEATHER_TYPE_DEFAULT;
}
}
}

@ -0,0 +1,236 @@
package com.example.myapplication.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.example.myapplication.model.City;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
*
*
*/
public class CityManager {
private static final String TAG = "CityManager";
private static final String PREFS_NAME = "CityPreferences";
private static final String KEY_CITIES = "cities";
private static final String KEY_CURRENT_CITY = "current_city";
/**
*
* @param context
* @return
*/
public static List<City> getAllCities(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String citiesJson = prefs.getString(KEY_CITIES, "[]");
List<City> cities = new ArrayList<>();
try {
JSONArray jsonArray = new JSONArray(citiesJson);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject cityObj = jsonArray.getJSONObject(i);
City city = new City();
city.setId(cityObj.getInt("id"));
city.setName(cityObj.getString("name"));
city.setSelected(cityObj.getBoolean("isSelected"));
cities.add(city);
}
} catch (JSONException e) {
Log.e(TAG, "解析城市列表失败: " + e.getMessage(), e);
}
// 我们不再自动添加默认城市,允许返回空列表
Log.d(TAG, "获取到 " + cities.size() + " 个收藏城市");
return cities;
}
/**
*
* @param context
* @param cities
*/
public static void saveCities(Context context, List<City> cities) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
JSONArray jsonArray = new JSONArray();
try {
for (City city : cities) {
JSONObject cityObj = new JSONObject();
cityObj.put("id", city.getId());
cityObj.put("name", city.getName());
cityObj.put("isSelected", city.isSelected());
jsonArray.put(cityObj);
}
} catch (JSONException e) {
Log.e(TAG, "序列化城市列表失败: " + e.getMessage(), e);
}
SharedPreferences.Editor editor = prefs.edit();
editor.putString(KEY_CITIES, jsonArray.toString());
editor.apply();
}
/**
*
* @param context
* @param cityName
* @param setAsSelected
* @return
*/
public static boolean addCity(Context context, String cityName, boolean setAsSelected) {
List<City> cities = getAllCities(context);
// 检查是否已存在该城市
for (City city : cities) {
if (city.getName().equals(cityName)) {
if (setAsSelected) {
setCurrentCity(context, cityName);
}
return false; // 城市已存在
}
}
// 生成新的ID
int newId = 1;
if (!cities.isEmpty()) {
newId = cities.get(cities.size() - 1).getId() + 1;
}
// 如果设为当前选中,先将其他城市设为未选中
if (setAsSelected) {
for (City city : cities) {
city.setSelected(false);
}
}
// 添加新城市
City newCity = new City(newId, cityName, setAsSelected);
cities.add(newCity);
// 保存城市列表
saveCities(context, cities);
// 如果设为当前选中,同时更新当前城市
if (setAsSelected) {
setCurrentCity(context, cityName);
}
return true;
}
/**
*
* @param context
* @param cityName
* @return
*/
public static boolean deleteCity(Context context, String cityName) {
List<City> cities = getAllCities(context);
boolean removed = false;
boolean wasSelected = false;
// 找到并删除城市
for (int i = 0; i < cities.size(); i++) {
if (cities.get(i).getName().equals(cityName)) {
wasSelected = cities.get(i).isSelected();
cities.remove(i);
removed = true;
break;
}
}
if (removed) {
// 如果删除的是当前选中城市,则选中列表中的第一个城市
if (wasSelected && !cities.isEmpty()) {
cities.get(0).setSelected(true);
setCurrentCity(context, cities.get(0).getName());
}
// 保存城市列表
saveCities(context, cities);
}
return removed;
}
/**
*
* @param context
* @param cityName
* @return
*/
public static boolean setCurrentCity(Context context, String cityName) {
List<City> cities = getAllCities(context);
boolean found = false;
// 更新选中状态
for (City city : cities) {
boolean isMatch = city.getName().equals(cityName);
city.setSelected(isMatch);
if (isMatch) {
found = true;
}
}
if (found) {
// 保存城市列表
saveCities(context, cities);
// 保存当前城市名称
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(KEY_CURRENT_CITY, cityName);
editor.apply();
}
return found;
}
/**
*
* @param context
* @return
*/
public static City getCurrentCity(Context context) {
List<City> cities = getAllCities(context);
// 首先通过选中状态查找
for (City city : cities) {
if (city.isSelected()) {
return city;
}
}
// 如果没有选中状态的城市从SharedPreferences获取
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String currentCityName = prefs.getString(KEY_CURRENT_CITY, "");
if (!currentCityName.isEmpty()) {
for (City city : cities) {
if (city.getName().equals(currentCityName)) {
city.setSelected(true);
return city;
}
}
}
// 如果还是没有找到,返回列表中的第一个城市
if (!cities.isEmpty()) {
cities.get(0).setSelected(true);
setCurrentCity(context, cities.get(0).getName());
return cities.get(0);
} else {
// 如果没有收藏的城市返回一个临时的City对象不保存到收藏列表
Log.d(TAG, "没有收藏的城市,返回一个临时城市对象");
return new City(0, "", false);
}
}
}

@ -0,0 +1,273 @@
package com.example.myapplication.utils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLEncoder;
/**
*
*/
public class HttpUtils {
private static final String TAG = "HttpUtils";
private static final int CONNECTION_TIMEOUT = 20000; // 20秒
private static final int READ_TIMEOUT = 20000; // 20秒
// 备用API地址
private static final String BACKUP_API = "https://api.map.baidu.com/weather/v1/"; // 示例需要替换为真实的备用API
/**
*
* @param context
* @return
*/
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
boolean isConnected = networkInfo != null && networkInfo.isConnected();
Log.d(TAG, "网络连接状态: " + (isConnected ? "已连接" : "未连接"));
return isConnected;
}
Log.d(TAG, "无法获取连接管理器,网络未连接");
return false;
}
/**
* GET
* @param urlString URL
* @return
*/
public static String sendHttpRequest(String urlString) {
HttpURLConnection connection = null;
BufferedReader reader = null;
StringBuilder response = new StringBuilder();
try {
Log.d(TAG, "开始HTTP请求: " + urlString);
// 尝试DNS预解析
try {
String host = new URL(urlString).getHost();
Log.d(TAG, "尝试DNS预解析: " + host);
InetAddress address = InetAddress.getByName(host);
Log.d(TAG, "DNS解析成功: " + host + " -> " + address.getHostAddress());
} catch (Exception e) {
Log.e(TAG, "DNS预解析失败: " + e.getMessage());
}
URL url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(CONNECTION_TIMEOUT);
connection.setReadTimeout(READ_TIMEOUT);
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("User-Agent", "MinimalistWeather-App");
// 获取响应代码
int responseCode = connection.getResponseCode();
Log.d(TAG, "HTTP响应代码: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
String responseData = response.toString();
Log.d(TAG, "HTTP响应数据: " + (responseData.length() > 200 ?
responseData.substring(0, 200) + "..." : responseData));
return responseData;
} else {
Log.e(TAG, "HTTP请求失败错误代码: " + responseCode);
// 读取错误流
InputStream errorStream = connection.getErrorStream();
if (errorStream != null) {
reader = new BufferedReader(new InputStreamReader(errorStream));
String line;
StringBuilder errorResponse = new StringBuilder();
while ((line = reader.readLine()) != null) {
errorResponse.append(line);
}
Log.e(TAG, "错误响应: " + errorResponse.toString());
}
return null;
}
} catch (Exception e) {
Log.e(TAG, "HTTP请求异常: " + e.getMessage(), e);
e.printStackTrace();
return null;
} finally {
Log.d(TAG, "HTTP请求结束清理资源");
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (connection != null) {
connection.disconnect();
}
}
}
/**
* URL
* @param cityName
* @param isForcast truefalse
* @return URL
*/
public static String buildWeatherUrl(String cityName, boolean isForcast) {
StringBuilder urlBuilder = new StringBuilder(WeatherApiConstants.WEATHER_BASE_URL);
try {
// 获取城市编码
String cityCode = Util.getCityCode(cityName);
Log.d(TAG, "城市: " + cityName + ", 城市编码: " + cityCode);
urlBuilder.append("?").append(WeatherApiConstants.PARAM_KEY).append("=")
.append(WeatherApiConstants.AMAP_WEB_API_KEY);
urlBuilder.append("&").append(WeatherApiConstants.PARAM_CITY).append("=")
.append(URLEncoder.encode(cityCode, "UTF-8"));
// 决定返回实况天气还是预报天气
String extensions = isForcast ?
WeatherApiConstants.VALUE_EXTENSIONS_ALL :
WeatherApiConstants.VALUE_EXTENSIONS_BASE;
urlBuilder.append("&").append(WeatherApiConstants.PARAM_EXTENSIONS).append("=")
.append(extensions);
// 返回JSON格式数据
urlBuilder.append("&").append(WeatherApiConstants.PARAM_OUTPUT).append("=")
.append(WeatherApiConstants.VALUE_OUTPUT_JSON);
String finalUrl = urlBuilder.toString();
Log.d(TAG, "构建的URL: " + finalUrl);
return finalUrl;
} catch (Exception e) {
Log.e(TAG, "构建URL时出错: " + e.getMessage(), e);
e.printStackTrace();
return null;
}
}
/**
* 使API
*/
public static String useBackupWeatherApi(String cityName) {
// 此处应实现备用API的调用逻辑
// 由于目前没有实际的备用API这里只返回模拟数据
String jsonTemplate = "{\n" +
" \"status\": \"1\",\n" +
" \"count\": \"1\",\n" +
" \"info\": \"OK\",\n" +
" \"infocode\": \"10000\",\n" +
" \"lives\": [\n" +
" {\n" +
" \"province\": \"北京\",\n" +
" \"city\": \"" + cityName + "\",\n" +
" \"adcode\": \"110000\",\n" +
" \"weather\": \"晴\",\n" +
" \"temperature\": \"25\",\n" +
" \"winddirection\": \"西南\",\n" +
" \"windpower\": \"4\",\n" +
" \"humidity\": \"30\",\n" +
" \"reporttime\": \"2023-07-14 16:00:00\"\n" +
" }\n" +
" ]\n" +
"}";
Log.d(TAG, "使用备用API返回模拟数据");
return jsonTemplate;
}
/**
* 使API
*/
public static String useBackupForecastApi(String cityName) {
// 此处应实现备用API的调用逻辑
// 由于目前没有实际的备用API这里只返回模拟数据
String jsonTemplate = "{\n" +
" \"status\": \"1\",\n" +
" \"count\": \"1\",\n" +
" \"info\": \"OK\",\n" +
" \"infocode\": \"10000\",\n" +
" \"forecasts\": [\n" +
" {\n" +
" \"city\": \"" + cityName + "\",\n" +
" \"adcode\": \"110000\",\n" +
" \"province\": \"北京\",\n" +
" \"reporttime\": \"2023-07-14 16:00:00\",\n" +
" \"casts\": [\n" +
" {\n" +
" \"date\": \"2023-07-14\",\n" +
" \"week\": \"5\",\n" +
" \"dayweather\": \"晴\",\n" +
" \"nightweather\": \"多云\",\n" +
" \"daytemp\": \"28\",\n" +
" \"nighttemp\": \"18\",\n" +
" \"daywind\": \"西南\",\n" +
" \"nightwind\": \"西南\",\n" +
" \"daypower\": \"4\",\n" +
" \"nightpower\": \"3\"\n" +
" },\n" +
" {\n" +
" \"date\": \"2023-07-15\",\n" +
" \"week\": \"6\",\n" +
" \"dayweather\": \"多云\",\n" +
" \"nightweather\": \"小雨\",\n" +
" \"daytemp\": \"26\",\n" +
" \"nighttemp\": \"17\",\n" +
" \"daywind\": \"东南\",\n" +
" \"nightwind\": \"东南\",\n" +
" \"daypower\": \"3\",\n" +
" \"nightpower\": \"3\"\n" +
" },\n" +
" {\n" +
" \"date\": \"2023-07-16\",\n" +
" \"week\": \"7\",\n" +
" \"dayweather\": \"小雨\",\n" +
" \"nightweather\": \"阴\",\n" +
" \"daytemp\": \"24\",\n" +
" \"nighttemp\": \"16\",\n" +
" \"daywind\": \"东北\",\n" +
" \"nightwind\": \"东北\",\n" +
" \"daypower\": \"3\",\n" +
" \"nightpower\": \"2\"\n" +
" },\n" +
" {\n" +
" \"date\": \"2023-07-17\",\n" +
" \"week\": \"1\",\n" +
" \"dayweather\": \"多云\",\n" +
" \"nightweather\": \"晴\",\n" +
" \"daytemp\": \"25\",\n" +
" \"nighttemp\": \"16\",\n" +
" \"daywind\": \"西北\",\n" +
" \"nightwind\": \"西北\",\n" +
" \"daypower\": \"3\",\n" +
" \"nightpower\": \"2\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}";
Log.d(TAG, "使用备用API返回天气预报模拟数据");
return jsonTemplate;
}
}

@ -0,0 +1,168 @@
package com.example.myapplication.utils;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
import com.example.myapplication.SettingsActivity;
import com.example.myapplication.R;
/**
*
*
*/
public class SettingsManager {
private static final String TAG = "SettingsManager";
/**
*
* @param celsius
* @return
*/
public static int celsiusToFahrenheit(int celsius) {
return (celsius * 9 / 5) + 32;
}
/**
*
* @param context
* @param tempCelsius
* @return
*/
public static String getFormattedTemperature(Context context, int tempCelsius) {
SharedPreferences prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE);
String tempUnit = prefs.getString(SettingsActivity.KEY_TEMP_UNIT, SettingsActivity.TEMP_UNIT_CELSIUS);
if (SettingsActivity.TEMP_UNIT_FAHRENHEIT.equals(tempUnit)) {
int tempFahrenheit = celsiusToFahrenheit(tempCelsius);
return tempFahrenheit + "°F";
} else {
return tempCelsius + "°C";
}
}
/**
*
* @param context
* @param minTempCelsius
* @param maxTempCelsius
* @return
*/
public static String getFormattedTemperatureRange(Context context, int minTempCelsius, int maxTempCelsius) {
SharedPreferences prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE);
String tempUnit = prefs.getString(SettingsActivity.KEY_TEMP_UNIT, SettingsActivity.TEMP_UNIT_CELSIUS);
if (SettingsActivity.TEMP_UNIT_FAHRENHEIT.equals(tempUnit)) {
int minTempF = celsiusToFahrenheit(minTempCelsius);
int maxTempF = celsiusToFahrenheit(maxTempCelsius);
return minTempF + "~" + maxTempF + "°F";
} else {
return minTempCelsius + "~" + maxTempCelsius + "°C";
}
}
/**
*
* @param activity
*/
public static void applyThemeSetting(Activity activity) {
boolean isDarkMode = isDarkModeEnabled(activity);
Log.d(TAG, "应用主题设置: " + (isDarkMode ? "深色模式" : "浅色模式"));
// 设置应用主题但不重建Activity
int currentNightMode = AppCompatDelegate.getDefaultNightMode();
int targetNightMode = isDarkMode ?
AppCompatDelegate.MODE_NIGHT_YES :
AppCompatDelegate.MODE_NIGHT_NO;
if (currentNightMode != targetNightMode) {
Log.d(TAG, "切换夜间模式: " + currentNightMode + " -> " + targetNightMode);
AppCompatDelegate.setDefaultNightMode(targetNightMode);
// 注意这里不调用activity的recreate()方法
// 让系统自动处理Activity的重建这样能确保状态正确保存和恢复
}
}
/**
*
* @param context
* @return 30
*/
public static int getUpdateFrequency(Context context) {
// 不再从偏好设置中读取固定返回30分钟
return 30;
}
/**
* 使
* @param context
* @return 使
*/
public static boolean isFahrenheitEnabled(Context context) {
SharedPreferences prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE);
String tempUnit = prefs.getString(SettingsActivity.KEY_TEMP_UNIT, SettingsActivity.TEMP_UNIT_CELSIUS);
return SettingsActivity.TEMP_UNIT_FAHRENHEIT.equals(tempUnit);
}
/**
*
* @param context
* @return
*/
public static boolean isDarkModeEnabled(Context context) {
SharedPreferences prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE);
return prefs.getBoolean(SettingsActivity.KEY_THEME_MODE, false);
}
/**
* UI
* @param context
* @param resourceId ID
* @return
*/
public static int getDarkModeColor(Context context, int resourceId) {
if (!isDarkModeEnabled(context)) {
return ContextCompat.getColor(context, resourceId);
}
// 根据原始颜色返回更暗的颜色
if (resourceId == R.color.colorPrimary) {
return ContextCompat.getColor(context, R.color.colorPrimaryDark);
} else if (resourceId == R.color.colorAccent) {
return ContextCompat.getColor(context, R.color.colorAccent);
} else if (resourceId == R.color.alert_red) {
return ContextCompat.getColor(context, R.color.alert_red);
} else if (resourceId == R.color.alert_orange) {
return ContextCompat.getColor(context, R.color.alert_orange);
} else if (resourceId == R.color.alert_yellow) {
return ContextCompat.getColor(context, R.color.alert_yellow);
} else if (resourceId == R.color.alert_blue) {
return ContextCompat.getColor(context, R.color.alert_blue);
} else if (resourceId == R.color.alert_default) {
return ContextCompat.getColor(context, R.color.alert_default);
} else {
// 没有特定映射的颜色,返回原始颜色或降低亮度
int originalColor = ContextCompat.getColor(context, resourceId);
return darkenColor(originalColor, 0.7f); // 降低亮度到70%
}
}
/**
*
* @param color
* @param factor (0-1)
* @return
*/
private static int darkenColor(int color, float factor) {
float[] hsv = new float[3];
android.graphics.Color.colorToHSV(color, hsv);
hsv[2] *= factor; // 降低亮度
return android.graphics.Color.HSVToColor(android.graphics.Color.alpha(color), hsv);
}
}

@ -0,0 +1,247 @@
package com.example.myapplication.utils;
import android.content.Context;
import android.content.res.Resources;
import com.example.myapplication.R;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class Util {
// 城市名称到城市编码的映射表
private static final Map<String, String> CITY_MAP = new HashMap<>();
// 天气状况到图片资源ID的映射表 - 白天
private static final Map<String, Integer> WEATHER_IMAGE_DAY_MAP = new HashMap<>();
// 天气状况到图片资源ID的映射表 - 夜晚
private static final Map<String, Integer> WEATHER_IMAGE_NIGHT_MAP = new HashMap<>();
// 天气状态到背景资源ID的映射表
private static final Map<String, Integer> WEATHER_BACKGROUND_MAP = new HashMap<>();
// 静态初始化块,初始化城市映射表
static {
// 初始化部分常用城市编码
CITY_MAP.put("北京", "110000");
CITY_MAP.put("上海", "310000");
CITY_MAP.put("广州", "440100");
CITY_MAP.put("深圳", "440300");
CITY_MAP.put("杭州", "330100");
CITY_MAP.put("南京", "320100");
CITY_MAP.put("天津", "120000");
CITY_MAP.put("重庆", "500000");
CITY_MAP.put("武汉", "420100");
CITY_MAP.put("成都", "510100");
CITY_MAP.put("西安", "610100");
CITY_MAP.put("长沙", "430100");
CITY_MAP.put("郑州", "410100");
CITY_MAP.put("青岛", "370200");
CITY_MAP.put("济南", "370100");
CITY_MAP.put("大连", "210200");
CITY_MAP.put("沈阳", "210100");
CITY_MAP.put("哈尔滨", "230100");
CITY_MAP.put("长春", "220100");
CITY_MAP.put("厦门", "350200");
CITY_MAP.put("福州", "350100");
CITY_MAP.put("南宁", "450100");
CITY_MAP.put("海口", "460100");
CITY_MAP.put("昆明", "530100");
CITY_MAP.put("贵阳", "520100");
CITY_MAP.put("南昌", "360100");
CITY_MAP.put("合肥", "340100");
CITY_MAP.put("兰州", "620100");
CITY_MAP.put("太原", "140100");
CITY_MAP.put("石家庄", "130100");
// 初始化白天天气图片资源映射
WEATHER_IMAGE_DAY_MAP.put("晴", R.drawable.sunny);
WEATHER_IMAGE_DAY_MAP.put("多云", R.drawable.cloudy);
WEATHER_IMAGE_DAY_MAP.put("阴", R.drawable.overcast);
WEATHER_IMAGE_DAY_MAP.put("小雨", R.drawable.light_rain);
WEATHER_IMAGE_DAY_MAP.put("中雨", R.drawable.moderate_rain);
WEATHER_IMAGE_DAY_MAP.put("大雨", R.drawable.heavy_rain);
WEATHER_IMAGE_DAY_MAP.put("暴雨", R.drawable.storm);
WEATHER_IMAGE_DAY_MAP.put("雷阵雨", R.drawable.storm);
WEATHER_IMAGE_DAY_MAP.put("雨夹雪", R.drawable.light_rain);
WEATHER_IMAGE_DAY_MAP.put("阵雨", R.drawable.light_rain);
WEATHER_IMAGE_DAY_MAP.put("雾", R.drawable.overcast);
WEATHER_IMAGE_DAY_MAP.put("霾", R.drawable.overcast);
// 初始化夜间天气图片资源映射
WEATHER_IMAGE_NIGHT_MAP.put("晴", R.drawable.sunny); // 使用同样的图标,后续可以替换为夜间专用图标
WEATHER_IMAGE_NIGHT_MAP.put("多云", R.drawable.cloudy);
WEATHER_IMAGE_NIGHT_MAP.put("阴", R.drawable.overcast);
WEATHER_IMAGE_NIGHT_MAP.put("小雨", R.drawable.light_rain);
WEATHER_IMAGE_NIGHT_MAP.put("中雨", R.drawable.moderate_rain);
WEATHER_IMAGE_NIGHT_MAP.put("大雨", R.drawable.heavy_rain);
WEATHER_IMAGE_NIGHT_MAP.put("暴雨", R.drawable.storm);
WEATHER_IMAGE_NIGHT_MAP.put("雷阵雨", R.drawable.storm);
WEATHER_IMAGE_NIGHT_MAP.put("雨夹雪", R.drawable.light_rain);
WEATHER_IMAGE_NIGHT_MAP.put("阵雨", R.drawable.light_rain);
WEATHER_IMAGE_NIGHT_MAP.put("雾", R.drawable.overcast);
WEATHER_IMAGE_NIGHT_MAP.put("霾", R.drawable.overcast);
// 初始化天气背景映射
WEATHER_BACKGROUND_MAP.put("晴", R.drawable.sunny_background);
WEATHER_BACKGROUND_MAP.put("多云", R.drawable.cloudy_background);
WEATHER_BACKGROUND_MAP.put("阴", R.drawable.cloudy_background);
WEATHER_BACKGROUND_MAP.put("小雨", R.drawable.rainy_background);
WEATHER_BACKGROUND_MAP.put("中雨", R.drawable.rainy_background);
WEATHER_BACKGROUND_MAP.put("大雨", R.drawable.rainy_background);
WEATHER_BACKGROUND_MAP.put("暴雨", R.drawable.rainy_background);
WEATHER_BACKGROUND_MAP.put("雷阵雨", R.drawable.rainy_background);
WEATHER_BACKGROUND_MAP.put("雨夹雪", R.drawable.rainy_background);
WEATHER_BACKGROUND_MAP.put("阵雨", R.drawable.rainy_background);
WEATHER_BACKGROUND_MAP.put("雾", R.drawable.cloudy_background);
WEATHER_BACKGROUND_MAP.put("霾", R.drawable.cloudy_background);
}
/**
*
* @param cityName
* @return
*/
public static String getCityCode(String cityName) {
if (cityName == null || cityName.isEmpty()) {
return "";
}
// 尝试直接从映射表中获取
String cityCode = CITY_MAP.get(cityName);
// 如果找不到对应的城市编码,则返回城市名称本身
// 高德地图API支持使用城市名称直接查询
return cityCode != null ? cityCode : cityName;
}
/**
*
* @param cityCode
* @return
*/
public static String getCityName(String cityCode) {
if (cityCode == null || cityCode.isEmpty()) {
return "";
}
// 遍历映射表查找对应的城市名称
for (Map.Entry<String, String> entry : CITY_MAP.entrySet()) {
if (entry.getValue().equals(cityCode)) {
return entry.getKey();
}
}
// 如果找不到对应的城市名称,则返回编码本身
return cityCode;
}
/**
* ID
* @param weather
* @return ID
*/
public static int getWeatherImage(String weather) {
return getWeatherImageByTime(weather);
}
/**
*
* @return truefalse
*/
public static boolean isDaytime() {
// 获取当前小时
int hour = java.util.Calendar.getInstance().get(java.util.Calendar.HOUR_OF_DAY);
// 假设6点到18点为白天
return hour >= 6 && hour < 18;
}
/**
* ID
* @param weather
* @return ID
*/
public static int getWeatherImageByTime(String weather) {
try {
if (weather == null || weather.isEmpty()) {
return R.drawable.sunny; // 默认图片
}
boolean isDaytime = isDaytime();
Map<String, Integer> imageMap = isDaytime ? WEATHER_IMAGE_DAY_MAP : WEATHER_IMAGE_NIGHT_MAP;
// 尝试精确匹配
Integer imageId = imageMap.get(weather);
if (imageId != null) {
return imageId;
}
// 如果没有精确匹配,尝试模糊匹配
for (Map.Entry<String, Integer> entry : imageMap.entrySet()) {
if (weather.contains(entry.getKey())) {
return entry.getValue();
}
}
// 如果没有匹配到,返回默认图片
return isDaytime ? R.drawable.sunny : R.drawable.sunny;
} catch (Exception e) {
e.printStackTrace();
return R.drawable.sunny;
}
}
/**
* ID
* @param weather
* @return ID
*/
public static int getWeatherBackground(String weather) {
try {
if (weather == null || weather.isEmpty()) {
return R.drawable.sunny_background; // 默认背景
}
// 尝试精确匹配
Integer backgroundId = WEATHER_BACKGROUND_MAP.get(weather);
if (backgroundId != null) {
return backgroundId;
}
// 如果没有精确匹配,尝试模糊匹配
for (Map.Entry<String, Integer> entry : WEATHER_BACKGROUND_MAP.entrySet()) {
if (weather.contains(entry.getKey())) {
return entry.getValue();
}
}
// 如果没有匹配到,返回默认背景
return R.drawable.sunny_background;
} catch (Exception e) {
e.printStackTrace();
return R.drawable.sunny_background;
}
}
/**
* ID使
* @param context
* @param resourceName
* @param defaultResourceId ID
* @return IDID
*/
public static int getResourceIdSafely(Context context, String resourceName, int defaultResourceId) {
try {
Resources resources = context.getResources();
String packageName = context.getPackageName();
int resourceId = resources.getIdentifier(resourceName, "drawable", packageName);
return resourceId != 0 ? resourceId : defaultResourceId;
} catch (Exception e) {
return defaultResourceId;
}
}
}

@ -0,0 +1,33 @@
package com.example.myapplication.utils;
/**
* API
*/
public class WeatherApiConstants {
// 高德地图Web服务API的Key值 - 使用新的有效密钥
public static final String AMAP_WEB_API_KEY = "3d9386dc59dc3b913037b7f67aa5d576";
// 天气查询API的基础URL
public static final String WEATHER_BASE_URL = "https://restapi.amap.com/v3/weather/weatherInfo";
// 天气预警API的基础URL
public static final String WEATHER_ALERT_URL = "https://restapi.amap.com/v3/weather/weatherInfo/alert";
// 请求参数名
public static final String PARAM_KEY = "key"; // API密钥
public static final String PARAM_CITY = "city"; // 城市编码
public static final String PARAM_EXTENSIONS = "extensions"; // 气象类型
public static final String PARAM_OUTPUT = "output"; // 返回格式
// 请求参数值
public static final String VALUE_EXTENSIONS_BASE = "base"; // 返回实况天气
public static final String VALUE_EXTENSIONS_ALL = "all"; // 返回预报天气
public static final String VALUE_OUTPUT_JSON = "JSON"; // 返回JSON格式
public static final String VALUE_OUTPUT_XML = "XML"; // 返回XML格式
// 预警级别常量
public static final String ALERT_LEVEL_RED = "红色"; // 红色预警
public static final String ALERT_LEVEL_ORANGE = "橙色"; // 橙色预警
public static final String ALERT_LEVEL_YELLOW = "黄色"; // 黄色预警
public static final String ALERT_LEVEL_BLUE = "蓝色"; // 蓝色预警
}

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 蓝天背景 -->
<item>
<shape>
<gradient
android:angle="90"
android:startColor="#4A90E2"
android:centerColor="#87CEEB"
android:endColor="#1E90FF"
android:type="linear" />
</shape>
</item>
<!-- 远处的山脉 -->
<item android:top="250dp">
<shape>
<gradient
android:angle="270"
android:startColor="#478563"
android:endColor="#224433"
android:type="linear" />
<corners
android:topLeftRadius="120dp"
android:topRightRadius="100dp" />
</shape>
</item>
<!-- 中间高耸的山峰 -->
<item android:top="200dp" android:left="150dp" android:right="150dp">
<shape>
<gradient
android:angle="270"
android:startColor="#2E8B57"
android:endColor="#006400"
android:type="linear" />
<corners android:topLeftRadius="150dp" android:topRightRadius="30dp" />
</shape>
</item>
<!-- 右侧山峰 -->
<item android:top="180dp" android:left="350dp" android:right="50dp">
<shape>
<gradient
android:angle="270"
android:startColor="#3CB371"
android:endColor="#006400"
android:type="linear" />
<corners android:topLeftRadius="100dp" android:topRightRadius="70dp" />
</shape>
</item>
<!-- 左侧较低的山 -->
<item android:top="230dp" android:left="50dp" android:right="320dp">
<shape>
<gradient
android:angle="270"
android:startColor="#2E8B57"
android:endColor="#006400"
android:type="linear" />
<corners android:topLeftRadius="80dp" android:topRightRadius="120dp" />
</shape>
</item>
<!-- 水面 -->
<item android:top="400dp">
<shape>
<gradient
android:angle="90"
android:startColor="#3d8a76"
android:endColor="#70B8A9"
android:type="linear" />
</shape>
</item>
<!-- 传统亭子/建筑 -->
<item android:top="360dp" android:left="180dp" android:right="180dp" android:bottom="350dp">
<shape>
<solid android:color="#8B4513" /> <!-- 棕色 -->
<corners android:radius="5dp" />
</shape>
</item>
<!-- 建筑屋顶 -->
<item android:top="350dp" android:left="170dp" android:right="170dp" android:bottom="370dp">
<shape>
<solid android:color="#2F4F4F" /> <!-- 深灰绿色 -->
<corners android:radius="2dp" />
</shape>
</item>
<!-- 粉色花朵1 -->
<item android:top="570dp" android:left="30dp" android:right="320dp" android:bottom="320dp">
<shape android:shape="oval">
<solid android:color="#FF69B4" /> <!-- 粉红色 -->
</shape>
</item>
<!-- 粉色花朵2 -->
<item android:top="580dp" android:left="60dp" android:right="300dp" android:bottom="310dp">
<shape android:shape="oval">
<solid android:color="#FF69B4" /> <!-- 粉红色 -->
</shape>
</item>
<!-- 粉色花朵3 -->
<item android:top="570dp" android:left="100dp" android:right="280dp" android:bottom="320dp">
<shape android:shape="oval">
<solid android:color="#FF69B4" /> <!-- 粉红色 -->
</shape>
</item>
<!-- 粉色花朵中心 -->
<item android:top="575dp" android:left="35dp" android:right="325dp" android:bottom="325dp">
<shape android:shape="oval">
<solid android:color="#FFFF00" /> <!-- 黄色花心 -->
</shape>
</item>
<item android:top="585dp" android:left="65dp" android:right="305dp" android:bottom="315dp">
<shape android:shape="oval">
<solid android:color="#FFFF00" /> <!-- 黄色花心 -->
</shape>
</item>
<item android:top="575dp" android:left="105dp" android:right="285dp" android:bottom="325dp">
<shape android:shape="oval">
<solid android:color="#FFFF00" /> <!-- 黄色花心 -->
</shape>
</item>
</layer-list>

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 底层是景观图像 -->
<item>
<shape>
<gradient
android:angle="90"
android:startColor="#87CEEB"
android:endColor="#4682B4"
android:type="linear" />
</shape>
</item>
<!-- 山脉 -->
<item android:top="150dp">
<shape>
<gradient
android:angle="270"
android:startColor="#228B22"
android:endColor="#006400"
android:type="linear" />
<corners
android:topLeftRadius="100dp"
android:topRightRadius="70dp" />
</shape>
</item>
<!-- 右侧山峰 -->
<item android:top="100dp" android:left="250dp" android:right="30dp">
<shape>
<solid android:color="#2E8B57" /> <!-- 海绿色 -->
<corners android:topLeftRadius="80dp" android:topRightRadius="50dp" />
</shape>
</item>
<!-- 左侧山峰 -->
<item android:top="130dp" android:left="50dp" android:right="200dp">
<shape>
<solid android:color="#3CB371" /> <!-- 中绿色 -->
<corners android:topLeftRadius="60dp" android:topRightRadius="90dp" />
</shape>
</item>
<!-- 水面 -->
<item android:top="350dp">
<shape>
<solid android:color="#5F9EA0" /> <!-- 淡蓝绿色 -->
</shape>
</item>
<!-- 传统建筑 -->
<item android:top="300dp" android:left="150dp" android:right="150dp" android:bottom="200dp">
<shape>
<solid android:color="#8B4513" /> <!-- 棕色 -->
<corners android:topLeftRadius="10dp" android:topRightRadius="10dp" />
</shape>
</item>
<!-- 屋顶 -->
<item android:top="280dp" android:left="130dp" android:right="130dp" android:bottom="230dp">
<shape>
<solid android:color="#2F4F4F" /> <!-- 深灰绿色 -->
<corners android:topLeftRadius="20dp" android:topRightRadius="20dp" />
</shape>
</item>
<!-- 粉色花朵1 -->
<item android:top="450dp" android:left="30dp" android:right="320dp" android:bottom="450dp">
<shape android:shape="oval">
<solid android:color="#FF69B4" /> <!-- 粉红色 -->
</shape>
</item>
<!-- 粉色花朵2 -->
<item android:top="470dp" android:left="70dp" android:right="290dp" android:bottom="430dp">
<shape android:shape="oval">
<solid android:color="#FF69B4" /> <!-- 粉红色 -->
</shape>
</item>
<!-- 半透明渐变层,确保数据能够显示 -->
<item>
<shape>
<solid android:color="#70FFFFFF" /> <!-- 更改为44%透明白色,确保数据清晰可见 -->
</shape>
</item>
</layer-list>

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 蓝天背景 -->
<item>
<shape>
<gradient
android:angle="90"
android:startColor="#4A90E2"
android:centerColor="#87CEEB"
android:endColor="#1E90FF"
android:type="linear" />
</shape>
</item>
<!-- 远处的山脉 -->
<item android:top="250dp">
<shape>
<gradient
android:angle="270"
android:startColor="#478563"
android:endColor="#224433"
android:type="linear" />
<corners
android:topLeftRadius="120dp"
android:topRightRadius="100dp" />
</shape>
</item>
<!-- 中间高耸的山峰 -->
<item android:top="200dp" android:left="150dp" android:right="150dp">
<shape>
<gradient
android:angle="270"
android:startColor="#2E8B57"
android:endColor="#006400"
android:type="linear" />
<corners android:topLeftRadius="150dp" android:topRightRadius="30dp" />
</shape>
</item>
<!-- 右侧山峰 -->
<item android:top="180dp" android:left="350dp" android:right="50dp">
<shape>
<gradient
android:angle="270"
android:startColor="#3CB371"
android:endColor="#006400"
android:type="linear" />
<corners android:topLeftRadius="100dp" android:topRightRadius="70dp" />
</shape>
</item>
<!-- 左侧较低的山 -->
<item android:top="230dp" android:left="50dp" android:right="320dp">
<shape>
<gradient
android:angle="270"
android:startColor="#2E8B57"
android:endColor="#006400"
android:type="linear" />
<corners android:topLeftRadius="80dp" android:topRightRadius="120dp" />
</shape>
</item>
<!-- 水面 -->
<item android:top="400dp">
<shape>
<gradient
android:angle="90"
android:startColor="#3d8a76"
android:endColor="#70B8A9"
android:type="linear" />
</shape>
</item>
<!-- 传统亭子/建筑 -->
<item android:top="360dp" android:left="180dp" android:right="180dp" android:bottom="350dp">
<shape>
<solid android:color="#8B4513" /> <!-- 棕色 -->
<corners android:radius="5dp" />
</shape>
</item>
<!-- 建筑屋顶 -->
<item android:top="350dp" android:left="170dp" android:right="170dp" android:bottom="370dp">
<shape>
<solid android:color="#2F4F4F" /> <!-- 深灰绿色 -->
<corners android:radius="2dp" />
</shape>
</item>
<!-- 粉色花朵1 -->
<item android:top="570dp" android:left="30dp" android:right="320dp" android:bottom="320dp">
<shape android:shape="oval">
<solid android:color="#FF69B4" /> <!-- 粉红色 -->
</shape>
</item>
<!-- 粉色花朵2 -->
<item android:top="580dp" android:left="60dp" android:right="300dp" android:bottom="310dp">
<shape android:shape="oval">
<solid android:color="#FF69B4" /> <!-- 粉红色 -->
</shape>
</item>
<!-- 粉色花朵3 -->
<item android:top="570dp" android:left="100dp" android:right="280dp" android:bottom="320dp">
<shape android:shape="oval">
<solid android:color="#FF69B4" /> <!-- 粉红色 -->
</shape>
</item>
<!-- 粉色花朵中心 -->
<item android:top="575dp" android:left="35dp" android:right="325dp" android:bottom="325dp">
<shape android:shape="oval">
<solid android:color="#FFFF00" /> <!-- 黄色花心 -->
</shape>
</item>
<item android:top="585dp" android:left="65dp" android:right="305dp" android:bottom="315dp">
<shape android:shape="oval">
<solid android:color="#FFFF00" /> <!-- 黄色花心 -->
</shape>
</item>
<item android:top="575dp" android:left="105dp" android:right="285dp" android:bottom="325dp">
<shape android:shape="oval">
<solid android:color="#FFFF00" /> <!-- 黄色花心 -->
</shape>
</item>
</layer-list>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M19.36,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.64,-4.96z"/>
</vector>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="90"
android:startColor="#B8D0E6"
android:centerColor="#9AB9D3"
android:endColor="#7A9CBE"
android:type="linear" />
</shape>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#60FFFFFF" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#40CCCCCC" />
<padding
android:left="8dp"
android:top="8dp"
android:right="8dp"
android:bottom="8dp" />
</shape>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M17.66,8L12,8l-1.88,-2L4,6l1.88,2L4,10l1.88,2L4,14l1.88,2L4,18l1.88,2L4,22h12c3.31,0 6,-2.69 6,-6L22,12C22,9.79 20.21,8 18,8h-0.34zM18,16h-4v-2h4v2zM18,12h-4v-2h4v2z"/>
<path
android:fillColor="#0000CD"
android:pathData="M7,13l2,6 2,-6h-4z"/>
<path
android:fillColor="#0000CD"
android:pathData="M11,13l2,6 2,-6h-4z"/>
<path
android:fillColor="#0000CD"
android:pathData="M15,13l2,6 2,-6h-4z"/>
</vector>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/alert_blue">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/alert_default">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/alert_orange">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/alert_red">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/alert_yellow">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M17.66,8L12,8l-1.88,-2L4,6l1.88,2L4,10l1.88,2L4,14l1.88,2L4,18l1.88,2L4,22h12c3.31,0 6,-2.69 6,-6L22,12C22,9.79 20.21,8 18,8h-0.34zM18,16h-4v-2h4v2zM18,12h-4v-2h4v2z"/>
<path
android:fillColor="#00BFFF"
android:pathData="M8,14l2,6 2,-6h-4z"/>
</vector>

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M17.66,8L12,8l-1.88,-2L4,6l1.88,2L4,10l1.88,2L4,14l1.88,2L4,18l1.88,2L4,22h12c3.31,0 6,-2.69 6,-6L22,12C22,9.79 20.21,8 18,8h-0.34zM18,16h-4v-2h4v2zM18,12h-4v-2h4v2z"/>
<path
android:fillColor="#1E90FF"
android:pathData="M8,13l2,6 2,-6h-4z"/>
<path
android:fillColor="#1E90FF"
android:pathData="M14,13l2,6 2,-6h-4z"/>
</vector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#A9A9A9"
android:pathData="M19.36,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.64,-4.96z"/>
</vector>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="90"
android:startColor="#546A7B"
android:centerColor="#465C6D"
android:endColor="#38485A"
android:type="linear" />
</shape>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#60FFFFFF" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#40CCCCCC" />
<padding
android:left="16dp"
android:top="8dp"
android:right="16dp"
android:bottom="8dp" />
</shape>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M17.66,8L12,8l-1.88,-2L4,6l1.88,2L4,10l1.88,2L4,14l1.88,2L4,18l1.88,2L4,22h12c3.31,0 6,-2.69 6,-6L22,12C22,9.79 20.21,8 18,8h-0.34zM18,16h-4v-2h4v2zM18,12h-4v-2h4v2z"/>
<path
android:fillColor="#000080"
android:pathData="M6,13l2,6 2,-6h-4z"/>
<path
android:fillColor="#000080"
android:pathData="M10,13l2,6 2,-6h-4z"/>
<path
android:fillColor="#000080"
android:pathData="M14,13l2,6 2,-6h-4z"/>
<path
android:fillColor="#FFFF00"
android:pathData="M12,15l-3,6h3l-2,5 7,-8h-4l3,-3z"/>
</vector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFF00"
android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM12,2L12,4C6.48,4 2,8.48 2,14s4.48,10 10,10 10,-4.48 10,-10c0,-5.52 -4.48,-10 -10,-10zM12,22c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="90"
android:startColor="#71C3FF"
android:centerColor="#55a4e9"
android:endColor="#3387d5"
android:type="linear" />
</shape>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 底层是我们的中国风景图片 -->
<item>
<bitmap
android:src="@drawable/china_landscape"
android:gravity="center"
android:tileMode="disabled" />
</item>
<!-- 上层是半透明的白色覆盖层,确保数据能够清晰显示 -->
<item>
<shape>
<solid android:color="#70FFFFFF" /> <!-- 44%透明白色 -->
</shape>
</item>
</layer-list>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#03A9F4"
android:endColor="#2196F3"
android:angle="45"/>
</shape>

File diff suppressed because one or more lines are too long

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 底层是我们的中国风景图片 -->
<item>
<bitmap
android:src="@drawable/vertopal_com_china_landscape_img"
android:gravity="center"
android:tileMode="disabled" />
</item>
<!-- 上层是半透明的白色覆盖层,确保数据能够清晰显示 -->
<item>
<shape>
<solid android:color="#70FFFFFF" /> <!-- 44%透明白色 -->
</shape>
</item>
</layer-list>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M13.127,0.688c-3.804,0.011 -7.149,2.527 -8.26,6.123 -3.309,0.333 -5.867,3.132 -5.867,6.522 0,3.624 2.945,6.569 6.569,6.569h15.748c3.624,0 6.569,-2.945 6.569,-6.569 0,-3.39 -2.558,-6.189 -5.867,-6.522 -1.111,-3.596 -4.456,-6.112 -8.26,-6.123zM10.973,15.368c0,0 -0.709,1.543 -1.397,3.214 -0.344,0.835 -0.652,1.731 -0.891,2.451 -0.119,0.36 -0.212,0.673 -0.273,0.891 -0.061,0.218 -0.041,0.288 -0.063,0.333 -0.002,0.004 -0.029,0.043 -0.063,0.063 -0.068,0.041 -0.166,0.063 -0.273,0.063 -0.214,0 -0.433,-0.095 -0.633,-0.209 -0.201,-0.115 -0.389,-0.253 -0.569,-0.4 -0.36,-0.294 -0.695,-0.643 -0.992,-1.003 -0.594,-0.721 -1.079,-1.508 -1.333,-2.179 -0.127,-0.335 -0.212,-0.647 -0.231,-0.912 -0.019,-0.265 0.019,-0.456 0.147,-0.631 0.077,-0.104 0.186,-0.193 0.326,-0.252 0.14,-0.059 0.306,-0.085 0.505,-0.084 0.111,0.001 0.233,0.011 0.357,0.031 0.249,0.042 0.518,0.114 0.798,0.21 0.559,0.192 1.17,0.465 1.73,0.765 1.119,0.599 2.084,1.301 2.084,1.301zM15.431,15.368c0,0 -0.709,1.543 -1.397,3.214 -0.344,0.835 -0.652,1.731 -0.891,2.451 -0.119,0.36 -0.212,0.673 -0.273,0.891 -0.061,0.218 -0.041,0.288 -0.063,0.333 -0.002,0.004 -0.029,0.043 -0.063,0.063 -0.068,0.041 -0.166,0.063 -0.273,0.063 -0.214,0 -0.433,-0.095 -0.633,-0.209 -0.201,-0.115 -0.389,-0.253 -0.569,-0.4 -0.36,-0.294 -0.695,-0.643 -0.992,-1.003 -0.594,-0.721 -1.079,-1.508 -1.333,-2.179 -0.127,-0.335 -0.212,-0.647 -0.231,-0.912 -0.019,-0.265 0.019,-0.456 0.147,-0.631 0.077,-0.104 0.186,-0.193 0.326,-0.252 0.14,-0.059 0.306,-0.085 0.505,-0.084 0.111,0.001 0.233,0.011 0.357,0.031 0.249,0.042 0.518,0.114 0.798,0.21 0.559,0.192 1.17,0.465 1.73,0.765 1.119,0.599 2.084,1.301 2.084,1.301z"/>
</vector>

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 工具栏 -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/city_manager_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<!-- 添加城市区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="#F5F5F5"
android:orientation="horizontal">
<EditText
android:id="@+id/et_new_city"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="@android:color/white"
android:textColor="#333333"
android:textSize="16sp"
android:hint="输入城市名称"
android:maxLines="1"
android:inputType="text" />
<Button
android:id="@+id/btn_add_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/add_to_favorites"
android:textColor="#FFFFFF"
android:backgroundTint="?attr/colorPrimary" />
</LinearLayout>
<!-- 分割线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#DDDDDD" />
<!-- 城市列表 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="已保存的城市"
android:textColor="#666666"
android:textStyle="bold"
android:background="#F5F5F5" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_cities"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:clipToPadding="false"
android:scrollbars="vertical" />
</LinearLayout>

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/et_city"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入城市名称"
android:paddingTop="8dp"
android:paddingBottom="8dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp">
<Button
android:id="@+id/btn_real_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="获取实时天气" />
<Button
android:id="@+id/btn_forecast"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="获取天气预报" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp" />
</ScrollView>
</LinearLayout>

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/settings_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- 温度单位设置 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="温度单位"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"/>
<RadioGroup
android:id="@+id/temp_unit_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/celsius_radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="摄氏度 (°C)"
android:checked="true"
android:layout_marginEnd="16dp"/>
<RadioButton
android:id="@+id/fahrenheit_radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="华氏度 (°F)"/>
</RadioGroup>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#DDDDDD"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"/>
<!-- 主题切换设置 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="暗黑模式"
android:textSize="18sp"
android:textStyle="bold"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/theme_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:switchMinWidth="56dp"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启用后将切换为暗黑主题"
android:textSize="14sp"
android:textColor="#666666"
android:layout_marginTop="4dp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#DDDDDD"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"/>
<!-- 关于应用 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="关于应用"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="极简天气 v1.0"
android:textSize="16sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="一款简洁、易用的天气预报应用"
android:textSize="14sp"
android:textColor="#666666"
android:layout_marginTop="4dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="数据来源高德地图天气API"
android:textSize="14sp"
android:textColor="#666666"
android:layout_marginTop="8dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

@ -0,0 +1,327 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 背景图 - 移至最顶层,覆盖整个屏幕 -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 背景层 -->
<ImageView
android:id="@+id/background_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/timg"/>
<!-- 内容层 - 半透明UI -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 半透明工具栏 -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#80000000"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
<!-- 添加Spinner到Toolbar -->
<Spinner
android:id="@+id/spinner_cities"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="#FFFFFF"
android:popupBackground="#FFFFFF" />
</androidx.appcompat.widget.Toolbar>
<!-- 搜索栏 - 半透明背景 -->
<LinearLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="#80000000"
android:elevation="2dp">
<EditText
android:id="@+id/cityname"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="@drawable/edit_text_background"
android:textColor="#000000"
android:textStyle="bold"
android:hint="输入城市名称"
android:textColorHint="#80666666"
android:shadowColor="#40000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="1"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:layout_marginEnd="8dp"/>
<Button
android:id="@+id/search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textStyle="bold"
android:background="@drawable/rounded_button_background"
android:text="查询"
android:shadowColor="#40FFFFFF"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:elevation="2dp"/>
</LinearLayout>
<!-- 天气内容 -->
<LinearLayout
android:id="@+id/weather_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp"
android:background="#20000000">
<ImageView
android:id="@+id/img"
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_gravity="center"
android:layout_marginBottom="16dp"
android:src="@drawable/sunny"/>
<!-- 城市名称和收藏按钮 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="城市:"
android:textColor="#FFFFFF"
android:textSize="32sp"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="3"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_favorite"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:background="@null"
android:src="@android:drawable/btn_star_big_off"
android:contentDescription="@string/favorite_button_desc"/>
</LinearLayout>
<TextView
android:id="@+id/weather"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="天气:"
android:textColor="#FFFFFF"
android:textSize="26sp"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:layout_marginTop="8dp" />
<TextView
android:id="@+id/temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="温度:"
android:textColor="#FFFFFF"
android:textSize="34sp"
android:textStyle="bold"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="3"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp"
android:gravity="center">
<TextView
android:id="@+id/wind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="风力风向:"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:layout_marginEnd="16dp" />
<TextView
android:id="@+id/humidity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="湿度:"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2" />
</LinearLayout>
</LinearLayout>
<!-- 天气预警容器 - 美化 -->
<LinearLayout
android:id="@+id/alertContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#60000000"
android:visibility="gone"
android:padding="8dp"
android:elevation="4dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="天气预警信息"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:padding="4dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvAlerts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:visibility="gone"
android:padding="4dp"/>
<TextView
android:id="@+id/tvNoAlerts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="当前无预警信息"
android:textColor="#FFFFFF"
android:padding="8dp"
android:gravity="center"/>
</LinearLayout>
<!-- 预报栏 - 美化 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:layout_marginTop="10dp"
android:background="#60000000"
android:orientation="horizontal"
android:elevation="4dp">
<TextView
android:id="@+id/forcast1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="未来天气"
android:layout_weight="1"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:padding="4dp" />
<TextView
android:id="@+id/forcast2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="未来天气"
android:layout_weight="1"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:padding="4dp" />
<TextView
android:id="@+id/forcast3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="未来天气"
android:layout_weight="1"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:padding="4dp" />
<TextView
android:id="@+id/forcast4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="未来天气"
android:layout_weight="1"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:padding="4dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</LinearLayout>

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#80FFFFFF">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- 预警级别颜色条 -->
<View
android:id="@+id/vAlertLevel"
android:layout_width="8dp"
android:layout_height="match_parent"
android:background="@color/alert_default" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:padding="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<!-- 预警图标 -->
<ImageView
android:id="@+id/ivAlertIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:src="@android:drawable/ic_dialog_alert"
android:contentDescription="预警图标" />
<!-- 预警标题 -->
<TextView
android:id="@+id/tvAlertTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/black"
android:text="暴雨预警" />
<!-- 预警时间 -->
<TextView
android:id="@+id/tvAlertTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@color/light_text_secondary"
android:text="2023-08-01 10:00" />
</LinearLayout>
<!-- 预警内容 -->
<TextView
android:id="@+id/tvAlertContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textSize="14sp"
android:textColor="@color/light_text_primary"
android:maxLines="2"
android:ellipsize="end"
android:text="预计未来6小时本市将有大到暴雨伴有雷电和大风请注意防范。" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<!-- 城市名称 -->
<TextView
android:id="@+id/tv_city_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/btn_delete_city"
android:text="北京"
android:textColor="#333333"
android:textSize="18sp"
android:textStyle="bold" />
<!-- 当前城市标记 -->
<TextView
android:id="@+id/tv_current_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_city_name"
android:layout_alignParentStart="true"
android:layout_marginTop="4dp"
android:text="当前城市"
android:textColor="?attr/colorPrimary"
android:textSize="12sp"
android:visibility="gone" />
<!-- 删除按钮 -->
<ImageButton
android:id="@+id/btn_delete_city"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="删除城市"
android:padding="8dp"
android:src="@android:drawable/ic_menu_delete" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="#333333"
android:padding="12dp"
android:gravity="start"
android:background="?android:attr/selectableItemBackground"
android:singleLine="true"
android:ellipsize="end" />

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="?android:attr/textColorPrimary"
android:padding="10dp"
android:gravity="start"
android:singleLine="true"
android:ellipsize="end" />

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_refresh"
android:orderInCategory="100"
android:title="刷新"
android:icon="@android:drawable/ic_popup_sync"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_gallery"
android:orderInCategory="101"
android:title="设置天气背景"
android:icon="@android:drawable/ic_menu_gallery"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_reset_background"
android:orderInCategory="102"
android:title="恢复天气默认背景"
android:icon="@android:drawable/ic_menu_revert"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_city_manager"
android:orderInCategory="103"
android:title="城市管理"
android:icon="@android:drawable/ic_dialog_map"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_share"
android:orderInCategory="104"
android:title="@string/share"
android:icon="@android:drawable/ic_menu_share"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_settings"
android:orderInCategory="105"
android:title="设置"
android:icon="@android:drawable/ic_menu_preferences"
app:showAsAction="ifRoom" />
</menu>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 暗黑模式下的颜色设置 - 更暗的颜色值 -->
<color name="colorPrimary">#1A4559</color>
<color name="colorPrimaryDark">#0A1B24</color>
<color name="colorAccent">#B32D59</color>
<color name="dark_background">#080808</color>
<color name="dark_text_primary">#BBBBBB</color>
<color name="dark_text_secondary">#888888</color>
<color name="dark_status_bar">#000000</color>
<color name="text_color_dark">#BBBBBB</color>
<!-- 暗黑模式下预警颜色 - 降低亮度 -->
<color name="alert_red">#B82E2A</color>
<color name="alert_orange">#B36800</color>
<color name="alert_yellow">#AA9500</color>
<color name="alert_blue">#1667A8</color>
<color name="alert_default">#666666</color>
</resources>

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 夜间模式主题 -->
<style name="Theme.MyApplication" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:windowBackground">@color/dark_background</item>
<item name="android:statusBarColor">@color/dark_status_bar</item>
<item name="android:navigationBarColor">@color/dark_status_bar</item>
</style>
<!-- Compose应用专用夜间主题 -->
<style name="Theme.MyApplication.Compose" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- 添加必要的设置以支持Compose -->
<item name="android:statusBarColor">@color/dark_status_bar</item>
<item name="android:navigationBarColor">@color/dark_status_bar</item>
<item name="android:windowBackground">@color/dark_background</item>
</style>
<!-- 暗黑模式主题 - 降低整体亮度 -->
<style name="Theme.MinimalistWeather" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@color/dark_background</item>
<item name="android:textColorPrimary">@color/dark_text_primary</item>
<item name="android:textColorSecondary">@color/dark_text_secondary</item>
<item name="android:statusBarColor">@color/dark_status_bar</item>
<item name="android:navigationBarColor">@color/dark_status_bar</item>
<item name="actionBarStyle">@style/DarkActionBar</item>
<item name="toolbarStyle">@style/DarkToolbar</item>
</style>
<!-- 暗黑模式下的ActionBar样式 -->
<style name="DarkActionBar" parent="Widget.AppCompat.ActionBar">
<item name="background">@color/colorPrimaryDark</item>
<item name="titleTextStyle">@style/DarkTitleText</item>
</style>
<!-- 暗黑模式下的Toolbar样式 -->
<style name="DarkToolbar" parent="Widget.AppCompat.Toolbar">
<item name="android:background">@color/colorPrimaryDark</item>
<item name="titleTextColor">@color/dark_text_primary</item>
<item name="subtitleTextColor">@color/dark_text_secondary</item>
</style>
<!-- 暗黑模式下的标题文本样式 -->
<style name="DarkTitleText" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
<item name="android:textColor">@color/dark_text_primary</item>
</style>
</resources>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="colorPrimary">#3388AA</color>
<color name="colorPrimaryDark">#1F6F91</color>
<color name="colorAccent">#FF4081</color>
<color name="light_background">#FFFFFF</color>
<color name="light_text_primary">#333333</color>
<color name="light_text_secondary">#757575</color>
<color name="light_status_bar">#3388AA</color>
<color name="dark_background">#121212</color>
<color name="dark_text_primary">#E1E1E1</color>
<color name="dark_text_secondary">#AAAAAA</color>
<color name="dark_status_bar">#000000</color>
<color name="text_color_dark">#E1E1E1</color>
<color name="text_color_light">#333333</color>
<!-- 天气预警颜色 -->
<color name="alert_red">#FFE53935</color>
<color name="alert_orange">#FFFF9800</color>
<color name="alert_yellow">#FFFFEB3B</color>
<color name="alert_blue">#FF2196F3</color>
<color name="alert_default">#FF9E9E9E</color>
</resources>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save