Repository navigation
Shield
The Shields are simply one hexagon centered in the parent ConstraintLayout
with six others distributed around it in a circle at 60° intervals. The radius
is determined by the shorter dimension – width or height – of the cell's
bounding rectangle, plus the desired margin between the hexagons.
fragment_shield.xml, ViewShield.kt
The View version is initialized from layout XML with pre-computed measures to let us see an accurate preview.
<androidx.constraintlayout.widget.ConstraintLayout …>
<View
android:id="@+id/center"
android:layout_width="58dp"
android:layout_height="50dp"
android:background="@drawable/hexagon"
android:backgroundTint="#CCCCCC"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/outer_red"
android:layout_width="58dp"
android:layout_height="50dp"
android:background="@drawable/hexagon"
android:backgroundTint="#FF0000"
app:layout_constraintCircle="@+id/center"
app:layout_constraintCircleAngle="0"
app:layout_constraintCircleRadius="55dp"
tools:ignore="MissingConstraints" />
…
</androidx.constraintlayout.widget.ConstraintLayout>The hexagon's longer dimension – here, the width – is calculated as 2/√3 times
the shorter height, and the radius is the shorter side plus the margin, which is
5dp here, for the total 55dp.
At runtime, the children are automatically resized to match the grid's width to
the parent's. The calculations for this are in a helper function,
hexSizeForLineCount(), which determines the specific cell size for a grid of
regular hexagons given the constraining dimension's length, the grid's relevant
line count, and the hexagons' orientation. This function and other tedious maths
are grouped together in the Calculations file.
This function defines lines as collinear cells, not necessarily contiguous ones. In this diagram from the Grid definitions section, you can see how the blue row lines' hexagons do not touch each other.
All of the library's features deal only in these collinear lines, but the
ConstraintLayout Grid examples here use "natural" rows and columns, because
they build the grids by linking neighbor cells in each row start to end, top to
bottom.
The one user option in the app for the Shields is a switch that flips the hexagon's orientation; i.e., whether a major axis is aligned horizontally or vertically.
Changing the orientation simply swaps the children's dimensions, and shifts all the angles by 30°.
The Compose version has the same setup, but obviously everything is done in
code. The Hexagon Composable works inside ConstraintLayouts to provide the
shaped backgrounds, and to get everything linked together.
val center = createRef()
Hexagon(
ref = center,
size = dpSize,
shape = shape,
backgroundColor = Color.LightGray
) {
start.linkTo(parent.start)
top.linkTo(parent.top)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
}
…
repeat(6) { index ->
Hexagon(
ref = createRef(),
size = dpSize,
shape = shape,
backgroundColor = outerColors[index]
) {
val angle = (60F * index) + if (isHorizontal) 0F else 30F
circular(other = center, angle = angle, distance = radius)
}
}The hexSizeForLineCount() is used also here to figure out the correct cell
size for the space and specs, but we have to convert most everything to Dps in
order to be able to pass it to Composables and Modifiers. Consult the code
linked above for details.