目录
一、测试环境说明
电脑环境
Windows 11
编写语言
JAVA
开发软件
Android Studio (2020)
开发软件只要大于等于测试版本即可(近几年官网直接下载也可以),若是版本低于测试版本请自行测试。项目需要根据你的软件自行适配
二、项目简介
该项目采用和风天气API接口,并且实现了自动定位当前位置(百度定位SDK),语音播报功能(讯飞语音合成技术),天气通知功能等,自动定位、语音播报需要使用真机进行测试,模拟器无法实现对应功能。
该项目使用Android Studio软件 JAVA语言,Room数据库开发完成的一款天气预报App。
实时展示温度湿度等气象数据、提供24小时精准预报和未来15天趋势预测。空气质量、风向、生活建议,并且可以展示每小时每天的具体天气情况。
三、项目演示
网络资源模板--基于Android studio 天气预报App
四、部设计详情(部分)
首页
1.页面的结构
页面采用经典的ConstraintLayout约束布局,整体分为顶部工具栏、下拉刷新容器、嵌套滚动视图三大部分。
滚动视图内包含天气信息展示区、小时预报横向列表、多日预报纵向列表、空气质量环形进度面板、风向风力可视化风车组件以及生活建议列表等多个功能模块,通过合理的层级结构和约束关系实现复杂界面布局。
2.使用到的技术
技术运用方面融合了MVVM架构、LiveData数据观察、权限动态请求、百度定位SDK、RecyclerView多类型列表、下拉刷新控件和自定义视图组件。
同时集成讯飞语音合成技术实现天气播报功能,实现后台定时任务调度,形成完整的技术栈体系。
3.页面详细介绍
页面核心功能包括自动定位获取当地天气、支持城市切换搜索、实时展示温度湿度等气象数据、提供24小时精准预报和未来15天趋势预测。
创新性地采用环形进度条可视化空气质量指数,配合动态风车展示风力风向,生活指数模块提供穿衣、运动等多项实用建议,形成全方位的天气服务平台。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/lay_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg2"
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity">
<!--顶部标题-->
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/materialToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:ellipsize="middle"
android:singleLine="true"
android:text="城市天气"
android:textColor="@color/white"
android:textSize="@dimen/sp_16" />
</com.google.android.material.appbar.MaterialToolbar>
<!-- 可以优化成这样就不用带textview了,但是要在activity改
< com.google.android.material.appbar.MaterialToolbar-->
<!-- android:id="@+id/materialToolbar"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:fitsSystemWindows="true"-->
<!-- app:title="城市天气"-->
<!-- app:titleTextColor="@color/white"-->
<!-- app:titleCentered="true"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent"/>-->
<!--下拉刷新视图-->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/lay_refresh"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/materialToolbar">
<!--滚动视图-->
<androidx.core.widget.NestedScrollView
android:id="@+id/lay_scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--页面主要内容视图-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_0">
<!--滑动距离布局-->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/lay_scroll_height"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!--天气状况-->
<TextView
android:id="@+id/tv_week"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:layout_marginTop="@dimen/dp_8"
android:text="星期几"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!--播放图标-->
<ImageView
android:id="@+id/iv_voice_broadcast"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:background="@mipmap/icon_broadcast"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/tv_week"
app:layout_constraintLeft_toRightOf="@+id/tv_week"
app:layout_constraintTop_toTopOf="@+id/tv_week" />
<!--播报状态-->
<TextView
android:id="@+id/tv_broadcast_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_12"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="@+id/iv_voice_broadcast"
app:layout_constraintLeft_toRightOf="@+id/iv_voice_broadcast"
app:layout_constraintTop_toTopOf="@+id/iv_voice_broadcast" />
<!--温度-->
<TextView
android:id="@+id/tv_temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_24"
android:text="0"
android:textColor="@color/white"
android:textSize="@dimen/sp_60"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_week" />
<!--摄氏度符号-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="℃"
android:textColor="@color/white"
android:textSize="@dimen/sp_24"
app:layout_constraintStart_toEndOf="@+id/tv_temp"
app:layout_constraintTop_toTopOf="@+id/tv_temp" />
<!--当天最高温和最低温-->
<LinearLayout
android:id="@+id/lay_temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="@+id/tv_temp"
app:layout_constraintStart_toStartOf="@+id/tv_temp"
app:layout_constraintTop_toBottomOf="@+id/tv_temp">
<!--最高温-->
<TextView
android:id="@+id/tv_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="@dimen/sp_14" />
<!--最低温-->
<TextView
android:id="@+id/tv_low"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/temp_min_tx"
android:textSize="@dimen/sp_14" />
</LinearLayout>
<!--天气状况和空气质量-->
<LinearLayout
android:id="@+id/lay_info_air_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="@+id/lay_temp"
app:layout_constraintStart_toStartOf="@+id/lay_temp"
app:layout_constraintTop_toBottomOf="@+id/lay_temp">
<!--天气状况-->
<TextView
android:id="@+id/tv_weather_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="@dimen/sp_14" />
<!--空气质量-->
<TextView
android:id="@+id/tv_air_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/dp_8"
android:textColor="@color/white"
android:textSize="@dimen/sp_14" />
</LinearLayout>
<!--城市-->
<TextView
android:id="@+id/tv_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="城市"
android:textColor="@color/white"
android:textSize="@dimen/sp_20"
app:layout_constraintEnd_toEndOf="@+id/tv_temp"
app:layout_constraintStart_toStartOf="@+id/tv_temp"
app:layout_constraintTop_toBottomOf="@+id/lay_info_air_info" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!--App名称-->
<TextView
android:id="@+id/tv_app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:layout_marginTop="@dimen/dp_16"
android:drawableStart="@mipmap/icon_weather_sun"
android:drawablePadding="@dimen/dp_4"
android:text="祝你有个好心情"
android:textColor="@color/white"
android:textSize="@dimen/sp_12"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lay_scroll_height" />
<!--上一次更新时间-->
<TextView
android:id="@+id/tv_update_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dp_16"
android:text="最近更新时间:"
android:textColor="@color/white"
android:textSize="@dimen/sp_12"
app:layout_constraintBottom_toBottomOf="@+id/tv_app_name"
app:layout_constraintEnd_toEndOf="@+id/lay_scroll_height"
app:layout_constraintTop_toTopOf="@+id/tv_app_name" />
<!--分隔线 增加UI效果-->
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_1"
android:layout_marginStart="@dimen/dp_16"
android:layout_marginTop="@dimen/dp_8"
android:layout_marginEnd="@dimen/dp_16"
android:alpha="0.1"
android:background="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_app_name" />
<!--逐小时天气预报列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_hourly"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_16"
android:paddingStart="@dimen/dp_16"
android:paddingEnd="@dimen/dp_16"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view" />
<!--天气预报列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_daily"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rv_hourly" />
<!--空气质量-->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/lay_air"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/rv_daily"
tools:layout_editor_absoluteX="16dp">
<!--空气质量-->
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="空气质量"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!--污染指数-->
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="污染指数"
android:textColor="#DAEBEE"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3" />
<com.work.goodweather.ui.view.RoundProgressBar
android:id="@+id/rpb_aqi"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toStartOf="@+id/guideline3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView4"
app:round_bg_color="#C6D7F4"
app:round_progress_color="#FBFEF7" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="205dp" />
<TextView
android:id="@+id/textView5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="PM10"
android:textColor="@color/ari_tx_color"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/tv_pm10"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView4"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/textView4" />
<TextView
android:id="@+id/tv_pm10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/textView5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView5" />
<TextView
android:id="@+id/textView6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="PM2.5"
android:textColor="@color/ari_tx_color"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/tv_pm10"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/textView5" />
<TextView
android:id="@+id/tv_pm25"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/textView6"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView5" />
<!--NO2 二氧化氮-->
<LinearLayout
android:id="@+id/lay_air_no"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
app:layout_constraintEnd_toStartOf="@+id/tv_pm10"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/textView6">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="NO"
android:textColor="@color/ari_tx_color"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:text="2"
android:textColor="@color/ari_tx_color"
android:textSize="8sp" />
</LinearLayout>
<TextView
android:id="@+id/tv_no2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/lay_air_no"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView5" />
<!--NO2 二氧化硫-->
<LinearLayout
android:id="@+id/lay_air_so"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
app:layout_constraintEnd_toStartOf="@+id/tv_pm10"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/lay_air_no">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SO"
android:textColor="@color/ari_tx_color"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:text="2"
android:textColor="@color/ari_tx_color"
android:textSize="8sp" />
</LinearLayout>
<TextView
android:id="@+id/tv_so2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/lay_air_so"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView5" />
<!--O3 臭氧-->
<LinearLayout
android:id="@+id/lay_air_o3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
app:layout_constraintEnd_toStartOf="@+id/tv_pm10"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/lay_air_so">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="O"
android:textColor="@color/ari_tx_color"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:text="3"
android:textColor="@color/ari_tx_color"
android:textSize="8sp" />
</LinearLayout>
<TextView
android:id="@+id/tv_o3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/lay_air_o3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView5" />
<TextView
android:id="@+id/textView7"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="CO"
android:textColor="@color/ari_tx_color"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/tv_pm10"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/lay_air_o3" />
<TextView
android:id="@+id/tv_co"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/textView7"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView5" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!--风向风力-->
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:layout_marginTop="16dp"
android:text="风向风力"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lay_air" />
<!--大风车-->
<com.work.goodweather.ui.view.WhiteWindmills
android:id="@+id/ww_big"
android:layout_width="100dp"
android:layout_height="120dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2"
tools:ignore="MissingConstraints" />
<!--小风车-->
<com.work.goodweather.ui.view.WhiteWindmills
android:id="@+id/ww_small"
android:layout_width="50dp"
android:layout_height="60dp"
android:layout_marginStart="32dp"
app:layout_constraintBottom_toBottomOf="@+id/ww_big"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="@+id/ww_big"
tools:ignore="MissingConstraints" />
<!--纵向辅助线-->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="205dp" />
<!--风向风力文字-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@+id/ww_big"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/ww_big">
<!--风向-->
<TextView
android:id="@+id/tv_wind_direction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="@dimen/sp_14" />
<!--风力-->
<TextView
android:id="@+id/tv_wind_power"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_24"
android:textColor="@color/white"
android:textSize="@dimen/sp_14" />
</LinearLayout>
<!--生活建议-->
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:layout_marginTop="16dp"
android:text="生活建议"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ww_big" />
<!--生活建议列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_lifestyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingStart="@dimen/dp_16"
android:paddingEnd="@dimen/dp_16"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
城市管理
1.页面的结构
页面采用简洁的线性布局结构,顶部为标题工具栏,中部为城市列表展示区域,底部为添加城市按钮。
城市列表项支持左右滑动操作,采用卡片式布局展示已保存的城市信息,整体界面清晰直观,符合材料设计规范。
2.使用到的技术
技术实现采用RecyclerView展示城市数据,配合ItemTouchHelper实现侧滑删除功能。
使用LiveData进行数据观察更新,通过AlertDialog实现删除确认交互。
采用MVVM架构进行数据管理,实现界面与数据的分离。
3.页面详细介绍
该页面主要提供城市管理功能,用户可查看已保存的城市列表,通过侧滑删除不需要的城市数据,点击底部按钮可弹出城市选择对话框添加新城市。
选择城市后会返回结果到天气主页面,实现城市数据的动态维护和快速切换。
package com.work.goodweather.ui;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import com.work.goodweather.Constant;
import com.work.goodweather.R;
import com.work.goodweather.databinding.ActivityManageCityBinding;
import com.work.goodweather.db.bean.MyCity;
import com.work.goodweather.ui.adapter.MyCityAdapter;
import com.work.goodweather.utils.AddCityDialog;
import com.work.goodweather.viewmodel.ManageCityViewModel;
import com.work.library.base.NetworkActivity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ManageCityActivity extends NetworkActivity<ActivityManageCityBinding> {
private ManageCityViewModel viewModel;
private final List<MyCity> myCityList = new ArrayList<>();
private final MyCityAdapter myCityAdapter = new MyCityAdapter(myCityList);
@Override
protected void onCreate() {
initView();
viewModel = new ViewModelProvider(this).get(ManageCityViewModel.class);
viewModel.getAllCityData();
}
private void initView() {
backAndFinish(binding.toolbar);
setStatusBar(true);
myCityAdapter.setOnClickItemCallback(position -> setPageResult(myCityList.get(position).getCityName()));
/**ManageCityActivity.this是ManageCityActivity的上下文(Context),它是创建LinearLayoutManager实例时必需的参数,
因为布局管理器需要知道它所属的Activity或Fragment的上下文信息,以便正确地测量和布局子项。
*/
binding.rvCity.setLayoutManager(new LinearLayoutManager(ManageCityActivity.this));
binding.rvCity.setAdapter(myCityAdapter);
// binding.btnAddCity.setOnClickListener(v -> showMsg("添加城市"));
ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
//控制快速滑动的方向
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(0, swipeFlags);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
//显示提示弹窗
showDeleteCity(viewHolder.getAdapterPosition());
}
});
//关联recyclerView
helper.attachToRecyclerView(binding.rvCity);
binding.btnAddCity.setOnClickListener(v ->
AddCityDialog.show(ManageCityActivity.this, Arrays.asList(Constant.CITY_ARRAY), cityName -> {
//保存到数据库中
viewModel.addMyCityData(cityName);
//设置页面返回数据
setPageResult(cityName);
}));
}
private void showDeleteCity(int position) {
// 声明对象
AlertDialog dialog;
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle("删除城市")
.setIcon(R.drawable.ic_round_delete_forever_24)
.setMessage("您确定要删除吗?")
.setPositiveButton("确定", (dialog1, which) -> {
MyCity myCity = myCityList.get(position);
myCityList.remove(position);
myCityAdapter.notifyItemRemoved(position);
viewModel.deleteMyCityData(myCity);
dialog1.dismiss();
}).setNegativeButton("取消", (dialog12, which) -> {
myCityAdapter.notifyItemChanged(position);
dialog12.dismiss();
});
dialog = builder.create();
dialog.show();
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.GRAY);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.BLACK);
}
@Override
protected void onObserveData() {
/**
* 在Android开发中,myCities -> {...} 是一个Lambda表达式的使用,它特别用于实现Observer接口的方法
* (如LiveData的observe方法中的Observer回调)。从Android Jetpack的LiveData组件开始,这种简洁的语法被广泛用于响应数据变化。*/
viewModel.listMutableLiveData.observe(this,myCities -> {
if (myCities != null && myCities.size() > 0) {
myCityList.clear();
myCityList.addAll(myCities);
myCityAdapter.notifyDataSetChanged();
} else {
showMsg("空空如也");
}
});
}
/**
* 设置页面返回数据
* @param cityName 城市名
*/
private void setPageResult(String cityName) {
Intent intent = new Intent();
intent.putExtra(Constant.CITY_RESULT, cityName);
setResult(Activity.RESULT_OK, intent);
finish();
}
}
五、项目源码
👇👇👇👇👇快捷方式👇👇👇👇👇