您的位置:首页 > 其它

与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高度即可,思路类似。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: