又名
RecyclerView如何通过onGetChildDrawingOrder达到更改层级诸如setElevation的效果?
问题
对RecyclerView的子View使用bringToFront()
失效,子View并没有比其他子View的层级高。
原因
我们知道在Android 5.0后可以通过setElevation()
或者setTranslationZ()
来更改View的视觉层级,对于5.0以下普通的ViewGroup可以通过bringToFront()
实现的。但是对RecyclerView不可以因为RecyclerView是由LayoutManager来掌管子View的层级的(见bringToFront does not work in RecyclerView )
解决方法
方案一
对于RecyclerView重写其LayoutManager,需要妥善处理每个出现在视觉里的view的detach与recycle
方案二
使用ItemTouchHelper
快速定义拖拽等逻辑
方案三
为RecyclerView添加setChildDrawingOrderCallback()
本文详述方案三:
我们知道RecycleView只要增加ItemTouchHelper
就可以轻而易举地实现子item的拖拽并且不被遮挡,但是由于本文实现的是RecyclerView+ViewDragHelper的方式所以不使用ItemTouchHelper。
解决过程
我们可以参考ItemTouchHelper的实现方式,通过读源码可以看见,ItemTouchHelper在5.0以上是使用的Elevation,而5.0以下是通过给RecyclerView添加ChildDrawingOrderCallback来达到的这种效果的。
private void addChildDrawingOrderCallback() {
if (Build.VERSION.SDK_INT >= 21) {// we use elevation on Lollipop
if (mOverdrawChild != null) {
mOverdrawChild.setTranslationZ(10);
}
return;
}
if (mChildDrawingOrderCallback == null) {
mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() {
@Override
public int onGetChildDrawingOrder(int childCount, int i) { // childCount总数, i 遍历到的位置
if (mOverdrawChild == null) { //mOverdrawChild想要提起不被遮挡的子View
return i;
}
int childPosition = mOverdrawChildPosition; //mOverdrawChildPosition 是mOverdrawChild的position
if (childPosition == -1) { // 如果没有指定
childPosition = recyclerView.indexOfChild(mOverdrawChild); // 寻找mOverdrawChild的位置
mOverdrawChildPosition = childPosition;
}
// 如果超出总数,或者没有找到这个view在RecyclerView的位置
if (childPosition >= childCount || childPosition == -1) {
return i;
}
Log.d("clip:", mOverdrawChildPosition + "," + childCount + "," + i);
// 渲染层级走到最后一个位置,返回我们需要抬起不被遮挡的View的postion即mOverdrawChildPosition
if (i == childCount - 1) {
return childPosition;
}
return i < childPosition ? i : i + 1; // 返回其他在底下的view的position
}
};
}
recyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback);
}
由此在每个view被捕捉的时候对子View addChildDrawingOrderCallback()
,完成后 recyclerView.setChildDrawingOrderCallback(null)
,在捕获新View的时候去掉之前老View的CallBack,代码如下:
recyclerView.setChildrenDrawingOrderEnabled(true) // 启用子视图排序功能
private void bringToFront(View child) {
mOverdrawChildPosition = -1; //重置`mOverdrawChildPosition = -1`
if (mOverdrawChild != child) {
mOverdrawChild = null;
recyclerView.setChildDrawingOrderCallback(null); // 需要对recycleView判空
mOverdrawChild = child;
}
addChildDrawingOrderCallback();
recyclerView.invalidate(); // 必须加上,使得新得ChildDrawingOrderCallback立即生效
}
注意
- 注意最后这一句
recyclerView.invalidate()
,必须invalidate()
使得新的ChildDrawingOrderCallback
生效 - 需要recyclerView.setChildrenDrawingOrderEnabled(true)启用子视图排序功能
- onGetChildDrawingOrder可以来控制子View显示的先后顺序
- ItemTouchHelper源码中在每次onDraw()的时候都对mOverdrawChildPosition置-1,是为了防止中途还做了其他改变(比如remove View)导致失效
RecycleView是不能通过诸如
addView(View v)
轻易添加儿子的,需要一些特殊的方法,毕竟其本意就是子View由RecycleView自己管理。