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;
}
}
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;
}
}