Android 天气APP城市切换 之 自定义弹窗与使用
Posted 初学者-Study
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 天气APP城市切换 之 自定义弹窗与使用相关的知识,希望对你有一定的参考价值。
上一篇:Android 天气APP(七)城市切换 之 城市数据源
添加数据库、城市切换
新版-------------------
在上一篇文章中,完成了风力风向的显示,文章最后我添加了一个菜单,菜单里面只有一个选项,切换城市,本篇文章就是完成切换城市之后查询城市天气的功能,说起来是不是很简单,那么我们看一下实现这个功能要怎么做。
一、添加依赖和城市数据
想一下,我们的城市数据怎么来,怎么保存和获取,在我之前的版本中,有一个txt文件,读取这个文件,然后显示在UI上,乍一看似乎可以使用,但是会有问题,性能损耗太大,每一次都需要重新进行文件读取,不优雅,这一块我们就用数据库来处理,只做一次文件读取,然后写入到数据库中,用的时候从数据库中读取即可,怎么保证只做一次文件读取呢,可以利用缓存值,在程序第一次运行时进行读取,后面就不再读取,现在想一下是不是比之前要复杂一些了,下面我们先添加依赖,在app的build.gralde的dependencies
闭包下添加如下所示代码:
//Room数据库
implementation 'androidx.room:room-runtime:2.4.2'
annotationProcessor 'androidx.room:room-compiler:2.4.2'
//Room 支持RxJava2
implementation 'androidx.room:room-rxjava2:2.4.2'
//腾讯MMKV
implementation 'com.tencent:mmkv:1.2.11'
//Gson
implementation 'com.google.code.gson:gson:2.9.0'
添加位置如下图所示:
然后Sync Now,下面准备城市数据源,在app模块的main文件夹下创建一个assets文件夹,里面放入一个city.txt,这里我就不贴里面的内容了,你可以去我的源码里面直接获取,如下图所示:
二、添加启动页
一般程序会在启动页做一些事情,比如初始数据获取等一些操作,我们现在只有一个MainActivity,这明显是不够的,下面我们在com.llw.goodweather
包下新建ui
,ui包下新建一个SplashActivity
,创建Activity的方式你应该会吧,为了方便管理,我们将MainActivity也移动到ui包下,移动之后检查一下MainActivity里面有没有报错,有的话就是ViewBinding的问题,你把错误的那一条语句删掉,重新导包就行了,然后就需要将SplashActivity作为启动页面了,修改AndroidManifest.xml中的代码,如下图所示:
要注意细节,在Android 12中有一个android:exported
属性,启动Activity必须为true,其他的就默认为false就行了,下面简单修改一下activity_splash.xml中的内容,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/splash_bg"
android:fitsSystemWindows="true"
tools:context=".ui.SplashActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@drawable/splash_bg" />
</androidx.constraintlayout.widget.ConstraintLayout>
这里用到的背景图你可以去源码里面去找,那么你现在运行一下,应该就是显示SplashActivity了。
三、城市数据操作
下面我们进行城市数据的操作,也就是从txt中读取、通过数据库保存、查询,这里我们用到的是Room数据库,首先我们在bean包下新建一个Province类,里面的代码如下:
@Entity
public class Province
@PrimaryKey(autoGenerate = true)
private int uid;
private String provinceName;
private List<City> cityList;
public int getUid()
return uid;
public void setUid(int uid)
this.uid = uid;
public String getProvinceName()
return provinceName;
public void setProvinceName(String provinceName)
this.provinceName = provinceName;
public List<City> getCityList()
return cityList;
public void setCityList(List<City> cityList)
this.cityList = cityList;
public Province()
@Ignore
public Province(String provinceName, List<City> cityList)
this.provinceName = provinceName;
this.cityList = cityList;
public static class City
private String cityName;
private List<Area> areaList;
public String getCityName()
return cityName;
public void setCityName(String cityName)
this.cityName = cityName;
public List<Area> getAreaList()
return areaList;
public void setAreaList(List<Area> areaList)
this.areaList = areaList;
public City()
public static class Area
private String areaName;
public String getAreaName()
return areaName;
public void setAreaName(String areaName)
this.areaName = areaName;
public Area(String countyName)
this.areaName = countyName;
这里面用到的注解你记得导包,都是room下的,然后我们创建一个转换器,在bean包下新建一个CityConverter类,代码如下:
public class CityConverter
@TypeConverter
public List<Province.City> stringToObject(String value)
Type userListType = new TypeToken<ArrayList<Province.City>>() .getType();
return new Gson().fromJson(value, userListType);
@TypeConverter
public String objectToString(List<Province.City> list)
return new Gson().toJson(list);
这里的注解导包同样是room下的,为什么要有这个转换器呢?因为我们是在一张表里面又插入了一张表,不用这个转换器就会报错。
然后我们再回到Province,在@Entity
注解上面再添加一行注解,代码如下:
@TypeConverters(CityConverter.class)
添加位置如下图所示:
下面我们在com.llw.goodweather
包下新建一个db
包,然后在db包下创建一个AppDatabase
类,稍微我们会用到它,下面将bean包移到db包下,然后在db包下新建一个dao包,dao包中就是对于数据库操作的接口方法包,在dao包下新建一个ProvinceDao
接口,代码如下所示:
@Dao
public interface ProvinceDao
/**
* 查询所有
*/
@Query("SELECT * FROM Province")
Flowable<List<Province>> getAll();
/**
* 插入所有
* @param provinces 所有行政区数据
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
Completable insertAll(Province... provinces);
这里的Flowable和Completable 都是RxJava中的内容,背压,你可以去了解一下,下面回到AppDatabase类,修改代码如下所示:
@Database(entities = Province.class,version = 1,exportSchema = false)
public abstract class AppDatabase extends RoomDatabase
private static final String DATABASE_NAME = "GoodWeatherNew";
private static volatile AppDatabase mInstance;
public abstract ProvinceDao provinceDao();
/**
* 单例模式
*/
public static AppDatabase getInstance(Context context)
if (mInstance == null)
synchronized (AppDatabase.class)
if (mInstance == null)
mInstance = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, DATABASE_NAME).build();
return mInstance;
注意看这是一个抽象类,我们通过注解会生成一个编译时类,然后将之前创建的Province当成一个表放进数据库,数据库版本为1,里面有一个抽象接口方法,还有一个单例,单例中做数据库的构建,下面关于数据库的操作就基本上完成了,看一下db包下的内容,如下图所示:
那么我们在哪里调用呢?在Repository中,下面在repository包下新建一个CustomDisposable类,这里面是对数据的背压操作所创建的工具类,里面的代码如下所示:
public class CustomDisposable
private static final CompositeDisposable compositeDisposable = new CompositeDisposable();
/**
* Flowable
* @param flowable
* @param consumer
* @param <T>
*/
public static <T> void addDisposable(Flowable<T> flowable, Consumer<T> consumer)
compositeDisposable.add(flowable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(consumer));
/**
* Completable
* @param completable
* @param action
* @param <T>
*/
public static <T> void addDisposable(Completable completable, Action action)
compositeDisposable.add(completable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(action));
然后在repository包下新建一个CityRepository类,代码如下所示:
public class CityRepository
private static final String TAG = CityRepository.class.getSimpleName();
private static final class CityRepositoryHolder
private static final CityRepository mInstance = new CityRepository();
public static CityRepository getInstance()
return CityRepository.CityRepositoryHolder.mInstance;
/**
* 添加城市数据
*/
public void addCityData(List<Province> cityList)
Province[] provinceArray = cityList.toArray(new Province[0]);
Completable insertAll = WeatherApp.getDb().provinceDao().insertAll(provinceArray);
CustomDisposable.addDisposable(insertAll, () -> Log.d(TAG, "addCityData: 插入数据成功。"));
/**
* 获取城市数据
*/
public void getCityData(MutableLiveData<List<Province>> listMutableLiveData)
Flowable<List<Province>> listFlowable = WeatherApp.getDb().provinceDao().getAll();
CustomDisposable.addDisposable(listFlowable, listMutableLiveData::postValue);
然后就是调用这些方法的地方,根据MVVM的框架模式,我们可以在viewmodel包下新建一个SplashViewModel类,代码如下:
public class SplashViewModel extends BaseViewModel
public MutableLiveData<List<Province>> listMutableLiveData = new MutableLiveData<>();
/**
* 添加城市数据
*/
public void addCityData(List<Province> provinceList)
CityRepository.getInstance().addCityData(provinceList);
/**
* 获取所有城市数据
*/
public void getAllCityData()
CityRepository.getInstance().getCityData(listMutableLiveData);
下面在Constant中新增两个常量,代码如下:
/**
* 程序第一次运行
*/
public static final String FIRST_RUN = "firstRun";
/**
* 今天第一次启动时间
*/
public static final String FIRST_STARTUP_TIME_TODAY = "firstStartupTimeToday";
下面我们添加MMKV的使用,在utils包下新建一个MVUtils 类,代码如下所示:
public class MVUtils
private static MVUtils mInstance;
private static MMKV mmkv;
public MVUtils()
mmkv = MMKV.defaultMMKV();
public static MVUtils getInstance()
if (mInstance == null)
synchronized (MVUtils.class)
if (mInstance == null)
mInstance = new MVUtils();
return mInstance;
/**
* 写入基本数据类型缓存
*
* @param key 键
* @param object 值
*/
public static void put(String key, Object object)
if (object instanceof String)
mmkv.encode(key, (String) object);
else if (object instanceof Integer)
mmkv.encode(key, (Integer) object);
else if (object instanceof Boolean)
mmkv.encode(key, (Boolean) object);
else if (object instanceof Float)
mmkv.encode(key, (Float) object);
else if (object instanceof Long)
mmkv.encode(key, (Long) object);
else if (object instanceof Double)
mmkv.encode(key, (Double) object);
else if (object instanceof byte[])
mmkv.encode(key, (byte[]) object);
else
mmkv.encode(key, object.toString());
public static void putSet(String key, Set<String> sets)
mmkv.encode(key, sets);
public static void putParcelable(String key, Parcelable obj)
mmkv.encode(key, obj);
public static Integer getInt(String key)
return mmkv.decodeInt(key, 0);
public static Integer getInt(String key, int defaultValue)
return mmkv.decodeInt(key, defaultValue);
public static Double getDouble(String key)
return mmkv.decodeDouble(key, 0.00);
public static Double getDouble(String key, double defaultValue)
return mmkv.decodeDouble(key, defaultValue);
public static Long getLong(String key)
return mmkv.decodeLong(key, 0L);
public static Long getLong(String key, long defaultValue)
return mmkv.decodeLong(key, defaultValue);
public static Boolean getBoolean(String key) 以上是关于Android 天气APP城市切换 之 自定义弹窗与使用的主要内容,如果未能解决你的问题,请参考以下文章
Android MVVM框架搭建TabLayoutViewPager城市地图天气切换