RecyclerView的子项item使用bringToFront()失效【RecyclerView+ViewDragHelper】

又名

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立即生效
    }

注意

  1. 注意最后这一句recyclerView.invalidate(),必须invalidate()使得新的ChildDrawingOrderCallback生效
  2. 需要recyclerView.setChildrenDrawingOrderEnabled(true)启用子视图排序功能
  3. onGetChildDrawingOrder可以来控制子View显示的先后顺序
  4. ItemTouchHelper源码中在每次onDraw()的时候都对mOverdrawChildPosition置-1,是为了防止中途还做了其他改变(比如remove View)导致失效

RecycleView是不能通过诸如addView(View v)轻易添加儿子的,需要一些特殊的方法,毕竟其本意就是子View由RecycleView自己管理。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值