A, sequence

Hello everyone, I am Chengxiang Moying, long time no see, very miss!

Today we’re going to talk about a small detail of the View drawing process, the custom drawing order.

View of the three processes, measurement, layout, drawing, I think we should be familiar with the heart. In the drawing phase, the ViewGroup not only needs to draw itself, but also needs to draw its children. This drawing strategy defaults to sequential drawing, i.e. [0 ~ childCount).

Is there any way to adjust the default policy? For example, change it to (childCount ~ 0), or change a View to draw last. At the same time, what scenes did we need to make this change?

It is important to note that the drawing order affects the overlay order, and also affects the event distribution of the View. These are related effects, which can be described as affecting the whole.

Let’s talk about that today.

2. Item processing of TV App

Modify the View drawing order, in daily development, basic use. The UI design of many mobile apps is mostly flat, except for some very special custom views. In most cases, we do not need to consider the default drawing order of the View.

This makes sense. Normally, a View added later in a ViewGroup should be visually overlaid on top of the previous View.

But there is one scene that is very special, and that is the Android TV App.

In the design of TV, it is necessary to deal with the change of the focus state of View in order to enrich the visual experience because the remote control button is needed to control.

For example, it’s common to get a focus ItemView and highlight it, zoom in and add a shadow.

Then this brings a problem. Normally, we use RecyclerView to realize the list effect. When the distance between items is too small, a single Item will be enlarged and the effect of covering will appear.

As you can see in the image above, a very common focus magnification and highlighting is obscured by the View behind it.

How to solve such a situation?

Pat head thinking, since is the spacing is too small, that we widen the spacing is good. Modify a property to solve a requirement and the designer is crying at the workstation.

However, there are some design effects that are sufficiently spaced so that there is no hiding, such as some pages on Bilibili’s TV terminal.

But we can’t solve the problem by just changing the spacing, and in most cases, designers leave us with very little spacing. Most TV apps do that.

If we can’t get away with it, we need to figure out how to do it.

3. Modify the principle of drawing sequence

Changing the drawing order is actually quite simple, Android has already set aside extension points for us.

We know that a ViewGroup stores subviews through its members, the mChildren array. In the dispatchDraw() method loop where the ViewGroup draws child views, the values are not directly indexed from the mChildren array.

@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);
    // Not directly from mChildren
    final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
    if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! =null) { more |= drawChild(canvas, child, drawingTime); }}// ...
}
Copy the code

As you can see, the child is not from mChildren directly take, but by getAndVerifyPreorderedView (), its parameters in addition to the children, there will be a preorderedList ArrayList, And the index of the child 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;
}
Copy the code

In this case, if preorderedList is not empty, the child View is fetched from it, otherwise it is fetched from children.

Going back to dispatchDraw(), the preorderedList key list used here, from buildOrderedChildList(), In a method through getAndVerifyPreorderedIndex () to obtain corresponding index of View, this method requires a Boolean type customOrder, indicates that whether the custom order is needed.

ArrayList<View> buildOrderedChildList(a) {
  // ...
  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;
}
Copy the code

BuildOrderedChildList () has the logic to adjust the children order along the Z-axis. If the z-axis values are the same, refer to the customOrder configuration.

Usually the sub-views in a ViewGroup have the same Z value, so the key parameter is the customOrder switch.

Learned from the code customOrder by isChildrenDrawingOrderEnabled () method to obtain, With the matching is setChildrenDrawingOrderEnabled () can set customOrder values.

That is, if we want to adjust the order, we can adjust it using the following steps:

  1. callsetChildrenDrawingOrderEnable(true)Enable custom drawing sequence
  2. rewritegetChildDrawingOrder()Modify the value index of a View

Four, the instance,

Finally, we will write a Demo to rewrite the RecycleView getChildDrawingOrder() method to achieve the focus of the 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);
}
Copy the code

Don’t forget to still need to call setChildrenDrawingOrderEnabled open custom drawing order (true).

At this point, when the focus is enlarged, it will not be blocked by other views.

Did this article help you? Message, forward, point good-looking is the biggest support, thank you!


Hot article recommended

  • Comics: talk about thread growth/recycle strategies in thread pools
  • OkHttp supports WebSocket, authentication/long connection keepalive
  • What can I do if TCP handshake/reclaiming fails?

“Growth”, will get the learning materials I prepared.