Background the problem

In RecyclerView grid layout, we often encounter the situation of setting spacing for each Item, and use GridLayoutManger, as shown in the following figure:

A(0) ~ A(3) is A line in the grid. Set SpaceH for each Item, and set edgeH for each side. To achieve this situation, we usually use ItemDecoration and rewrite its getItemOffsets method to calculate the left and right margins of each Item. It’s easy to miswrite this as: (gridSize is a row with several columns)

override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {

    super.getItemOffsets(outRect, view, parent, state)
    val position = parent.getChildAdapterPosition(view)

    // Get the column number
    val column = position % gridSize
    outRect.left = if (column == 0) edgeH else spaceH / 2
    outRect.right = if (column < gridSize - 1) spaceH / 2 else edgeH
}
Copy the code

The main reason for this is that it is ok to give each Item a proper spacing between the left and right, but after running it, it will be found that the width of each Item is not equal. This is due to the principle of GridLayoutManager bisexing. The width of each Item is calculated in this way

  1. Bisect width of reyclerView to get width of each grid grideWidth = parentWidth/gridSize
  2. ChildWidth = GridWidth-outRect.left-outRect.right

Given the above calculation formula, it is easy to find that the width of items may not be equal, for example

  • A(0) = grideWidth – edgeH – spaceH / 2
  • A(1) = grideWidth – spaceH

You can see that the widths of A(0) and A(1) are equal only when edgeH = spaceH / 2, and unequal all other times.

Derivation process

So how do we do that? ChildWidth = gridWidth-outRect.left-outRect. right All that is required is that the outrect. left + outrect. right for each Item is equal.

We set the margin on the left of the NTH item as L(n) and the margin on the right as R(n), and set their sum as P. P is currently unknown, and we get the first formula

① L(n) + R(n) = p

In addition, the spacing between two items will be set when we set the grid. We set it as spaceH, so the spacing between the NTH and n+1 is composed of R(n) + L(n+1), and the second formula can be obtained

② R(n) + L(n+1) = spaceH

It’s pure math once you get these two formulas

  1. So the first one, we can enumerate all of the cases, so gridSize is the number of columns in the grid, which is definitely given
L(0) + R(0) = p
L(1) + R(1) = p
....
L(gridSize-1) + R(gridSize-1) = p
Copy the code

So if we add all of these things together, we can see that R(0) + L(1), R(1) + L(2), these are the ② equations, so we have gridSize minus 1, so we have the formula

L(0) + (gridSize - 1) * h + R(gridSize -1 ) = gridSize * p
Copy the code

And since both sides of the grid are edgeH, namely L(0) and R(Gridsize-1) are edgeH, the value of P can be calculated as

p = (2 * edgeH + (gridSize – 1) * spaceH) / gridSize

  1. We find that R(n) exists on both the left side of formula ① and ② carefully. We eliminate it by subtraction, that is, ②-①, and the following is left:
L(n+1) - L(n) = spaceH - p
Copy the code

This expression is obviously an arithmetic sequence, which has a formula and can be directly drawn to a conclusion

L(n) = L(0) + n * (spaceH – p)

Note that L(0) is edgeH, and since we started at 0, we multiplied by n

  1. And since p was figured out in the first step, L(n) is already known

L(n) = edgeH + n * (spaceH – p)

Then the formula ① and ② of R(n) pattern can be calculated.

R(n) = p – L(n)

ItemDecoration implementation

Finally, we can get something like this

class GridSpaceDecoration(
        private val gridSize: Int.private val spaceH: Int = 0.private val spaceV: Int = 0.private val edgeH: Int = 0 // The spacing between the two sides of the grid
): RecyclerView.ItemDecoration() {
    

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        super.getItemOffsets(outRect, view, parent, state)
        val position = parent.getChildAdapterPosition(view)

         // Get the column number
         val column = position % gridSize
         / / how many rows
         val row: Int = position / gridSize
         if(row ! =0) { / / set the top
             outRect.top = spaceV
         }

        // p is the spacing to be subtracted for each Item
        val  p = (2 * edgeH + (gridSize - 1) * spaceH) * 1f / gridSize
        val left = edgeH + column * (spaceH - p)
        val right = p - left

        outRect.left = Math.round(left)
        outRect.right = Math.round(right)
    }

}
Copy the code
  1. Some people might say that the distance between the two sides can be calculated by recyclerView paddingLeft and paddingRight, which is fine, but the key problem is that many times we need to implement different types of items via GridLayoutManger. Different items may need to be set by Item decoration. As for how to write multiple types, I will not elaborate here.
  2. Many articles on the Internet do not take into account the left and right margins, and there is no derivation process, are to find rules, here is mainly a mathematical way to do the derivation, record the derivation process
  3. Careful observation shows that if edgeH is larger than spaceH, some of the left and right margins of item obtained are negative, but this does not affect the final effect. This is also found by colleagues after testing, and they instinctively think that edgeH cannot be larger than spaceH…