【从零开始撸一个App】RecyclerView的使用

前段时间打造了一款简单易用功能全面的图片上传组件,现在就来将上传的图片以图片集的形式展现到App上。出于用户体验考虑,加载新图片采用[无限]滚动模式,Android平台上我们优选RecyclerView组件。

显示图片,用的自然是ImageView,然而它并不支持直接加载网络图片,需要先通过其它网络组件(如HttpURLConnection、okhttp3等)将图片获取到本地,得到BitMap数据,然后通过setImageBitmap()加载。
ImageView也有setImageURI(Uri uri)方法,这里uri的命名容易让人产生错觉,其实只能是本地文件路径。

所幸,一些开源组件封装了繁琐的网络操作和缓存策略,提供了易用的API。这里我选择了Glide。

实现 加载更多 项布局

有两个,一个用于列表中各个图片显示,一个显示加载更多/已全部加载放置在列表最末提示用户。

<!--图片--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:id="@+id/thumbnail_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop"/> </LinearLayout> <!--loadmore--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <TextView android:id="@+id/tv_load_more" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="正在加载更多" /> </LinearLayout> RecyclerView.Adapter

RecyclerView的设计模式网上资料很多,此处不再赘述。先实现RecyclerView.Adapter。

class ThumbnailListAdapter( private val thumbnails: List<Thumbnail>, private val totalCount: Long, private val context: Context ) : RecyclerView.Adapter<ThumbnailListAdapter.ThumbnailViewHolder>() { // 调用若干次 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThumbnailViewHolder { // viewType就是通过getItemViewType得到的 val itemView = LayoutInflater.from(context).inflate(viewType, parent, false) return ThumbnailViewHolder(itemView) } // 搞分页/瀑布加载的同学不要把这个和数据库的总数量搞混,这里的itemCount表示现在内存中数据量 // 我们可以[从后端]获取新数据添加到数据集,以实现loadmore功能 override fun getItemCount(): Int { return if (thumbnails.isNotEmpty()) thumbnails.size + 1 // +1 是因为除了thumbnails数据集之外,还有个写死的loadmore项 else 0 } // R.layout.xxx 是Int类型,可以直接返回 override fun getItemViewType(position: Int): Int { return if (position < thumbnails.size) R.layout.list_thumbnail_image // 正常图片显示 else R.layout.list_loadmore_footer // 末尾loadmore } // 有屏幕外item进入屏幕时就会调用 override fun onBindViewHolder(holder: ThumbnailViewHolder, position: Int) { if (position < thumbnails.size) { Glide.with(context) .load(thumbnails[position].uri) .into(holder.itemView.thumbnail_view) } else { if (thumbnails.size >= totalCount) holder.itemView.tv_load_more.text = "全部加载完毕" } } // 必须这么继承一下 class ThumbnailViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) } 滚动监听

为RecyclerView添加滚动监听,在合适的时候加载新数据到数据集中。

recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) // 已经在加载则跳过 if (!_thumbnailsLoading) { // 找到最后可见项的索引 val lastPos = layoutManager.findLastVisibleItemPosition() val sum = adapter.itemCount // 当快接近末尾项时(这里差额10,表示再显示10个item就没数据了)获取新数据 if (newState == RecyclerView.SCROLL_STATE_IDLE && sum - lastPos <= 10) { vm.thumbnails.addAll(vm.getMoreAlbumCovers()) // 加载新数据到数据集中 _thumbnailsLoading = true } } } })

不要将上面预加载数据和Glide的预加载图片混淆起来,拿到数据,和通过数据中的uri获取图片并下载,这是两个步骤。Glide专门针对RecyclerView提供了预加载方案,是为了减少滑动时图片还未从网络请求导致的等待加载情况,目前只支持LinearLayoutManager或其子类布局

布局 StaggeredGridLayoutManager

按列瀑布流显示图片。简单地将RecyclerView的layoutManager设为StaggeredGridLayoutManager实例即可,注意StaggeredGridLayoutManager目前还是beta版。

val sgLayoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL) recyclerview.layoutManager = sgLayoutManager

使用StaggeredGridLayoutManager会发现上下滑动过程中,经常发生图片块重排。根据网上说法,这是因为复用的ViewHolder和该ViewHolder要加载的图片,它们的尺寸不一致导致。比如某个ViewHolder之前加载的图片高度为60,之后被回收,但是尺寸信息仍然保留着,后来被一张高度80的图片复用,由于StaggeredGridLayoutManager是根据ViewHolder的尺寸排序布局,尺寸的变化导致发生多次排序。解决方法是在ViewHolder绑定数据时(在RecyclerView.Adapter.onBindViewHolder()中),就事先设置好本次布局的最终尺寸,如下:

override fun onBindViewHolder(holder: ThumbnailViewHolder, position: Int) { val layoutParams = holder.itemView.thumbnail_view.layoutParams as LinearLayout.LayoutParams //手动设置ViewHolder高度 layoutParams.height = thumbnails[position].height Glide.with(context).load(thumbnails[position].uri) .into(holder.itemView.thumbnail_view) }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyzzgz.html