< 返回技术文档列表

如何修改ViewGroup默认的顺序绘制子View

发布时间:2021-11-07 01:06:51⊙投诉举报

这篇文章主要讲解了“如何修改ViewGroup默认的顺序绘制子View”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何修改ViewGroup默认的顺序绘制子View”吧!

TV App 的 Item 处理

修改 View 的绘制顺序,在日常开发中,基本用不到。众多手机端 App 的 UI 设计,大部分采用扁平化的设计思想,除非是一些很特别的自定义  View,多数情况下,我们无需考虑 View 的默认绘制顺序。

这也很好理解,正常情况下,ViewGroup 中后添加的 View,视觉上就是应该覆盖在之前的 View 之上。

但是有一个场景的设计,很特别,那就是 Android TV App。

在 TV 的设计上,因为需要遥控器按键控制,为了更丰富的视觉体验,是需要额外处理 View 对焦点状态的变化的。

例如:获取焦点的 ItemView 整个高亮,放大再加个阴影,都是很常见的设计。

那么这就带来一个问题,正常我们使用 RecyclerView 实现的列表效果,当 Item 之间的间距过小时,单个 Item  被放大就会出现遮盖的效果。

如何修改ViewGroup默认的顺序绘制子View

例如上图所示,一个很常见的焦点放大高亮的设计,但却被后面的 View 遮盖了。

这样的情况,如何解决呢?

拍脑袋想,既然是间距太小了,那我们就拉大间距就好了。修改一个属性解决一个需求,设计师哭晕在工位上。

不过确实有一些设计效果,间距足够,也就不存在遮盖的现象

但是我们不能只靠改间距解决问题,多数情况下,设计师留给我们的间距并不多

既然逃不掉,那就研究一下如何解决。

修改绘制顺序原理

修改绘制顺序,其实很简单,Android 已经为我们留出了扩展点。

我们知道,ViewGroup 通过其成员 mChildren 数组,存储子 View。而在 ViewGroup 绘制子 View 的  dispatchDraw() 方法循环中,并不是直接利用索引从 mChildren 数组中取值的。

@Override protected void dispatchDraw(Canvas canvas) {   // ...   final ArrayList<View> preorderedList = usingRenderNodeProperties         ? null : buildOrderedChildList();   final boolean customOrder = preorderedList == null         && isChildrenDrawingOrderEnabled();   for (int i = 0; i < childrenCount; i++) {     // ...     final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);     // 并非直接从 mChildren 中获取     final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {         more |= drawChild(canvas, child, drawingTime);     }   }   // ... }

可以看到,child 并非是从 mChildren 中直取,而是通过 getAndVerifyPreorderedView() 获得,它的参数除了  children 外,还有一个 preorderedList 的 ArrayList,及子 View 的索引。

private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList,         View[] children,         int childIndex) {   final View child;   if (preorderedList != null) {     child = preorderedList.get(childIndex);     if (child == null) {         throw new RuntimeException("Invalid preorderedList contained null child at index "                 + childIndex);     }   } else {     child = children[childIndex];   }   return child; }

在其中,若 preorderedList 不为空,则从其中获取子 View,反之则还是从 children 中获取。

回到前面 dispatchDraw() 中,这里使用的 preorderedList 关键列表,来自  buildOrderedChildList(),在方法中通过 getAndVerifyPreorderedIndex() 获取对应子 View  的索引,此方法需要一个 Boolean 类型的 customOrder,即表示是否需要自定义顺序。

ArrayList<View> buildOrderedChildList() {   // ...   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() 的逻辑就是按照 Z 轴调整 children 顺序,Z 轴值相同则参考 customOrder  的配置。

通常 ViewGroup 中的子 View,Z 值一致,所以关键参数是 customOrder 开关。

从代码上了解到 customOrder 是通过 isChildrenDrawingOrderEnabled() 方法获取,与之对应的是  setChildrenDrawingOrderEnabled() 可以设置 customOrder 的取值。

也就是说,如果我们要调整顺序,只需 2 步调整:

调用 setChildrenDrawingOrderEnable(true) 开启自定义绘制顺序

重写 getChildDrawingOrder() 修改 View 的取值索引

实例

最后,我们写个 Demo,重写 RecycleView 的 getChildDrawingOrder() 方法,来实现获得焦点的 View  最后绘制。

@Override protected int getChildDrawingOrder(int childCount, int i) {   View view = getLayoutManager().getFocusedChild();   if (null == view) {     return super.getChildDrawingOrder(childCount, i);   }   int position = indexOfChild(view);   if (position < 0) {     return super.getChildDrawingOrder(childCount, i);   }   if (i == childCount - 1) {     return position;   }   if (i == position) {     return childCount - 1;   }   return super.getChildDrawingOrder(childCount, i); }

别忘了还需要调用 setChildrenDrawingOrderEnabled(true) 开启自定义绘制顺序。

如何修改ViewGroup默认的顺序绘制子View

此时,焦点放大时,就不会被其他 View 遮挡。

感谢各位的阅读,以上就是“如何修改ViewGroup默认的顺序绘制子View”的内容了,经过本文的学习后,相信大家对如何修改ViewGroup默认的顺序绘制子View这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是血鸟云,小编将为大家推送更多相关知识点的文章,欢迎关注!


/template/Home/Zkeys/PC/Static