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城市地图天气切换

android性能测试

天气 App 中常见的 Android 自定义控件,可以用来动态展示日出和日落

4-PropertyGrid绑定自定义弹窗

天气预报APP

Android FlycoDialog 简单实用的自定义Android弹窗对话框之Dialog篇