Skip to content
gonodono edited this page Jul 21, 2024 · 3 revisions

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.

Views

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.

Grid definition's diagram for an inset grid, showing row and column axes overlaid.

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.

Two single hexagons, one with a major axis horizontal, and the other with it vertical.

Changing the orientation simply swaps the children's dimensions, and shifts all the angles by 30°.

Compose

ComposeShield.kt

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.


Next: Grid →

Clone this wiki locally