Android View Collisions

How to try to prevent view intersection

Base Solution

Let’s create our custom ViewGroup that can handle its children like in aforementioned gif. The main 2 methods that are important for new are onMeasure and onLayout.

OnMeasure

The content of this method, will be similar to measuring FrameLayout with difference that we not need to find maximum height through all children, but sum all heights to resulting one:

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val fixedL = l + paddingLeft
val fixedT = t + paddingTop
val fixedR = r - paddingRight
val fixedB = b - paddingBottom

when(childCount) {
1 -> layoutOneChild(fixedL, fixedT, fixedR, fixedB)
2 -> layoutTwoChildren(fixedL, fixedT, fixedR, fixedB)
else -> layoutTreeChildren(fixedL, fixedT, fixedR, fixedB)
}
}
Animation of collapsing and expanding layout with 1, 2 and 3 children to see, how view deform their positions

👍Boost 1️⃣: Extrapolate it for any child count

Now our task is to extrapolate our solution and invite new generic solution for any amount of children more than 1. To do that let’s think how we can approach the final algorithm.

Di is an distance between center of View_i element and View_(i + 1) element, except View1 (top) and View4 (bottom).

Concept of algorithm

The idea is to analyze distances between views when they already placed in case, when parent is really-really big, so children can be placed on their anchors correctly, without shifting. Each D_i consists of tree values:

  • a half of height of View_(i+1) (for View_(n-1) it will be full height);
  • remained height
weight[i] = (view[i].height + view[i + 1].height) / 2
weight[0] = view[0].height + view[1].height / 2
weight[n - 2] = view[n - 2].height / 2 + view[n - 1].height
totalHeight = children.sum { it.height }
The freeHeight from the top is distributed between all elements of sortedWeights

Implementation

Let’s start with implementing steps of our meta algoritm with appropriate methods and them we will little bit optimize them by memory. So let’s go step by step:

Results

Here will be shown some results for random child count with random colors and heights:

👍Boost 2️⃣: Add offsets

left: we can do that, right: we can not do that yet

Concept

So we need to transform our task to the previous one. Suppose we now have scales and weights . If we will be able to scale down every weight by its scale we will be able to equalize every weights as they were if every scale would be equal to one (i.e in a aforementioned task). To do that we need to ensure that sum of all scales are equal to 1. In other words we need to normalize our scales.

  1. Normalize scales
  2. Adapt all weights (as we counted them earlier) with given scales
  3. Distribute freeHeights between all weights properly applying scales
  4. Build a distances
  5. Build a layout

Implementation

As the implementation will be an enhancement of previous implementation we will see pretty much same lines of code, but beside that we also introduce a new structure Bucket for convenient computing:

private class Bucket(
val filled: Float,
val scales: Float,
val position: Int
)

Results

Now let’s check what we can create with our new layout:

Afterwords

In this article we solved one interesting task. And you know what? It doesn’t matter, if such layout is not needed in ordinary app development. More interesting that we came to that solution by ourself. It means that we leveled up our brain and skills and this is good :)

Love being creative to solve some problems with an simple and elegant ways