在项目中需要显示一组用户头像(见下图),右边的图片有一部分在左边的图片下面,依次叠加。
我们可以使用LinearLayout,然后每个图片向左偏移-xdp,但是这样做你会发现实现的效果并不是这样,而是右边的头像在左边的头像上面。
这是因为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//倒序
}
}