自定义ViewGroup中child绘制顺序

背景

在项目中需要显示一组用户头像(见下图),右边的图片有一部分在左边的图片下面,依次叠加。

自定义ViewGroup中child绘制顺序_第1张图片

我们可以使用LinearLayout,然后每个图片向左偏移-xdp,但是这样做你会发现实现的效果并不是这样,而是右边的头像在左边的头像上面。

自定义ViewGroup中child绘制顺序_第2张图片

这是因为ViewGroup绘制子view的顺序导致的,先添加的view先绘制,我们可不可以改变ViewGroup的绘制顺序呢,当然可以,在View中有一个方法setTranslationZ(float translationZ),translationZ值越大优先级就越低,这个方法虽然可以实现我们的效果,但是只能在5.0及以上版本上使用,我们的项目最低版本是4.1,因此也就不能用这个方法。还有没有其他方法可以改变绘制顺序呢,只有去看下ViewGroup的源码了。

源码分析

找到ViewGroup中dispatchDraw方法,这个方法作用是遍历ViewGroup中所有的子View绘制出来。

@Override
protected void dispatchDraw(Canvas canvas) {
		...省略无关代码...
    final ArrayList<View> preorderedList = usingRenderNodeProperties
                ? null : buildOrderedChildList();//①获取子View列表
        final boolean customOrder = preorderedList == null
                && isChildrenDrawingOrderEnabled();//②判断是否是自定义排序
        for (int i = 0; i < childrenCount; i++) {
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);//③绘制View
            }
        }
  ......
}

先看注释①,获取预定义的排序列表,判断没有使用硬件加速则调用buildOrderedChildList方法,

ArrayList<View> buildOrderedChildList() {
        final int childrenCount = mChildrenCount;
        if (childrenCount <= 1 || !hasChildWithZ()) return null;

        if (mPreSortedChildren == null) {
            mPreSortedChildren = new ArrayList<>(childrenCount);
        } else {
            // callers should clear, so clear shouldn't be necessary, but for safety...
            mPreSortedChildren.clear();
            mPreSortedChildren.ensureCapacity(childrenCount);
        }

        final boolean customOrder = isChildrenDrawingOrderEnabled();//④
        for (int i = 0; i < childrenCount; i++) {
            // add next child (in child order) to end of list
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);//⑤
            final View nextChild = mChildren[childIndex];
            final float currentZ = nextChild.getZ();//⑥

            // insert ahead of any Views with greater Z
            int insertIndex = i;
          //⑦
            while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
                insertIndex--;
            }
            mPreSortedChildren.add(insertIndex, nextChild);
        }
        return mPreSortedChildren;
    }

buildOrderedChildList方法中 ,注释④调用isChildrenDrawingOrderEnabled方法,这个方法是控制是否开启自定义排序,默认是返回false,注释⑤中调用getAndVerifyPreorderedIndex方法,获取View的顺序

private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
        final int childIndex;
        if (customOrder) {
            final int childIndex1 = getChildDrawingOrder(childrenCount, i);//⑧
            if (childIndex1 >= childrenCount) {
                throw new IndexOutOfBoundsException("getChildDrawingOrder() "
                        + "returned invalid index " + childIndex1
                        + " (child count is " + childrenCount + ")");
            }
            childIndex = childIndex1;
        } else {
            childIndex = i;
        }
        return childIndex;
    }

注释⑧调用getChildDrawingOrder方法,这个方法是根据顺序获取view下标,可以自定义顺序,如果没有开启自定义顺序默认绘制顺序也view的添加顺序,也就是说我们可以覆盖这个方法,返回我们自己定义的顺序,从而改变子View的绘制顺序。我们通过getAndVerifyPreorderedIndex获取到了View的绘制顺序后回到注释⑥,这里获取View的translationZ值,然后循环确保translationZ值小的在值大前面。最后执行完buildOrderedChildList方法后重新重新排序的子View列表。

最后回到dispatchDraw方法注释③循环遍历获取子View列表依次绘制子View。

解决方法

看完源码,我们就知道怎么自定义子View绘制顺序了

1.重写isChildrenDrawingOrderEnabled方法返回true,表示开启自定义绘制顺序

2.重写getChildDrawingOrder方法,根据自己实际需求返回View的顺序。由于新添加的View先绘制,所以我们需要倒序返回。

代码如下:

class CustomOrderDrawChildLinearLayout(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {

    override fun isChildrenDrawingOrderEnabled(): Boolean {
        return true
    }

    override fun getChildDrawingOrder(childCount: Int, i: Int): Int {
        return childCount - i - 1//倒序
    }
}

你可能感兴趣的:(Android,源码解析,android)