前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android MVVM框架搭建(四)RecyclerVIew + ViewPager2 + BaseQuickAdapter

Android MVVM框架搭建(四)RecyclerVIew + ViewPager2 + BaseQuickAdapter

作者头像
晨曦_LLW
发布2021-11-18 10:01:41
2.1K0
发布2021-11-18 10:01:41
举报
文章被收录于专栏:Android、鸿蒙开发

Android MVVM框架搭建(四)RecyclerVIew + ViewPager2 + BaseQuickAdapter

前言

  在日常的开发中,最常用于展示数据的形式就是列表,你会看到各种各样的列表,比如图片列表、视频列表,联系人列表,而在RecyclerView出来之前列表的开发是使用ListView,而现在绝大多数开发者都使用RecyclerVIew了,优势就不说了,都已经用了这么多年了,那么RecyclerView在MVVM中要怎么使用呢?另外它与JetPack的组件Paging之间,有什么联系呢?我们往下看。

正文

  在上一篇文章中,我讲述了怎么使用Room和MMKV去管理本地数据,本文将是不一样的介绍方式,因为会和页面打交道比较多,所以会比上一篇更有意思,起码我是这么觉得的。

一、图片列表数据

  首先我们要拿到数据才行,拿到数据才能去展示,最好是有图片的数据,我这里找了一个网络上的免费API接口,在我发布文章的时候这个API接口还是能用的。地址如下:

代码语言:javascript
复制
http://service.picasso.adesk.com/v1/vertical/vertical?limit=30&skip=180&adult=false&first=0&order=hot

我在写好天气的时候用过这个接口作为每日壁纸列表的使用。建议用浏览器测试一下找个接口,看有没有数据返回。那么同样的这个接口每天也只需要请求一次即可,后面对这个再做处理,前面先解决列表显示的问题。

我这里是有数据返回的,通过返回的数据构建一个数据实体,命名为WallPaperResponse,放在model包下,代码如下:

代码语言:javascript
复制
public class WallPaperResponse {
    private String msg;
    private ResBean res;
    private int code;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public ResBean getRes() {
        return res;
    }

    public void setRes(ResBean res) {
        this.res = res;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public static class ResBean {
        private List<VerticalBean> vertical;

        public List<VerticalBean> getVertical() {
            return vertical;
        }

        public void setVertical(List<VerticalBean> vertical) {
            this.vertical = vertical;
        }

        public static class VerticalBean {
            private String preview;
            private String thumb;
            private String img;
            private int views;
            private String rule;
            private int ncos;
            private int rank;
            private String source_type;
            private String wp;
            private boolean xr;
            private boolean cr;
            private int favs;
            private double atime;
            private String id;
            private String store;
            private String desc;
            private List<String> cid;
            private List<?> tag;
            private List<?> url;

            public String getPreview() {
                return preview;
            }

            public void setPreview(String preview) {
                this.preview = preview;
            }

            public String getThumb() {
                return thumb;
            }

            public void setThumb(String thumb) {
                this.thumb = thumb;
            }

            public String getImg() {
                return img;
            }

            public void setImg(String img) {
                this.img = img;
            }

            public int getViews() {
                return views;
            }

            public void setViews(int views) {
                this.views = views;
            }

            public String getRule() {
                return rule;
            }

            public void setRule(String rule) {
                this.rule = rule;
            }

            public int getNcos() {
                return ncos;
            }

            public void setNcos(int ncos) {
                this.ncos = ncos;
            }

            public int getRank() {
                return rank;
            }

            public void setRank(int rank) {
                this.rank = rank;
            }

            public String getSource_type() {
                return source_type;
            }

            public void setSource_type(String source_type) {
                this.source_type = source_type;
            }

            public String getWp() {
                return wp;
            }

            public void setWp(String wp) {
                this.wp = wp;
            }

            public boolean isXr() {
                return xr;
            }

            public void setXr(boolean xr) {
                this.xr = xr;
            }

            public boolean isCr() {
                return cr;
            }

            public void setCr(boolean cr) {
                this.cr = cr;
            }

            public int getFavs() {
                return favs;
            }

            public void setFavs(int favs) {
                this.favs = favs;
            }

            public double getAtime() {
                return atime;
            }

            public void setAtime(double atime) {
                this.atime = atime;
            }

            public String getId() {
                return id;
            }

            public void setId(String id) {
                this.id = id;
            }

            public String getStore() {
                return store;
            }

            public void setStore(String store) {
                this.store = store;
            }

            public String getDesc() {
                return desc;
            }

            public void setDesc(String desc) {
                this.desc = desc;
            }

            public List<String> getCid() {
                return cid;
            }

            public void setCid(List<String> cid) {
                this.cid = cid;
            }

            public List<?> getTag() {
                return tag;
            }

            public void setTag(List<?> tag) {
                this.tag = tag;
            }

            public List<?> getUrl() {
                return url;
            }

            public void setUrl(List<?> url) {
                this.url = url;
            }
        }
    }
}

二、新增访问地址和接口

  这个API的地址和必应明显是两个地址,那么我们就需要对第二篇文章中所写的网络框架做一些修改,首先我们修改NetworkApi中的代码。

将BASE_URL的默认值改成null,并去掉final关键字,然后我们在NetworkApi中增加一个方法,代码如下:

代码语言:javascript
复制
	/**
     * 设置访问Url类型
     * @param type 0 必应 1 壁纸列表
     */
    private static void setUrlType(int type){
        switch (type) {
            case 0:
                //必应
                BASE_URL = "https://cn.bing.com";
                break;
            case 1:
                //热门壁纸
                BASE_URL = "http://service.picasso.adesk.com";
                break;
            default:break;
        }
    }

这里根据输入的类型使用不同的网络服务器地址,然后再修改createService方法,修改后如下:

代码语言:javascript
复制
	public static <T> T createService(Class<T> serviceClass, int type) {
        //设置Url类型
        setUrlType(type);
        return getRetrofit(serviceClass).create(serviceClass);
    }

那么我们就同样需要修改代码中调用了createService方法的地方,在MainRepository中

这样就可以了,这样做的好处就在于我们既增加了访问API的可拓展性,同时易于修改,还不会对你之前的网络请求很影响。

对于之前的内容改动目前就这些了,下面需要增加新的接口了。在ApiService中增加如下接口。

代码语言:javascript
复制
	/**
     * 热门壁纸
     */
    @GET("/v1/vertical/vertical?limit=30&skip=180&adult=false&first=0&order=hot")
    Observable<WallPaperResponse> wallPaper();

三、访问接口

  接口有了,下面就是访问的事情了,现在主页面有点太空旷了,所以找个接口的数据访问依然可以在MainRepository中进行请求。打开MainRepository,在里面增加如下代码:

代码语言:javascript
复制
	/**
     * 热门壁纸数据
     */
    final MutableLiveData<WallPaperResponse> wallPaper = new MutableLiveData<>();
	
	/**
     * 获取壁纸数据
     * @return wallPaper
     */
    public LiveData<WallPaperResponse> getWallPaper() {
        NetworkApi.createService(ApiService.class,1).
                wallPaper().compose(NetworkApi.applySchedulers(new BaseObserver<WallPaperResponse>() {
            @Override
            public void onSuccess(WallPaperResponse wallPaperResponse) {
                KLog.e("WallPaper: " + new Gson().toJson(wallPaperResponse));
                wallPaper.setValue(wallPaperResponse);
            }

            @Override
            public void onFailure(Throwable e) {
                KLog.e("WallPaper Error: " + e.toString());
            }
        }));
        return wallPaper;
    }

然后进入到MainViewModel中,在里面增加如下代码:

代码语言:javascript
复制
	public LiveData<WallPaperResponse> wallPaper;

	public void getWallPaper() { wallPaper = new MainRepository().getWallPaper(); }

现在访问接口数据这一块就搞定了,下面就是显示出来就可以了。

四、RecyclerView显示数据

  因为返回的数据比较多,因此通过RecyclerView来进行显示,作为壁纸显示可以通过更改布局管理器,把列表变成纵向两列的形式去显示,首先我们先修改activity_main.xml的布局代码,如下图所示

这里我去掉了页面的居中布局,然后增加了一个RecyclerView,添加了一个id,同事改了一下CustomImageView的scaleType=“fitXY”,这样可以让我们的壁纸完整呈现出来。

这里我需要修改一下CustomImageView类的代码:

其实就是改它所继承的父类,为什么要这么改呢?现在就来说明一下。下面我们写一个列表适配器的item布局,在layout下新建一个item_wall_paper.xml文件,里面的代码我们先不写,先去写一个样式,在themes.xml文件中(老版本的AS中是styles.xml),增加如下样式代码:

代码语言:javascript
复制
    <style name="roundedImageStyle">
        "cornerFamily">rounded
        "cornerSize">24dp
    style>

这里是设置一个圆角图片的样式代码,那么怎么去使用它呢,下面我们修改一下item_wall_paper.xml,代码如下:

代码语言:javascript
复制
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    
    <data>
        <variable
            name="wallPaper"
            type="com.llw.mvvm.model.WallPaperResponse.ResBean.VerticalBean" />
    data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.llw.mvvm.view.CustomImageView
            networkUrl="@{wallPaper.img}"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:layout_margin="5dp"
            android:scaleType="centerCrop"
            app:shapeAppearanceOverlay="@style/roundedImageStyle" />
    LinearLayout>
layout>

这里依然是使用DataBinding,因为我们数据是要显示在列表上的,因此直接绑定item就可以了,然后这里我用的是networkUrl的属性,因为你如果使用了biyingUrl会添加一个前缀,而这个API不需要前缀,同时我把刚才写的样式设置了进来,这里就解释了为什么要更改继承的父类,因为之前的那个父类没有这个属性值,这个属性值可以让你的Image图片做很多的形状上的变化,相当Nice! 这样就不用再去导入其他的依赖库或者使用自定义View了,这得力于Material,关于ShapeableImageView更多的介绍可以看一下这一篇文章:Android Material UI控件之ShapeableImageView,如果你感兴趣的话。

好了回到正题,那就是我们现在布局都已经写好了,下面写一个适配器,在com.llw.mvvm包下新建一个adapter包,adapter包下新建一个WallPaperAdapter类,里面的代码如下:

代码语言:javascript
复制
public class WallPaperAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    /**
     * 传递过来的数据
     */
    private final List<WallPaperResponse.ResBean.VerticalBean> verticalBeans;


    public WallPaperAdapter(List<WallPaperResponse.ResBean.VerticalBean> verticalBeans) {
        this.verticalBeans = verticalBeans;
    }

    @NonNull
    @NotNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) {
        ItemWallPaperBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_wall_paper, parent, false);
        return new ViewHolderWallPaper(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull @NotNull RecyclerView.ViewHolder holder, int position) {
        ItemWallPaperBinding binding = ((ViewHolderWallPaper) holder).getBinding();
        binding.setWallPaper(verticalBeans.get(position));
        binding.executePendingBindings();
    }

    @Override
    public int getItemCount() {
        return verticalBeans.size();
    }

    private static class ViewHolderWallPaper extends RecyclerView.ViewHolder {

        private ItemWallPaperBinding binding;

        public ItemWallPaperBinding getBinding() {
            return binding;
        }

        public void setBinding(ItemWallPaperBinding binding) {
            this.binding = binding;
        }

        public ViewHolderWallPaper(ItemWallPaperBinding inflate) {
            super(inflate.getRoot());
            this.binding = inflate;
        }
    }
}

这就是RecyclerView.Adapter的常规使用而已,很简单,其中要注意的就是DataBinding的使用,这个很关键了,它决定了你的数据与xml绑定。下面我们回到MainActivity中,首先增加一个initView()方法,里面的代码如下:

代码语言:javascript
复制
	/**
     * 初始化
     */
    private void initView() {
        GridLayoutManager manager = new GridLayoutManager(this, 2);
        dataBinding.rv.setLayoutManager(manager);
    }

然后在onCreate方法中调用它并且实现数据更新的回调监听。

代码语言:javascript
复制
		initView();
        //热门壁纸 网络请求
        mainViewModel.getWallPaper();
        mainViewModel.wallPaper.observe(this, wallPaperResponse -> dataBinding.rv.setAdapter(new WallPaperAdapter(wallPaperResponse.getRes().getVertical())));

下面我们可以开始运行了,效果图如下:

由于这个平台的限制,所以这个动图是标清,建议读者可以直接安装APK去体验,这样会更舒服一些。这个图片展示的效果就很不错,现在我们已经掌握了怎么在MVVM中使用RecyclerView。

五、绑定点击事件

  当我们需要点击查看图片的时候,就需要先绑定点击事件,然后查看图片,在适配器WallPaperAdapter中增加一个ClickBinding内部类,里面的代码如下:

代码语言:javascript
复制
	public static class ClickBinding {
        public void itemClick(WallPaperResponse.ResBean.VerticalBean verticalBean, View view) {
            Intent intent = new Intent(view.getContext(), PictureViewActivity.class);
            intent.putExtra("img", verticalBean.getImg());
            view.getContext().startActivity(intent);
        }
    }

这里点击之后是跳转到PictureViewActivity,等会去创建它。

然后修改一下布局item_wall_paper.xml,

代码语言:javascript
复制
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    
    <data>

        <variable
            name="wallPaper"
            type="com.llw.mvvm.model.WallPaperResponse.ResBean.VerticalBean" />

        <variable
            name="onClick"
            type="com.llw.mvvm.adapter.WallPaperAdapter.ClickBinding" />
    data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.llw.mvvm.view.CustomImageView
            android:id="@+id/image"
            networkUrl="@{wallPaper.img}"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:layout_margin="5dp"
            android:onClick="@{() -> onClick.itemClick(wallPaper,image)}"
            android:scaleType="centerCrop"
            app:shapeAppearanceOverlay="@style/roundedImageStyle" />
    LinearLayout>
layout>

然后新增一个PictureViewActivity,对应的布局activity_picture_view.xml代码如下:

代码语言:javascript
复制
<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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PictureViewActivity">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

androidx.constraintlayout.widget.ConstraintLayout>

然后修改一下PictureViewActivity的代码,在onCreate方法中增加如下代码。

代码语言:javascript
复制
		String img = getIntent().getStringExtra("img");
        if (img != null) {
            ImageView imageView = findViewById(R.id.image);
            Glide.with(this).load(img).into(imageView);
        }

运行效果如下所示:

好的,下面对主页面进行一下美化,现在这个样子确实不好看。

六、协调布局使用

  在页面中默认的ActionBar占了无用的控件,我们可以自定义一个样式去替换当前页面的样式,在themes.xml下增加如下代码:

代码语言:javascript
复制
    <style name="MainTheme" parent="Theme.AppCompat.Light.NoActionBar">
        
        "colorPrimary">@color/purple_500
        "colorPrimaryDark">@color/purple_500
        "colorAccent">@color/white
        "android:statusBarColor">@android:color/transparent
    style>

然后修改AndroidManifest.xml中的代码:

设置theme,然后修改activity_main.xml布局代码:

代码语言:javascript
复制
<layout 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">

    
    <data>

        <variable
            name="viewModel"
            type="com.llw.mvvm.viewmodels.MainViewModel" />
    data>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:context=".MainActivity">

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar_layout"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
            
            <com.google.android.material.appbar.CollapsingToolbarLayout
                android:id="@+id/toolbar_layout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                app:collapsedTitleGravity="center_horizontal"
                app:contentScrim="@color/purple_500"
                app:layout_scrollFlags="scroll|exitUntilCollapsed"
                app:title="MVVM"
                app:toolbarId="@+id/toolbar">
                
                <com.llw.mvvm.view.CustomImageView
                    android:id="@+id/image"
                    biyingUrl="@{viewModel.biying.images.get(0).url}"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:fitsSystemWindows="true"
                    android:scaleType="fitXY" />

                <androidx.appcompat.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:contentInsetEnd="0dp"
                    app:contentInsetStart="0dp"
                    app:layout_collapseMode="pin"
                    app:layout_scrollFlags="scroll|snap" />
            com.google.android.material.appbar.CollapsingToolbarLayout>
        com.google.android.material.appbar.AppBarLayout>
        
        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true"
            android:orientation="vertical"
            android:overScrollMode="never"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rv"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="5dp" />
        androidx.core.widget.NestedScrollView>
    androidx.coordinatorlayout.widget.CoordinatorLayout>
layout>

然后修改MainActivity中的代码,在initView中增加如下代码:

代码语言:javascript
复制
		//伸缩偏移量监听
        dataBinding.appbarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            boolean isShow = true;
            int scrollRange = -1;

            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                if (scrollRange == -1) {
                    scrollRange = appBarLayout.getTotalScrollRange();
                }
                if (scrollRange + verticalOffset == 0) {//收缩时
                    dataBinding.toolbarLayout.setTitle("MVVM-Demo");
                    isShow = true;
                } else if (isShow) {//展开时
                    dataBinding.toolbarLayout.setTitle("");
                    isShow = false;
                }
            }
        });

运行效果如下:

这样的效果如何呢?不管怎么说比之前的页面好看吧,而且这个用户体验会比较的好。

七、保存本地数据库

  这里我们依然可以保存热门壁纸的数据,这样可以再第二次打开当前页面的时候使我们的加载效率提高很多,这里我们需要对数据库进行一次升级迁移。

1. Entity

在bean包下新增一个WallPaper类,里面的代码如下:

代码语言:javascript
复制
@Entity(tableName = "wallpaper")
public class WallPaper {

    @PrimaryKey(autoGenerate = true)
    private int uid = 0;

    private String img;

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getImg() {
        return img;
    }

    public void setImg(String img) {
        this.img = img;
    }

    public WallPaper() {}

    @Ignore
    public WallPaper(String img) {
        this.img = img;
    }
}

这里我设置了id字段自增,同时我设置了表名为:wallpaper。

2. Dao

在dao包下新建一个WallPaperDao接口,里面的代码如下:

代码语言:javascript
复制
@Dao
public interface WallPaperDao {

    @Query("SELECT * FROM wallpaper")
    Flowable<List<WallPaper>> getAll();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    Completable insertAll(List<WallPaper> wallPapers);

    @Query("DELETE FROM wallpaper")
    Completable deleteAll();
}

这里依然通过RxJava2去操作数据的处理,这里我有删除、增加、查询所有数据的方法。

3. 版本升级迁移

  在AppDatabase中增加如下代码,用于数据库版本的升级迁移,这里我是新增了一个迁移对象,构建迁移对象的版本,从1到2,然后执行一段SQL语句,该语句用于创建一个新的表。

代码语言:javascript
复制
	/**
     * 版本升级迁移
     */
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            // Create the new table
            database.execSQL("CREATE TABLE `wallpaper` " +
                    "(uid INTEGER NOT NULL, " +
                    "img TEXT, " +
                    "PRIMARY KEY(`uid`))");
        }
    };

然后在修改AppDatabase中的其他地方,如下图所示:

这里我修改了@Database注解中的内容,增加了新的表和版本升级到2,同时在构建数据库的时候增加一个迁移,最后增加wallPaperDao抽象方法,方便使用的地方直接去调用。

4. 热门壁纸数据处理

  下面进入MainRepository中去对热门壁纸的数据进行处理,之前是只有从数据库中获取数据,现在可以通过本地数据库获取。 首先在Constant中增加两个常量,用于控制热门壁纸的请求和使用方式。

代码语言:javascript
复制
	//今日是否请求了热门壁纸
    public static final String IS_TODAY_REQUEST_WALLPAPER = "isTodayRequestWallPaper";

    //今日请求热门壁纸的时间戳
    public static final String REQUEST_TIMESTAMP_WALLPAPER = "wallpaperRequestTimestamp";

然后在MainRepository中增加如下代码:

代码语言:javascript
复制
	/**
     * 热门壁纸数据
     */
    final MutableLiveData<WallPaperResponse> wallPaper = new MutableLiveData<>();

下面在MainRepository中增加一个保存网络热门壁纸数据到本地的方法,代码如下:

代码语言:javascript
复制
	/**
     * 保存热门壁纸数据
     */
    private void saveWallPaper(WallPaperResponse wallPaperResponse) {
        MVUtils.put(Constant.IS_TODAY_REQUEST_WALLPAPER, true);
        MVUtils.put(Constant.REQUEST_TIMESTAMP_WALLPAPER, DateUtil.getMillisNextEarlyMorning());

        Completable deleteAll = BaseApplication.getDb().wallPaperDao().deleteAll();
        CustomDisposable.addDisposable(deleteAll, () -> {
            Log.d(TAG, "saveWallPaper: 删除数据成功");
            List<WallPaper> wallPaperList = new ArrayList<>();
            for (WallPaperResponse.ResBean.VerticalBean verticalBean : wallPaperResponse.getRes().getVertical()) {
                wallPaperList.add(new WallPaper(verticalBean.getImg()));
            }
            //保存到数据库
            Completable insertAll = BaseApplication.getDb().wallPaperDao().insertAll(wallPaperList);
            Log.d(TAG, "saveWallPaper: 插入数据:" + wallPaperList.size() + "条");
            //RxJava处理Room数据存储
            CustomDisposable.addDisposable(insertAll, () -> Log.d(TAG, "saveWallPaper: 热门天气数据保存成功"));
        });
    }

这里的方法是在保存的时候对数据先进行了一个删除操作,因为我不希望保留之前的老数据,所以先删除所有数据,再添加新数据。

下面在MainRepository中增加一个从本地数据库中读取数据的方法,代码如下:

代码语言:javascript
复制
	/**
     * 从本地数据库获取热门壁纸
     */
    private void getLocalDBForWallPaper() {
        Log.d(TAG, "getLocalDBForWallPaper: 从本地数据库获取 热门壁纸");
        WallPaperResponse wallPaperResponse = new WallPaperResponse();
        WallPaperResponse.ResBean resBean = new WallPaperResponse.ResBean();
        List<WallPaperResponse.ResBean.VerticalBean> verticalBeanList = new ArrayList<>();
        Flowable<List<WallPaper>> listFlowable = BaseApplication.getDb().wallPaperDao().getAll();
        CustomDisposable.addDisposable(listFlowable, wallPapers -> {
            for (WallPaper paper : wallPapers) {
                WallPaperResponse.ResBean.VerticalBean verticalBean = new WallPaperResponse.ResBean.VerticalBean();
                verticalBean.setImg(paper.getImg());
                verticalBeanList.add(verticalBean);
            }
            resBean.setVertical(verticalBeanList);
            wallPaperResponse.setRes(resBean);
            wallPaper.postValue(wallPaperResponse);
        });
    }

这里就是从本地数据库中查询wallpaper表中的所有数据,然后赋值给实体,再通过postValue去发送数据,页面收到通知之后就会更新适配器。

最后就是修改getWallPaper方法,里面的代码如下:

代码语言:javascript
复制
	/**
     * 获取壁纸数据
     * @return wallPaper
     */
    public MutableLiveData<WallPaperResponse> getWallPaper() {
        //今日此接口是否已经请求
        if (MVUtils.getBoolean(Constant.IS_TODAY_REQUEST_WALLPAPER)) {
            if (DateUtil.getTimestamp() <= MVUtils.getLong(Constant.REQUEST_TIMESTAMP_WALLPAPER)) {
                getLocalDBForWallPaper();
            } else {
                requestNetworkApiForWallPaper();
            }
        } else {
            requestNetworkApiForWallPaper();
        }
        return wallPaper;
    }

这一段代码的逻辑和获取必应壁纸一样,因此没啥好说的。现在我们的页面是不用动的,那么你可以从手机上卸载应用再安装,然后看看今天第一次打开和第二次打开有什么区别,效果图如下:

然后我们再看看日志打印。

没啥问题,下面进入图片点击之后进入新的页面中去显示壁纸。

八、ViewPager2显示数据

  当点击某一个图片的时候,将值传到详情页面去,然后在这个页面可以左右滑动去查看图片,这无疑是比看一个点一个要好一些,可以利用ViewPager2来解决。

1. 布局使用ViewPager2

那么我们先来修改一下activity_picture_view.xml,里面的代码如下:

代码语言:javascript
复制
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/vp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    androidx.constraintlayout.widget.ConstraintLayout>
layout>

这里直接使用ViewPager2,相比于ViewPager来说,这个会更强大。因为ViewPager2可以在setAdapter时直接设置RecyclerView.Adapter,很方便,因此这里同样需要一个适配器,这里的适配器我不打算用原生的来写。

2. BaseQuickAdapter使用

  这是一个第三方开源框架,非常的强大,GitHub地址如下:BaseRecyclerViewAdapterHelper

最新的版本中是支持androidx和DataBinding的,因此很方便我们使用它。下面添加依赖库,在app的build.gradle的dependencies{}闭包中增加如下代码:

代码语言:javascript
复制
	//RecyclerView的好搭档
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'

然后点击Sync Now同步项目添加依赖库。

在使用BaseQuickAdapter时,先创建适配器的布局,在layout下新建一个item_image.xml,里面的代码如下:

代码语言:javascript
复制
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
layout>

这里也是通过DataBinding来处理数据的赋值,下面在adapter包下新建一个ImageAdapter类,里面的代码如下:

代码语言:javascript
复制
public class ImageAdapter extends BaseQuickAdapter<WallPaper, BaseDataBindingHolder<ItemImageBinding>> {
    
    public ImageAdapter(@Nullable List<WallPaper> data) {
        super(R.layout.item_image, data);
    }

    @Override
    protected void convert(@NotNull BaseDataBindingHolder<ItemImageBinding> bindingHolder, WallPaper wallPaper) {
        if (wallPaper == null) {
            return;
        }
        ItemImageBinding binding = bindingHolder.getDataBinding();
        if (binding != null) {
            binding.setWallPaper(wallPaper);
            binding.executePendingBindings();
        }
    }
}

下面说明一下:通过继承BaseQuickAdapter,通过使用需要适配器中的实体Bean,然后是ViewHolder,这里使用的是BaseDataBindingHolder,最终是继承RecyclerView.ViewHolder,同时传递了ItemImageBinding,这是布局在编译时生成的。对应了item_image.xml文件。然后在convert方法中,通过bindingHolder.getDataBinding()获取binding,然后设置数据,执行executePendingBindings。这里的环节就和普通的RecyclerView.Adapter差不多。

3. PictureRepository

  当我们一个页面有数据时,应该就需要创建一个对应页面的Repository,这是MVVM的使用习惯,在这里对数据进行处理,在repository包下新建一个PictureRepository类,里面的代码如下:

代码语言:javascript
复制
public class PictureRepository {

    private final MutableLiveData<List<WallPaper>> wallPaper = new MutableLiveData<>();

    public MutableLiveData<List<WallPaper>> getWallPaper() {
        Flowable<List<WallPaper>> listFlowable = BaseApplication.getDb().wallPaperDao().getAll();
        CustomDisposable.addDisposable(listFlowable, wallPaper::postValue);
        return wallPaper;
    }
}

这里通过查询所有数据将返回的值传递过去。

4. PictureViewModel

  同样一个Activity也对应了一个ViewModel,那么我们的PictureViewActivity就对应了PictureViewModel,在viewmodels包下新建一个PictureViewModel类,里面的代码如下:

代码语言:javascript
复制
public class PictureViewModel extends ViewModel {

    public LiveData<List<WallPaper>> wallPaper;

    public void getWallPaper() {
        wallPaper = new PictureRepository().getWallPaper();
    }
}

5. 显示数据

  前面的准备工作都已经做完了,下面进入到PictureViewActivity中去处理View的显示,创建变量,代码如下:

代码语言:javascript
复制
	private PictureViewModel viewModel;
    private ActivityPictureViewBinding binding;

然后修改onCreate中的方法代码。

代码语言:javascript
复制
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_picture_view);
        viewModel = new ViewModelProvider(this).get(PictureViewModel.class);
        String img = getIntent().getStringExtra("img");
        //获取热门壁纸数据
        viewModel.getWallPaper();
        viewModel.wallPaper.observe(this, wallPapers -> {
            binding.vp.setAdapter(new ImageAdapter(wallPapers));
        });
    }

这里绑定xml,然后绑定viewModel,获取页面传递过来的数据,然后请求壁纸数据,再监听页面的回调,然后在回调中设置适配器的数据。 这里还有一个问题,就是在上一个页面点击的位置,没有与当前页面的位置一致,所需还需要设置一下。 增阿如下代码:

代码语言:javascript
复制
			for (int i = 0; i < wallPapers.size(); i++) {
                if (img == null) {
                    return;
                }
                if (wallPapers.get(i).getImg().equals(img)) {
                    binding.vp.setCurrentItem(i,false);
                    break;
                }
            }

增加的位置如下:

这里就是通过传递过来的url地址和查询到的url进行比对,得到具体的位置,然后显示这个vp的当前位置item,这里有一个false,为什么是false,因为不需要显示动画效果,如果不设置为false,当所选的位置不是第0个时,会有一个动画效果,现在去掉这个效果。下面运行一下,运行效果如下图所示:

这个页面还有状态栏和导航栏,影响美化,下面去掉它。下面在themes.xml中添加如下代码:

代码语言:javascript
复制
    <style name="ImageTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        "android:background">@android:color/transparent
        "android:statusBarColor" tools:targetApi="lollipop">@android:color/transparent
        
        "android:windowTranslucentStatus">false
        "android:windowTranslucentNavigation">true
    style>

打开AndroidManifest.xml,添加主题样式,如下图:

运行一下:

这个效果是不是就比较好呢,好了,本文就到这里了。

九、源码

GitHub:MVVM-Demo 欢迎Star和Fork

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/11/17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Android MVVM框架搭建(四)RecyclerVIew + ViewPager2 + BaseQuickAdapter
  • 前言
  • 正文
    • 一、图片列表数据
      • 二、新增访问地址和接口
        • 三、访问接口
          • 四、RecyclerView显示数据
            • 五、绑定点击事件
              • 六、协调布局使用
                • 七、保存本地数据库
                  • 1. Entity
                  • 2. Dao
                  • 3. 版本升级迁移
                  • 4. 热门壁纸数据处理
                • 八、ViewPager2显示数据
                  • 1. 布局使用ViewPager2
                  • 2. BaseQuickAdapter使用
                  • 3. PictureRepository
                  • 4. PictureViewModel
                  • 5. 显示数据
                • 九、源码
                相关产品与服务
                数据库
                云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档