Control Overview

The ShinobiGridView provides a powerful and flexible way to represent data in tabular form. The grid comes with a number of interactions such as horizontal and vertical scrolling and drag-and-drop reordering that are optimized for touch interfaces. The highly performant nature of the grid lends itself to the mobile environment and provides a super smooth user experience.

This section describes the features and concepts which constitute a grid.

The Anatomy of the Grid

The ShinobiGridView is composed of rows which run horizontally and columns which run vertically.

Each column can have a header, which typically indicates the nature of the data displayed within that column.

In order to use the ShinobiGridView you will typically perform the following tasks:

  • Set an AdapterSpec on the ShinobiGridView to define the number of rows the grid will contain.
  • Add some columns to the ShinobiGridView – these define the columns that are rendered by the grid at runtime.

Item Creation and Recycling

The ShinobiGridView is capable of rendering grids that are composed of thousands of rows. One of the key concepts behind the ShinobiGridView that supports this is the way that it dynamically creates the various elements of the UI, pooling and recycling these elements as required.

The ShinobiGridView only creates the elements that it requires to render the data that is currently visible on the device’s screen. As an example, when the user scrolls the grid vertically, the items that scroll beyond the upper bounds of the screen are not destroyed, instead these items are re-positioned at the bottom of the scrolling container so that they come into view once more, as illustrated below:

Item recycling in a ShinobiGridView

The same principle applies to vertical or diagonal scrolling.

The ShinobiGridView handles the process of polling and recycling items, and with typical usages you don’t need to be concerned with the way that items are recycled. However, you should keep this process in mind – if for example, you make changes to the state of a visible item; you cannot expect the state to be the same if the item is scrolled off-screen then back on again. Indeed, you cannot expect it to be the same item!

Column Configuration

In order to render your data within the ShinobiGridView you need to add one or more Column objects, which are defined by the ColumnSpec class. One of the most important functions of the ColumnSpec is to specify the type of item to be used at each row position and how data is bound to this item. The different types of items are registered with the ShinobiGridView and are identified by a unique integer value, an itemViewType. Each item is provided as a ViewHolder and be cast appropriately depending on the requested itemViewType. By default a number of built-in itemViewTypes and corresponding ViewHolder subclasses are automatically registered with the grid but it is possible to both add your own custom ones as well as override the existing itemViewTypes to correspond to a different ViewHolder subclass.

The ViewHolder will contain a View that is a visual representation of the item in the grid. This View of course can be a ViewGroup which contains child Views if a more complex layout is required.

Each ColumnSpec also specifies whether the Column should have a header and, similar to above, what itemViewType the header should be as well as how any data is bound to the item.

Styling

The ShinobiGridView has a powerful and flexible mechanism for customizing its visual appearance. It offers a number of ways to specify the style for each of its items allowing you to, for example, easily create a grid with alternating background colors for each row, or a grid where the values in a single column are highlighted in bold.

Theme Styling

The lowest level of styling precedence comes from a ShinobiGridTheme, defined in XML, that has been applied to the ShinobiGridView. By default the ShinobiGridTheme.Light is applied to a ShinobiGridView. If you wish you can define your own ShinobiGridTheme in XML and apply it to the grid. In code you do this by using the appropriate constructor with the resource ID of your custom ShinobiGridTheme:

Java

ShinobiGridView shinobiGridView = new ShinobiGridView(context, R.style.MyGridTheme);

C#

ShinobiGridView shinobiGridView = new ShinobiGridView(context, Resource.Style.MyGridTheme);

In XML you use the sg_gridTheme attribute in your layout XML file:

<com.shinobicontrols.grids.core.ShinobiGridView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:shinobi="http://schemas.android.com/apk/res-auto"
    android:id="@+id/grid"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    shinobi:sg_gridTheme="@style/MyGridTheme"/>

Note the extra namespace declaration (xmlns:shinobi) which enables you to use the XML attributes defined in the shinobigrids library. The above assumes that the ShinobiGridView is the only View in your layout but if not make sure the namespace declaration is in the top-level element.

To define your own custom ShinobiGridTheme, in your res/values/styles.xml file simply create a new style that defines values for the various styleable attributes of ShinobiGridTheme. For a full list of styleable attributes see R.styleable.ShinobiGridTheme. There is no requirement to but if you would like you can set your theme’s parent to the ShinobiGridTheme.Light provided in the library. This way you inherit all of that theme’s values, allowing you to override just the ones you need to. For example, to create a custom ShinobiGridTheme that uses ShinobiGridTheme.Light as a base but changes the alternate background color for items in the grid, you would do the following:

<style name="MyGridTheme" parent="ShinobiGridTheme.Light">
    <item name="sg_itemAlternateBackgroundColor">#ffccbc</item>
</style>

Note: in the style definition you do not need to use a namespace on the ShinobiGridTheme attributes.

Our pre-defined ItemViewHolderCreator implementations make use of the various values defined in the ShinobiGridTheme when creating their ViewHolder. For example, the TextViewHolder.Creator will style its TextViewHolder’s text according to the text appearance specified by the sg_itemTextAppearance attribute. When creating your own custom ItemViewHolderCreators and ViewHolders you can also make use of the values in the current ShinobiGridTheme by following the usual procedure in the Android framework.

As an example, you may wish to set the background color of your View in the createItemViewHolder(ViewGroup parent) method of your custom ItemViewHolderCreator. First, obtain the Context from the given parent:

Java

Context context = parent.getContext();

C#

Context context = parent.Context;

From that you can obtain a TypedArray of styled attributes, that is values for all the attributes in the ShinobiGridTheme:

Java

TypedArray styledAttributes = context.obtainStyledAttributes(R.styleable.ShinobiGridTheme);

C#

TypedArray styledAttributes = context.ObtainStyledAttributes(Resource.Styleable.ShinobiGridTheme);

Then, to get the item background color in the current style, you call the following:

Java

int backgroundColor = styledAttributes.getColor(R.styleable.ShinobiGridTheme_sg_itemBackgroundColor, Color.TRANSPARENT);

C#

int backgroundColorARGB = styledAttributes.GetColor(Resource.Styleable.ShinobiGridTheme_sg_itemBackgroundColor, Color.Transparent);
Color backgroundColor = new Color(backgroundColorARGB);

Here we are specifying Color.TRANSPARENT to be used if no background color is found in the theme. You can now set the background color on whatever View your ViewHolder has.

Finally, it’s important to remember to recycle the TypedArray once you have finished with it:

Java

styledAttributes.recycle();

C#

styledAttributes.Recycle();

Column and Individual Item Styling

The ColumnSpec interface defines certain properties and behaviors of Column objects, including instructions on how to bind ViewHolders for the items within it. When providing your own implementation of ColumnSpec you may choose to use values from the current ShinobiGridTheme to set the visual appearance of the items.

You obtain the values from the ShinobiGridTheme in the same way as detailed above. However, you are only given access to a Context when the ColumnSpec’s Column is added to a ShinobiGridView. When this occurs the onColumnAdded(Context) method in your ColumnSpec is called giving you the opportunity to access the styled attributes and save various values for later use during binding.

The onBindViewHolder(RecyclerView.ViewHolder holder, int rowIndex) method provides both the holder, containing the View(s) for an item, and the rowIndex the holder is on. This gives you the flexibility to style the items at an individual level as well as at a column level. So, for example, you may wish to set the background color of all the Views in a Column to be the value of the sg_itemBackgroundColor attribute in the current ShinobiGridTheme. If however you wanted to alternate the background color you can use the rowIndex to determine whether the item is on an even or odd row and set the color accordingly. In fact the TextColumnSpec, which is a convenience implementation provided by the library for working with textual data, does this very thing using the values of sg_itemBackgroundColor and sg_alternateItemBackgroundColor to color the backgrounds of the TextViews in its Column.

Additionally, our TextColumnSpec has some API on it to enable you to dynamically change the styles and various styling options allowing finer-grained styling control at the column level as opposed to the grid level. There are four different TextColumnStyle fields which handle the getting and setting of style properties such as text size or background color.

For example if you wish to make the text of all items (other than header items) within a particular column bold, you could write code similar to the following:

Java

Column column = shinobiGridView.getColumns().get(columnIndex);
TextColumnSpec textColumnSpec = (TextColumnSpec) column.getColumnSpec();
textColumnSpec.getDefaultStyle().setTypeface(Typeface.DEFAULT_BOLD);
textColumnSpec.getAlternateStyle().setTypeface(Typeface.DEFAULT_BOLD);

C#

Column column = shinobiGridView.Columns[columnIndex];
TextColumnSpec textColumnSpec = (TextColumnSpec) column.ColumnSpec;
textColumnSpec.DefaultStyle.Typeface = Typeface.DefaultBold;
textColumnSpec.AlternateStyle.Typeface = Typeface.DefaultBold;

When implementing ColumnSpec yourself, if you are providing similar API you must call the notifyDataSetChanged method on your ColumnSpec’s Callback object in order for the changes to become visible.

Styling for column headers is achieved in much the same way - by using the onBindHeaderViewHolder(RecyclerView.ViewHolder holder) method in your own ColumnSpec implementations or by using the header specific API when using the TextColumnSpec.

Gridlines

Gridlines are added to your ShinobiGridView by means of ItemDecoration objects. An ItemDecoration is a standard Android RecyclerView concept so you may already be familiar with it but you can find further information on the Android developer pages. Essentially they enable you to add drawings and layout offset to specific items in your grid allowing you to add things such as dividing lines.

The ShinobiGridView lets you add, separately, a number of ItemDecoration objects to be used for the header row and for the main body of the data in your grid. The ItemDecoration objects will be drawn in the order they are added. Out-of-the-box the shinobigrids library provides two implementations you can use in your grid: HeaderItemDecoration and DataItemDecoration, and by default one instance of each will be added when the ShinobiGridView is created. These two ItemDecoration sub-classes have API on them that enable you to easily adjust properties of the gridlines, such as their color, thickness and the order in which they are drawn (horizontal or vertical lines first), as well as the margin around items.

For example, to change the color of the horizontal gridlines to blue you do the following:

Java

DataItemDecoration dataItemDecoration = shinobiGridView.getDefaultDataItemDecoration();
dataItemDecoration.setHorizontalLineColor(Color.BLUE);
shinobiGridView.getDataRecyclerView().invalidateItemDecorations();

C#

DataItemDecoration dataItemDecoration = shinobiGridView.DefaultDataItemDecoration;
dataItemDecoration.HorizontalLineColor = Color.Blue;
shinobiGridView.DataRecyclerView.InvalidateItemDecorations();

This gets hold of the first (and only unless you have added or removed more) ItemDecoration for the main body of data in your grid. We then make the required change to the horizontal line color, and finally tell the data GridRecyclerView to redraw its items so that the change takes effect.

You can of course remove the default ItemDecorations and add your own implementations if you require something more specialized. The various methods in the ItemDecoration class have a RecyclerView as a parameter. In some situations you may need to know certain information about the ShinobiGridView, for example, whether it has a header row or not to help you decide how to draw the top gridline. You can safely cast these given RecylerViews to a GridRecyclerView; the GridRecyclerView implements the ShinobiGridViewAccessProvider interface thereby giving you access to the parent ShinobiGridView.

To see an example of grid styling in action, take a look at our How-To: Style the grid guide.

Selection

The ShinobiGridView supports the selection of individual items and as such it provides several convenient API methods to facilitate this.

To use selection you will first need to set an appropriate SelectionMode on the ShinobiGridView. We provide you with a couple of convenience implementations - SingleSelectionMode and MultiSelectionMode. As the names suggest SingleSelectionMode allows only one item to be selected at any given time whereas MultiSelectionMode allows multiple items to be selected. By default, an instance of MultiSelectionMode is set on the ShinobiGridView. You can of course implement your own SelectionMode - you may for example require a custom action, such as setting a limit on how many items may be selected at any one time.

With the SelectionMode set, working with selection on the ShinobiGridView is really simple. For example, to select the sixth item in the data GridRecyclerView (zero-based indexing), you only need to write one line of code, similar to the following:

Java

shinobiGridView.getDataRecyclerView().setItemSelected(5, true);

C#

shinobiGridView.DataRecyclerView.SetItemSelected(5, true);

The first parameter in this method represents the position of the item with which we wish to change the selection state. As such you may find the following method useful, to help you locate the correct position of your item:

Java

shinobiGridView.getDataRecyclerView().getPosition(columnIndex, rowIndex);

C#

shinobiGridView.DataRecyclerView.GetPosition(columnIndex, rowIndex);

In a similar fashion, to set the selection state of the same item to deselected, you would write code something like:

Java

shinobiGridView.getDataRecyclerView().setItemSelected(5, false);

C#

shinobiGridView.DataRecyclerView.SetItemSelected(5, false);

You may also wish to know the positions of the currently selected items. One line of code will take care of this:

Java

int[] currentSelectedItems = shinobiGridView.getDataRecyclerView().getSelectedItemPositions();

C#

int[] currentSelectedItems = shinobiGridView.DataRecyclerView.GetSelectedItemPositions();

Note: If the dimensions of the ShinobiGridView are changed (such as adding or removing a row or column), the currently stored list of selected item positions will be cleared.

Gestures

By default the ShinobiGridView recognizes swiping gestures to mean panning the grid horizontally and/or vertically. It also recognizes single tap gestures to mean toggling the selection state of the data items on the grid.

In order to react to item touches then you will need to add an OnItemTouchListener to the ShinobiGridView’s header and data GridRecyclerViews. An OnItemTouchListener is a standard Android RecyclerView concept so you may already be familiar with it but you can find further information on the Android developer pages. Essentially they enable you to intercept touch events and handle these events as you see fit.

Separate instances of an OnItemTouchListener can be added to both GridRecyclerViews to detect gestures on data and on column header items. To add an OnItemTouchListener to listen for gestures on data items you would obtain the data GridRecyclerView from the ShinobiGridView by using the getDataRecyclerView() method, and then add the listener via the addOnItemTouchListener method. Similarly, to add an OnItemTouchListener to listen for gestures on column header items you would obtain the header GridRecyclerView instead by using the getHeaderRecyclerView() method.

Out-of-the-box we provide an ItemSingleTapDetector which detects single touches on items and handles them according to your implementation. You provide the implementation to handle this gesture by setting a Callback on the ItemSingleTapDetector which implements the onItemSingleTap method. This method provides you with the ViewHolder and an integer representing the position of the item that has been clicked. The GridRecyclerView has several utility methods on it to help work with conversions from a position to a row or column index or vice versa. Specifically, they are the getColumnIndexForPosition(int position), getRowIndexForPosition(int position) and getPosition(int columnIndex, int rowIndex) methods.

For example, to display the item’s column and row index:

Java

ItemSingleTapDetector itemSingleTapDetector = new ItemSingleTapDetector(this);
itemSingleTapDetector.setChildViewFinder(new NearestInTouchBoundsChildViewFinder());
itemSingleTapDetector.setCallback(new ItemSingleTapDetector.Callback() {
    @Override
    public void onItemSingleTap(RecyclerView.ViewHolder holder, int position) {
        GridRecyclerView dataRecyclerView = shinobiGridView.getDataRecyclerView();
        int colIndex = dataRecyclerView.getColumnIndexForPosition(position);
        int rowIndex = dataRecyclerView.getRowIndexForPosition(position);
        Toast.makeText(getApplicationContext(), "Item with column index: " + colIndex + " and row index: " + rowIndex + " has been clicked!", Toast.LENGTH_SHORT).show();
    }
});
shinobiGridView.getDataRecyclerView().addOnItemTouchListener(itemSingleTapDetector);

C#

ItemSingleTapDetector itemSingleTapDetector = new ItemSingleTapDetector(this);
itemSingleTapDetector.SetChildViewFinder(new NearestInTouchBoundsChildViewFinder());
itemSingleTapDetector.SetCallback(new Callback(shinobiGridView));
shinobiGridView.DataRecyclerView.AddOnItemTouchListener(itemSingleTapDetector);
[...]

class Callback : Java.Lang.Object, ItemSingleTapDetector.ICallback
{
    readonly ShinobiGridView shinobiGridView;

    public Callback(ShinobiGridView shinobiGridView)
    {
       this.shinobiGridView = shinobiGridView;
    }

    public void OnItemSingleTap(RecyclerView.ViewHolder holder, int position)
    {
       GridRecyclerView dataRecyclerView = shinobiGridView.DataRecyclerView;
       int colIndex = dataRecyclerView.GetColumnIndexForPosition(position);
       int rowIndex = dataRecyclerView.GetRowIndexForPosition(position);
       Toast.MakeText(shinobiGridView.Context, "Item with column index: " + colIndex + " and row index: " + rowIndex + " has been clicked!", ToastLength.Short).Show();
    }
}

There is no requirement to use our provided implementation and if you need more flexibility then you can add your own to either GridRecyclerView.

Note: The default OnItemTouchListener on the ShinobiGridView listens for gesture events on the data items. There is no default OnItemTouchListener to listen for gestures on the column header items. You can of course add your own if you require.