当由下滑回到最顶部时,经常会出现顶部(第一行)的图片相互重排。仔细观察,这是因为第一行初次布局时是按顺序排列而非按空缺插入,往回滑时则是按空缺(哪里最空最先排哪里),这导致顺序可能与初次排序不一致。不过还好,最终仍会按照图片尺寸各自归位。而且这种情况只会出现在第一次由下滑回到顶部时。
GreedoLayoutManagerStaggeredGridLayoutManager一共有3k多行代码,又是beta版。代码洁癖的我把目光投向了GreedoLayoutManager,它是500px开源的一个LayoutManager,能在保持图片宽高比例的前提下将多张图片拼接到一行显示,原理很简单,看下面动图:
替换LayoutManager也相当简单,重新设置下RecyclerView的layoutManager即可。
val layoutManager = GreedoLayoutManager(adapter).also { it.setMaxRowHeight(resources.displayMetrics.heightPixels / 3) } recyclerview.layoutManager = layoutManagerGreedoLayoutManager在布局之前需要知道item的宽高比例,只要让Adapter实现SizeCalculatorDelegate接口即可
override fun aspectRatioForIndex(index: Int): Double { val thumbnail = thumbnails[index] return thumbnail.width / thumbnail.height.toDouble() }运行界面显示:
可以看到每张图片都比预期大很多,只能看到一小部分。经研究发现,上面定义的图片展示项的布局(LinearLayout内嵌ImageView),最终呈现后,LinearLayout的尺寸是每个网格的尺寸,而内嵌的ImageView则超出了LinearLayout,似乎其最终尺寸是MeasuredSize——我们在onCreateViewHolder时使用了LayoutInflater.from(context).inflate(viewType, parent, false),这里的parent是RecyclerView,而在布局xml中宽高都设置为match_parent,因此其中ImageView的MeasuredSize同RecyclerView的宽高——然而ImageView最终尺寸应该同样适配网格尺寸才对。
以width为例:
期望:ImageView.width == LinearLayout.width == 网格.width 实际:ImageView.width == ImageView.measuredWith == RecyclerView.width我们看到每个框格其实是ImageView被截取的左上角那部分。
经过一番搜索,网上各种对getWidth和getMeasuredWidth区别的阐述,并没有解决我的困惑,直到这篇从源码的角度分析,getWidth() 与 getMeasuredWidth() 的不同之处让我知道,其实Android系统并没有对width下定义,自定义布局时可随意设置子项大小,是否超出屏幕也没有限制。在我们这个场景下,估计GreedoLayoutMananger在处理了最外层控件(这里是LinearLayout)的width后,并没有递归处理内部控件的width,从而导致了这个bug。
既然如此,那么就不要外围的LinearLayout,直接使用ImageView,反倒省了一点开销。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThumbnailViewHolder { return if (viewType == 0) { val imageView = ImageView(parent.context).apply { scaleType = ImageView.ScaleType.CENTER_CROP layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) } ThumbnailViewHolder(imageView) } else { val itemView = LayoutInflater.from(context).inflate(viewType, parent, false) ThumbnailViewHolder(itemView) } }当然也有ViewHolder重用导致的显示问题,图片只显示一部分,且是按ViewHolder重用前的宽高比例显示,如下:
懒得深究,使用Glide官方文档建议的waitForLayout()并没有用,override(width, height)提前告知图片尺寸解决。
Glide.with(context) .load(thumbnails[position].uri) .override(thumbnails[position].width, thumbnails[position].height) .into(holder.itemView as ImageView) // .waitForLayout() //并没有用 下拉刷新使用SwipeRefreshLayout,easy,按过不表。最后成品如下