注册

CoordinatorLayout 嵌套Recycleview 卡顿问题

1.问题场景


伪代码:
<CoordinatorLayout>
<AppBarLayout>
<RecycleView>
</RecycleView>
</AppBarLayout>
</ConstraintLayout>

一般这种做法是,底部view的相应滑动,滑动联动,但是同时会出现RecycleView ViewHoder复用失败,造成cpu 的消耗,item到达一定数量后会造成oom页面出现卡顿


2. 问题原理


RecycleView ViewHoder 复用问题第一时间我们应想到是; ViewGrop/onMeasureChild
测量问题,重写 onMeasureChild ,避免中间MeasureSpec.UNSPECIFIED模式 的赋值造成RecycleView的item复用,但是是失败的!


 @Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
child.measure(parentWidthMeasureSpec, parentHeightMeasureSpec);
}


原因是: parentHeightMeasureSpec 已经被设置 MeasureSpec.UNSPECIFIED 测量模式 看下源码CoordinatorLayout onMeasure 局部关键代码:


prepareChildren();

final Behavior b = lp.getBehavior();
if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0)) {
onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0);
}

通过prepareChildren()结合LayoutParams


        R.styleable.CoordinatorLayout_Layout_layout_behavior);
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_Layout_layout_behavior));
}

我们可以得到 Behavior b 就是我们再布局内设置的 AppBarLayout. layout_behavior, 可以看到 Behavior/onMeasureChild 做了一层测量, ,我们继续看 Behavior/onMeasureChild 源码:


@Override
public boolean onMeasureChild(
@NonNull CoordinatorLayout parent,
@NonNull T child,
int parentWidthMeasureSpec,
int widthUsed,
int parentHeightMeasureSpec,
int heightUsed) {
final CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) child.getLayoutParams();
if (lp.height == CoordinatorLayout.LayoutParams.WRAP_CONTENT) {
// If the view is set to wrap on it's height, CoordinatorLayout by default will
// cap the view at the CoL's height. Since the AppBarLayout can scroll, this isn't
// what we actually want, so we measure it ourselves with an unspecified spec to
// allow the child to be larger than it's parent
parent.onMeasureChild(
child,
parentWidthMeasureSpec,
widthUsed,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
heightUsed);
return true;
}

// Let the parent handle it as normal
return super.onMeasureChild(
parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
}

问题找到了问题关键 if (lp.height == CoordinatorLayout.LayoutParams.WRAP_CONTENT) ,造成了MeasureSpec.UNSPECIFIED 的使用, 而这个模式又会造成Recycleview.LayoutManager加载所有的item,导致复用失败; 看到这 AppBarLayout给固定值或者match_parent 不就解决问题了吗, 是能解决问题,但是这样 我们的layout ui就不符合我们绘制ui的布局了,也会造成页面空白显示问题,所以这样使用recycleview 嵌套是非法使用,矛盾使用!


解决问题



  • 同一使用RecycleView 使用,作为RecycleView item 的一部分,但是也会造成滑动冲突问题,然后通过 NestedScrollingParent3 外部拦截法,来解决内外层的滑动冲突,问题顺利解决

override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
if (e!!.action == MotionEvent.ACTION_DOWN) {
val childRecyclerView = findCurrentChildRecyclerView()

// 1. 是否禁止拦截
doNotInterceptTouchEvent = doNotInterceptTouch(e.rawY, childRecyclerView)

// 2. 停止Fling
this.stopFling()
childRecyclerView?.stopFling()
}

return if (doNotInterceptTouchEvent) {
false
} else {
super.onInterceptTouchEvent(e)
}
}


  • 根据业务场景,也可使用baserecyclerviewadapterhelper,一个优秀的Adapter 框架, 

addHeaderView来添加itemView,通过 notifyItemInserted(position) 添加ReceiveView 的item


@JvmOverloads
fun addHeaderView(view: View, index: Int = -1, orientation: Int = LinearLayout.VERTICAL): Int {
if (!this::mHeaderLayout.isInitialized) {
mHeaderLayout = LinearLayout(view.context)
mHeaderLayout.orientation = orientation
mHeaderLayout.layoutParams = if (orientation == LinearLayout.VERTICAL) {
RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
} else {
RecyclerView.LayoutParams(WRAP_CONTENT, MATCH_PARENT)
}
}

val childCount = mHeaderLayout.childCount
var mIndex = index
if (index < 0 || index > childCount) {
mIndex = childCount
}
mHeaderLayout.addView(view, mIndex)
if (mHeaderLayout.childCount == 1) {
val position = headerViewPosition
if (position != -1) {
notifyItemInserted(position)
}
}
return mIndex
}

0 个评论

要回复文章请先登录注册