FREE TRIAL

|

☀
☾

GridView

The GridView, like the ListView, provides a way to construct a visual representation of a data collection by creating a mapping between data objects in a data source to a scrollable collection of ItemViews and ItemVisuals. But, while the items in a ListView are arranged linearly along a single axis, the items in a GridView are arranged along two axes.

As the GridView class inherits from the ListView class, working with either is identical in many respects, so in this article we will only discuss the concepts and features specific to the GridView. For information on shared concepts that are relevant to both, like providing ItemView prefabs, DataBinders, Type-Matching, and setting a DataSource, see the ListView article.

PrimaryAxis, CrossAxis, and GridSlices

Content within a GridView is positioned along two axes: the PrimaryAxis and the CrossAxis.

Axis Description Positions
PrimaryAxis The AutoLayout axis of the GridView.UIBlock. GridSlices are positioned along this axis.
CrossAxis The AutoLayout axis of the GridView's GridSlices. ItemViews are positioned along this axis.

A GridSlice can be thought of as a "virtual" UIBlock parent for a fixed number of consecutive grid items. Depending on the PrimaryAxis and CrossAxis configuration, a GridSlice is basically a single row or column within the GridView. The number of items "virtually parented" to each GridSlice is set through the CrossAxisItemCount property on the GridView.

For example, the configuration in the following diagram is:

Property Value Outcome
PrimaryAxis Y Grid scrolls vertically.
CrossAxis X Each GridSlice is a row.
CrossAxisItemCount 4 4 items per row, meaning 4 items per GridSlice.

Configuring GridSlices

GridSlices support most UIBlock visual and layout features, effectively acting as "virtual" parents to the sets of UIBlocks they position along the CrossAxis.

There are three types of GridSlices, each with their own set of visuals to choose from based on however you want to style the rows or columns of your grid:

Type Equivalent UIBlock Type Description
GridSlice UIBlock Layout only configuration, no visuals.
GridSlice2D UIBlock2D Layouts plus 2D visual configuration including color, gradient, rounded corners, border, and shadow.
GridSlice3D UIBlock3D Layouts plus 3D visual configuration including color and rounded corners and edges.

Configuring GridSlices is accomplished by calling the SetSliceProvider method to provide the GridView with a GridSliceProviderCallback. The GridSliceProviderCallback is invoked each time a new GridSlice is loaded into view. The provided GridSlices do not need to be the same across the entire GridView, which means you can use the GridSliceProviderCallback to accomplish things like alternate the background color of rows:

// Register a slice provider
GridView.SetSliceProvider(ProvideSlice);

// ...

private void ProvideSlice(int sliceIndex, GridView gridView, ref GridSlice2D gridSlice)
{
    // Alternate the color of the rows or 
    // columns between gray and white
    if (sliceIndex % 2 == 0)
    {
        gridSlice.Color = Color.gray;
    }
    else
    {
        gridSlice.Color = Color.white;
    }
}
Warning

Percent-based Layout properties on UIBlocks parented directly to a GridView are calculated relative to their "virtual" parent GridSlice, not the GridView itself.

Dynamic Grid

The CrossAxisItemCount can also be updated at runtime, enabling data-bound UIs to better adapt to various window and/or screen sizes. The following DynamicGrid component demonstrates a simple approach to adjusting a GridView to support multiple aspect ratios.

using Nova;
using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Dynamically change the <see cref="GridView.CrossAxisItemCount"/> of a grid based 
/// on the rendered size of the <see cref="Nova.GridView"/> along its cross axis.
/// </summary>
[RequireComponent(typeof(GridView))]
public class DynamicGrid : MonoBehaviour
{
    [Tooltip("Assign different cross axis item counts based on the rendered size of the GridView.")]
    public List<GridBreakPoint> BreakPoints = new List<GridBreakPoint>()
    {
        new GridBreakPoint() {MinLength = Length.Zero, MaxLength = Length.FixedValue(100), ItemCount = 1 },
        new GridBreakPoint() {MinLength = Length.FixedValue(100), MaxLength = Length.FixedValue(250), ItemCount = 2 },
        new GridBreakPoint() {MinLength = Length.FixedValue(250), MaxLength = Length.FixedValue(500), ItemCount = 4 },
        new GridBreakPoint() {MinLength = Length.FixedValue(500), MaxLength = Length.FixedValue(float.PositiveInfinity), ItemCount = 8 },
    };

    /// <summary>
    /// The <see cref="Nova.GridView"/> attached to this component's Game Object.
    /// </summary>
    [NonSerialized]
    private GridView gridView = null;
    
    /// <summary>
    /// The <see cref="Nova.GridView"/> attached to this component's Game Object.
    /// </summary>
    public GridView GridView
    {
        get
        {
            if (gridView == null)
            {
                gridView = GetComponent<GridView>();
            }

            return gridView;
        }
    }

    void Update()
    {
        if (BreakPoints == null)
        {
            // No breakpoints
            return;
        }

        if (!GridView.CrossAxis.TryGetIndex(out int crossAxis))
        {
            // Cross axis not configured.
            return;
        }

        // Get the rendered size of the grid along the cross axis
        Length.Calculated gridSize = GridView.UIBlock.CalculatedSize[crossAxis];

        // Determine the cross axis item count based on the rendered size of the grid
        for (int i = 0; i < BreakPoints.Count; ++i)
        {
            GridBreakPoint bp = BreakPoints[i];

            if (bp.InRange(gridSize))
            {
                // gridSize is with bp.MinLength and bp.MaxLength,
                // so we update the cross axis item count and exit
                GridView.CrossAxisItemCount = bp.ItemCount;
                break;
            }
        }
    }
}

/// <summary>
/// Configure a min/max <see cref="Length"/> for a given cross axis item count.
/// </summary>
[Serializable]
public struct GridBreakPoint
{
    [Tooltip("The minimum length of the grid along the cross axis for the given item count.")]
    public Length MinLength;
    [Tooltip("The maximum length of the grid along the cross axis for the given item count.")]
    public Length MaxLength;
    [Tooltip("The cross axis item count to assign to the attached GridView when the GridView's length along the cross axis is between MinLength and MaxLength")]
    public int ItemCount;

    /// <summary>
    /// Is the calculated length within <see cref="MinLength"/> and <see cref="MaxLength"/>?
    /// </summary>
    /// <param name="length">The calculated length of the grid along the cross axis.</param>
    public bool InRange(Length.Calculated length) => LessThanOrEqual(MinLength, length) && GreaterThanOrEqual(MaxLength, length);

    /// <summary>
    /// Is the given <see cref="Length"/> configuration, <paramref name="lhs"/>,
    /// less than or equal to the currently calculated value?
    /// </summary>
    /// <param name="lhs">The configured <see cref="Length"/> to check.</param>
    /// <param name="rhs">The calculated <see cref="Length.Calculated"/> to compare against.</param>
    private static bool LessThanOrEqual(Length lhs, Length.Calculated rhs)
    {
        float calc = lhs.Type == LengthType.Value ? rhs.Value : rhs.Percent;

        return lhs.Raw <= calc;
    }

    /// <summary>
    /// Is the given <see cref="Length"/> configuration, <paramref name="lhs"/>,
    /// greater than or equal to the currently calculated value?
    /// </summary>
    /// <param name="lhs">The configured <see cref="Length"/> to check.</param>
    /// <param name="rhs">The calculated <see cref="Length.Calculated"/> to compare against.</param>
    private static bool GreaterThanOrEqual(Length lhs, Length.Calculated rhs)
    {
        float calc = lhs.Type == LengthType.Value ? rhs.Value : rhs.Percent;

        return lhs.Raw >= calc;
    }
}
☀
☾
GridView
  • PrimaryAxis, CrossAxis, and GridSlices
  • Configuring GridSlices
  • Dynamic Grid
Legal EmailContact Github
Copyright © 2022 Supernova Technologies, LLC