然后进入到MainViewModel中,在里面增加如下代码:
public LiveData 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),增加如下样式代码:
这里是设置一个圆角图片的样式代码,那么怎么去使用它呢,下面我们修改一下item_wall_paper.xml,代码如下:
xmlns:app=“http://schemas.android.com/apk/res-auto”> name=“wallPaper” type=“com.llw.mvvm.model.WallPaperResponse.ResBean.VerticalBean” /> android:layout_width=“match_parent” android:layout_height=“wrap_content” android:orientation=“vertical”> networkUrl=“@{wallPaper.img}” android:layout_width=“match_parent” android:layout_height=“300dp” android:layout_margin=“5dp” android:scaleType=“centerCrop” app:shapeAppearanceOverlay=“@style/roundedImageStyle” /> 这里依然是使用DataBinding,因为我们数据是要显示在列表上的,因此直接绑定item就可以了,然后这里我用的是networkUrl的属性,因为你如果使用了biyingUrl会添加一个前缀,而这个API不需要前缀,同时我把刚才写的样式设置了进来,这里就解释了为什么要更改继承的父类,因为之前的那个父类没有这个属性值,这个属性值可以让你的Image图片做很多的形状上的变化,相当Nice! 这样就不用再去导入其他的依赖库或者使用自定义View了,这得力于Material,关于ShapeableImageView更多的介绍可以看一下这一篇文章:Android Material UI控件之ShapeableImageView,如果你感兴趣的话。 好了回到正题,那就是我们现在布局都已经写好了,下面写一个适配器,在com.llw.mvvm包下新建一个adapter包,adapter包下新建一个WallPaperAdapter类,里面的代码如下: public class WallPaperAdapter extends RecyclerView.Adapter /** 传递过来的数据 */ private final List public WallPaperAdapter(List 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()方法,里面的代码如下: /** 初始化 */ private void initView() { GridLayoutManager manager = new GridLayoutManager(this, 2); dataBinding.rv.setLayoutManager(manager); } 然后在onCreate方法中调用它并且实现数据更新的回调监听。 initView(); //热门壁纸 网络请求 mainViewModel.getWallPaper(); mainViewModel.wallPaper.observe(this, wallPaperResponse -> dataBinding.rv.setAdapter(new WallPaperAdapter(wallPaperResponse.getRes().getVertical()))); 下面我们可以开始运行了,效果图如下: 由于这个平台的限制,所以这个动图是标清,建议读者可以直接安装APK去体验,这样会更舒服一些。这个图片展示的效果就很不错,现在我们已经掌握了怎么在MVVM中使用RecyclerView。 五、绑定点击事件 当我们需要点击查看图片的时候,就需要先绑定点击事件,然后查看图片,在适配器WallPaperAdapter中增加一个ClickBinding内部类,里面的代码如下: 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, xmlns:app=“http://schemas.android.com/apk/res-auto”> name=“wallPaper” type=“com.llw.mvvm.model.WallPaperResponse.ResBean.VerticalBean” /> name=“onClick” type=“com.llw.mvvm.adapter.WallPaperAdapter.ClickBinding” /> android:layout_width=“match_parent” android:layout_height=“wrap_content” android:orientation=“vertical”> 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” /> 适配器布局修改好了之后,再回到WallPaperAdapter中,在onBindViewHolder方法中添加对点击事件的绑定,修改代码如下图如下: 然后新增一个PictureViewActivity,对应的布局activity_picture_view.xml代码如下: 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”> 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” /> 然后修改一下PictureViewActivity的代码,在onCreate方法中增加如下代码。 String img = getIntent().getStringExtra(“img”); if (img != null) { ImageView imageView = findViewById(R.id.image); Glide.with(this).load(img).into(imageView); } 运行效果如下所示: 好的,下面对主页面进行一下美化,现在这个样子确实不好看。 六、协调布局使用 在页面中默认的ActionBar占了无用的控件,我们可以自定义一个样式去替换当前页面的样式,在themes.xml下增加如下代码: 然后修改AndroidManifest.xml中的代码: 设置theme,然后修改activity_main.xml布局代码: xmlns:app=“http://schemas.android.com/apk/res-auto” xmlns:tools=“http://schemas.android.com/tools”> name=“viewModel” type=“com.llw.mvvm.viewmodels.MainViewModel” /> android:layout_width=“match_parent” android:layout_height=“match_parent” android:fitsSystemWindows=“true” tools:context=“.MainActivity”> android:id=“@+id/appbar_layout” android:layout_width=“match_parent” android:layout_height=“200dp” android:fitsSystemWindows=“true” android:theme=“@style/ThemeOverlay.AppCompat.Dark.ActionBar”> 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”> 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” /> 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” /> 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”> android:id=“@+id/rv” android:layout_width=“match_parent” android:layout_height=“wrap_content” android:padding=“5dp” /> 然后修改MainActivity中的代码,在initView中增加如下代码: //伸缩偏移量监听 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类,里面的代码如下: @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接口,里面的代码如下: @Dao public interface WallPaperDao { @Query(“SELECT * FROM wallpaper”) Flowable @Insert(onConflict = OnConflictStrategy.REPLACE) Completable insertAll(List wallPapers); @Query(“DELETE FROM wallpaper”) Completable deleteAll(); } 这里依然通过RxJava2去操作数据的处理,这里我有删除、增加、查询所有数据的方法。 3. 版本升级迁移 在AppDatabase中增加如下代码,用于数据库版本的升级迁移,这里我是新增了一个迁移对象,构建迁移对象的版本,从1到2,然后执行一段SQL语句,该语句用于创建一个新的表。 /** 版本升级迁移 */ 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中增加两个常量,用于控制热门壁纸的请求和使用方式。 //今日是否请求了热门壁纸 public static final String IS_TODAY_REQUEST_WALLPAPER = “isTodayRequestWallPaper”; //今日请求热门壁纸的时间戳 public static final String REQUEST_TIMESTAMP_WALLPAPER = “wallpaperRequestTimestamp”; 然后在MainRepository中增加如下代码: /** 热门壁纸数据 */ final MutableLiveData wallPaper = new MutableLiveData<>(); 下面在MainRepository中增加一个保存网络热门壁纸数据到本地的方法,代码如下: /** 保存热门壁纸数据 */ 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 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中增加一个从本地数据库中读取数据的方法,代码如下: /** 从本地数据库获取热门壁纸 */ private void getLocalDBForWallPaper() { Log.d(TAG, “getLocalDBForWallPaper: 从本地数据库获取 热门壁纸”); WallPaperResponse wallPaperResponse = new WallPaperResponse(); WallPaperResponse.ResBean resBean = new WallPaperResponse.ResBean(); List Flowable 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方法,里面的代码如下: /** 获取壁纸数据 @return wallPaper */ public MutableLiveData 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,里面的代码如下: xmlns:app=“http://schemas.android.com/apk/res-auto”> android:layout_width=“match_parent” android:layout_height=“match_parent”> 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” /> 这里直接使用ViewPager2,相比于ViewPager来说,这个会更强大。因为ViewPager2可以在setAdapter时直接设置RecyclerView.Adapter,很方便,因此这里同样需要一个适配器,这里的适配器我不打算用原生的来写。 2. BaseQuickAdapter使用 这是一个第三方开源框架,非常的强大,GitHub地址如下:BaseRecyclerViewAdapterHelper 最新的版本中是支持androidx和DataBinding的,因此很方便我们使用它。下面添加依赖库,在app的build.gradle的dependencies{}闭包中增加如下代码: //RecyclerView的好搭档 implementation ‘com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4’ 然后点击Sync Now同步项目添加依赖库。 在使用BaseQuickAdapter时,先创建适配器的布局,在layout下新建一个item_image.xml,里面的代码如下: name=“wallPaper” type=“com.llw.mvvm.db.bean.WallPaper” /> android:id=“@+id/image” networkUrl=“@{wallPaper.img}” android:background=“@drawable/wallpaper_bg” android:layout_width=“match_parent” android:layout_height=“match_parent” android:scaleType=“centerCrop” /> 这里也是通过DataBinding来处理数据的赋值,下面在adapter包下新建一个ImageAdapter类,里面的代码如下: public class ImageAdapter extends BaseQuickAdapter public ImageAdapter(@Nullable List data) { super(R.layout.item_image, data); } @Override protected void convert(@NotNull BaseDataBindingHolder 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类,里面的代码如下: public class PictureRepository { private final MutableLiveData public MutableLiveData 自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。 深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前! 因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。 既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化! 由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新! 如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android) 总结 首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。 另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。 之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。 下一步还是要查漏补缺,进行针对性复习。 最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!希望大家不要犯和我一样的错误呀!!!一定要看完! 《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取! MutableLiveData public MutableLiveData 自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。 深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前! 因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。 [外链图片转存中…(img-r5MuIM8b-1711754738962)] [外链图片转存中…(img-dm5cnija-1711754738962)] [外链图片转存中…(img-5yTfE3AP-1711754738963)] [外链图片转存中…(img-9ABiCyTM-1711754738963)] [外链图片转存中…(img-NfBlPaAX-1711754738963)] 既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化! 由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新! 如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android) 总结 首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。 另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。 之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。 下一步还是要查漏补缺,进行针对性复习。 最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!希望大家不要犯和我一样的错误呀!!!一定要看完! [外链图片转存中…(img-Jj4x40fb-1711754738963)] [外链图片转存中…(img-pNlhkZp8-1711754738964)] [外链图片转存中…(img-KRmkpsqH-1711754738964)] 《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取! 推荐阅读 getAll();
listFlowable = BaseApplication.getDb().wallPaperDao().getAll();
wallPaper = new MutableLiveData<>();
getWallPaper() {
wallPaper = new MutableLiveData<>();
getWallPaper() {
发表评论