Skip to content
Stanislav Georgiev edited this page Jul 27, 2020 · 3 revisions

InfiniteGridView

Wiki page for the InfiniteGridView made using Kotlin

About

The idea is to create a infinite grid, that can have a transformations applied to it and only the visible lines that are seen by the user are drawn. You would be able to set the background color, horizontal and vertical lines color and stroke width. There will be two types of lines normal and thick lines, and there will be a way to set through how many normal lines a thick line will be used. Below is the overview of the algorithms that are used in this library.

Image description

Algorithm

First after the transformation are made, the new distance between line is found. The new distance is calculated from the initial line distance multiplied by the current transformation matrix scale factor (image1).

Image description
(image1)

Calculate the new the distances.

val newDistanceBetweenHorizontalLines = distanceBetweenHorizontalLines * scale
val newDistanceBetweenVerticalLines = distanceBetweenVerticalLines * scale

Second step is to find all the parallel lines to the base lines and are limited to the canvas bound rectangle. There are two base lines AB and CD, that means there two parallel line types vertical and horizontal. Horizontal are lines parallel to line AB and, vertical are all lines parallel to line CD (image2).

Image description
(image2)


The final step is to find the line segments, those are the intersections between the parallel lines and the canvas bound rectangle(image3).

Image description
(image3)

Algorithm

Here is explanation of the algorithms and how they are used in Kotlin. First the parallel lines are separated into two groups, depending if the parallel lines are on the (negative-) or the (positve+) side of the base line. Next examples are for the vertical parallel lines only (image4).

Image description
(image4)

Here the negative and positive sides are the corresponding left and right sides from the base line CD.


We have the original base points, and we can easily find the transformed base points. And the two things we need to find out are:

  1. How many lines to create on each direction for the positive and negative lines?
  2. How many lines to skip, if the base lines are outside the canvas bound?

Image description
(image5)

Now lets look at (image5) and clear some things. The transformed base points, are A', B', C' and D'. The canvas bound rectangle is made of the points Q, R, M and N , they are constant and never change. To find how many lines to create in each direction, we need to find the maximum distance between the transformed base line C'D' and the four canvas bound points Q, R, M and N. To find the max distance for each side, we first need to know on which side each of the four corner points is associated with. To do this we first need to find the intersection points between the transformed base lines C'D' and A'B' with the horizontal bound lines Q'R' and M'N'.

The intersection points of A'B' and the top horizontal bound line QR is I, and with the bottom bound line MN is the point J. And the intersection points of C'D' and the top horizontal bound line QR is T, and with the bottom bound line MN is the point S. Those points will help us determine on which (negative-) or (positive+) side the four bound points lay on.

Now we can associate the intersection point I and J, with side, using the fact that A' is always on the negative side and B' is always on the positive side. That means if any of the intersection point is closer to A', it must be laying on the negative side, or if it is close to B' that means it must be on the positive side. For the case from (image5), this means:

  • If the distance A'I < B'I, that means I is closer to A', rather then B' => point I is on the negative side
  • If the distance A'J > B'J, that means J is closer to B', rather then A' => point J is on the positive side

Now as we know that I is on the negative side, we can make some claims for the horizontal bound line segment QR.

  • Since I.x < T.x
    => every point P lying on QR with P.x < T.x is also on the negative side.
    => every point P lying on QR with P.x > T.x is on the opposite positive side.

That means for the corner bound point Q and R:
since Q.x < T.x => point Q is on the negative side
since R.x > T.x => point R is on the positive side

Same goes for the bound line segment MN and the intersection points J and S. As J is on the positive side, that means:

  • Since J.x > S.x
    => every point P lying on MN with P.x < S.x is on the opposite negative side.
    => every point P lying on MN with P.x > S.x is also on the positive side.

That means for the corner bound point M and N:
since M.x > S.x => point M is on the positive side
since N.x > S.x => point N is on the positive side.

That way we can, figure out on which side each of the four bound corners are laying. On the example from (image5), we figure out that point: Q is on the (negative-) side, and points: R, M and N are on the (positive+) side. Now we can found the maximum distance and calculate how many parallel lines we need do draw for each direction. From our example we can see that the maximum distance for the negative side is between the bound corner point Q and the transformed base line C'D', and for the negative side the maximum distance is between the corner point N and the base line C'D'. The number of lines for each side can then be calculated using:

val totalPositiveSideLines = (maxPositiveSideDistance / transformedDistance).toInt()
val totalNegativeSideLines = (maxNegativeSideDistance / transformedDistance).toInt()

Next thing we need to figure out is the parallel line segments, those can easily be found, be generating parallel line that are distant from each other with the newly calculated distance: newDistanceBetweenVerticalLines starting from the transformed base line. This is basic math, so you can check the source code if you are interested.

And the last thing is to clip the lines with the canvas bound rectangle QRNM, to form the line segments, that are only visible on the canvas. This is done by checking each newly created parallel line, with each of the four line segments QR, RN, MN and QM*. Then determining the the intersection points, each line must have at least two intersection points. In the case of a diagonal line like QN and MR, only the first two intersection are used, and the intersection check are in the following order QR, MN and then QM, RN. That way the case with the diagonal lines are managed.

In case when the base line C'D' is outside the canvas bound, a offset is calculated, that represent the number of line to skip in order to create only the segment lines that are visible by the user (image6)

Image description
(image6)

This is done by finding the minimum distance between the transformed base line C'D' and the four canvas bound points Q, R, M and N, then we can create only the visible parallel lines (image7).

Image description
(image7)

How to use

To create a infinite grid view, we just need to create a InfiniteGridView object in the layout file using xml. Use the code below to create a infinite grid with blue background and white lines.

<com.slaviboy.infinitegridview.InfiniteGridView
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="#46ADE8"
     app:horizontalNormalLinesColor="#ffffff"
     app:horizontalThickLinesColor="#ffffff"
     app:verticalNormalLinesColor="#ffffff"
     app:verticalThickLinesColor="#ffffff" />

Here is the result of the code above.

To change the color or thickness of the lines the custom properties for the class InfiniteGridView are used.

Properties

Here is a list with all supported properties, that can be used to change lines color, stroke width and the distance between lines:

Properties Type Default Values
app:distanceBetweenVerticalLines dimension 0.04f
app:verticalLinesStrokeWidthThick float 0.006f
app:verticalLinesStrokeWidthNormal float 0.002f
app:verticalLinesThickLineIndex integer 5
app:verticalNormalLinesColor color Color.BLACK
app:verticalThickLinesColor color Color.BLACK
app:distanceBetweenHorizontalLines dimension 0.04f
app:horizontalLinesStrokeWidthThick float 0.006f
app:horizontalLinesStrokeWidthNormal float 0.002f
app:horizontalLinesThickLineIndex integer 5
app:horizontalNormalLinesColor color Color.BLACK
app:horizontalThickLinesColor color Color.BLACK

Using the properties above, the line stroke width and color can be changed. Another useful property show though how many normal lines a thick line should be drawn.

Clone this wiki locally