与ListView不同,RecyclerView的嵌套解决
2016-05-16 15:48
218 查看
1、问题
ListView的item中嵌套了RecyclerView实现水平方向列表,导致RecyclerView高度不能正常显示。2、传统解决方案
嵌套问题最基本的解决方法是重写onMeasure方法,手动测量ViewGroup的高度或者宽度。这里,因为我们的RecyclerView是水平的,而且每一个item的高度是相同的,所以只需要测出一个item的所占用高度(包括父控件的上下padding以及item的上下margin)作为RecyclerView的高度即可。代码如下:public class MeasuredRecyclerView extends RecyclerView { public MeasuredRecyclerView(Context context) { this(context, null); } public MeasuredRecyclerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MeasuredRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onMeasure(int widthSpec, int heightSpec) { int maxHeight = 0; if(getChildCount() > 0) { View child = getChildAt(0); RecyclerView.LayoutParams params = (LayoutParams) child.getLayoutParams(); child.measure(widthSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); maxHeight = child.getMeasuredHeight() + getPaddingTop() + getPaddingBottom()+ params.topMargin + params.bottomMargin; } super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY)); } }
上述代码,大部分时候运行是正常的,RecyclerView高度正确。在RecyclerView初始化adapter还没有设置数据时,maxHeight为0;当数据请求完成后调用adapter.notifyDataSetChanged(),onMeasure方法再次调用,重新测量item高度,显示正确高度,这是符合期望的。但是测试时注意到,有些时候adapter.notifyDataSetChanged()调用后onMeasure操作并没有重新执行,导致RecyclerView高度不能正确显示。为了解决这个问题,我们也可以给maxHeight设置默认高度,但是item样式不一定统一,为了通用性,可能需要寻找其他解决方法。
3、优化方案
google一下发现(最近某度被黑的很惨,已经弃用),大都解决方案是通过重写LinearLayoutManager实现的。RecyclerView和ListView,GridView等其他控件最大不同可能就是LayoutManager了,RecyclerView使用LayoutManager来管理item的布局。在RecyclerView的数据发生改变时,LayoutManager都需要重新对item进行布局。可以看下RecyclerView.Adapter关于notifyDataSetChanged() 方法的注释。/** * Notify any registered observers that the data set has changed. * <p>There are two different classes of data change events, item changes and structural * changes. Item changes are when a single item has its data updated but no positional * changes have occurred. Structural changes are when items are inserted, removed or moved * within the data set.</p> * <p>This event does not specify what about the data set has changed, forcing * any observers to assume that all existing items and structure may no longer be valid. * LayoutManagers will be forced to fully rebind and relayout all visible views.</p> */ public final void notifyDataSetChanged() { mObservable.notifyChanged(); }
从注释可以看到,对于data的任何变化,不管是item数据更新还是item插入删除,layoutManager都需要强制刷新布局。因此,可以重写LayoutManager的onMeasure方法来实现手动测量item高度。代码如下:
public class MeasuredLinearLayoutManager extends LinearLayoutManager { private int maxHeight; public MeasuredLinearLayoutManager(Context context) { super(context); } @Override public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { if(maxHeight == 0 && getItemCount() > 0) { View child = recycler.getViewForPosition(0); RecyclerView.LayoutParams params = (LayoutParams) child.getLayoutParams(); child.measure(widthSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); int height = child.getMeasuredHeight() + getPaddingTop() + getPaddingBottom() + params.topMargin + params.bottomMargin; if (height > maxHeight) { maxHeight = height; } } heightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY); super.onMeasure(recycler, state, widthSpec, heightSpec); } }
这种方案可以解决问题,但是值得注意的是,每次item发生改变时,都会重新measure,所以在onMeasure中最好不要做太多复杂的操作。
注:以上代码均针对item高度一致的情况,对于高度不同的情况,需要遍历所有item,找出item的最大高度,并设置成RecyclerView高度即可,思路类似。
相关文章推荐
- gitbub上传代码
- Hdu-3336 Count the string(KMP + DP)
- 有容云——窥探Docker中的Volume Plugin内幕
- 用正则判断密码是否正确
- 常用的不透明度的值,以黑色为例
- iOS 构造json并提交到服务器
- Callable, Future和FutureTask简述
- RGB-D 实时三维重建/SLAM 中的 ICP 算法解析
- flume入门配置
- 这个制作App的技能,让你酷到没朋友!
- 二维数组 组合方案
- ORA-12154 & TNS-03505 案例分享
- 求最大素数
- iOS实时滤镜实现--基于GPUImage。
- 面试中的智力题及编程实践(二)
- Qt实现应用单实例运行(3)-使用QTSingleApplication
- 电池电压容量关系
- iOS DrawRect简单使用
- 一些C++经典书籍
- CSU 国防科大决赛 D题 找路径 (水题-DP)