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.

Column Width

shinobigrids for Android offers a number of ways in which the widths of columns can be set. There are firstly several options which when set, will apply to all columns in the grid. These are:

  • Auto sizing

    By default, the ShinobiGridView will apply an equal width to each column so that they all fit to the screen, without the need for scrolling.

  • Minimum column width

    When set, each column in the grid will have its width set to be at least this size. Scrolling may or may not be possible.

    Java

    shinobiGridView.setMinimumColumnWidth(100);
    

    C#

    shinobiGridView.MinimumColumnWidth = 100;
    
  • Default column width

    When set, each column in the grid will have its width set to this size. Scrolling may or may not be possible.

    Java

    shinobiGridView.setDefaultColumnWidth(100);
    

    C#

    shinobiGridView.DefaultColumnWidth = 100;
    

Tip: If both minimum and default column widths are set on the ShinobiGridView, the default takes precedence. All width values are in pixels.

It is also possible to set the width of a column individually. If your Column uses a TextColumnSpec, setting the width is simple:

Java

textColumnSpec.setWidth(100);

C#

textColumnSpec.Width = 100;

Note: Setting the width of one or more columns will trigger a full redraw of the grid.

You may of course choose to use your own custom ColumnSpec implementation with your Column. In this case, to set the column width you need to ensure the getWidth method (Width property for C#) of your ColumnSpec implementation returns the desired width, in pixels. During layout the library will query this value and layout the associated column accordingly. Returning null will cause the ShinobiGridView to apply the default, minimum or auto-sized width to the associated column depending on what has been set on the grid. There is no need to provide a setWidth method, but it is certainly an option if your use case requires it!

Tip: An individual column width setting will always take precedence over any width setting affecting all columns, such as default column width.

Row Height

shinobigrids for Android also offers control over the height which rows will adopt. The row height can be set in several ways:

  • Auto sizing

    By default, the ShinobiGridView will apply an equal height to each row so that they all fit to the screen, without the need for scrolling.

  • Minimum row height

    When set, each row in the grid will have its height set to be at least this size. Scrolling may or may not be possible.

    Java

    shinobiGridView.setMinimumRowHeight(100);
    

    C#

    shinobiGridView.MinimumRowHeight = 100;
    
  • Default row height

    When set, each row in the grid will have its height set to this size. Scrolling may or may not be possible.

    Java

    shinobiGridView.setDefaultRowHeight(100);
    

    C#

    shinobiGridView.DefaultRowHeight = 100;
    

Tip: If both minimum and default row heights are set on the ShinobiGridView, the default takes precedence. All height values are in pixels.

Note: Setting the row height will trigger a full redraw of the grid.

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, whether it be invoked via gesture, or programmatically. By default, gesture based selection, with a MultiSelectionMode is enabled on the data section of the ShinobiGridView. A single tap on a data item toggles the selected or deselected status of that item.

Programmatically setting an item to be selected is also quite straight forward. 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();

By default the ShinobiGridView is given a DefaultItemStateManager to handle the maintenance of its items’ state over the addition, removal and reordering of rows and columns. This means when such an action is performed on the ShinobiGridView, a selected item will still appear selected in its new position on the grid. It is possible to set the ItemStateManager held by the ShinobiGridView to null or to an alternative implementation of the ItemStateManager interface. Setting it to null will mean item state is not maintained at all and so after one of the actions described above has been performed on the ShinobiGridView items that were previously unselected may now be selected and vice-versa.

Tip: In addition to selected state, the DefaultItemStateManager will also maintain the hidden state of items.

Row and Column Reordering

ShinobiGridView makes it very easy for long a press gesture followed by a swipe to perform a visual row or column reorder. In each case the row or column will be visually pulled out and placed into its new position. To facilitate this the library provides a RowColumnReorderManager convenience class which does most of the heavy lifting. This class is itself an OnItemTouchListener implementation which is a standard RecyclerView concept. This will be discussed further below. Enabling this functionality thus requires very little code:

Java

RowColumnReorderManager rowColumnReorderManager = new RowColumnReorderManager(shinobiGridView, listener);
shinobiGridView.getHeaderRecyclerView().addOnItemTouchListener(rowColumnReorderManager);
shinobiGridView.getDataRecyclerView().addOnItemTouchListener(rowColumnReorderManager);

C#

RowColumnReorderManager rowColumnReorderManager = new RowColumnReorderManager(shinobiGridView, listener);
shinobiGridView.HeaderRecyclerView.AddOnItemTouchListener(rowColumnReorderManager);
shinobiGridView.DataRecyclerView.AddOnItemTouchListener(rowColumnReorderManager);

A Column is a concept unique to a ShinobiGridView and its position holds no bearing upon the data displayed within it. As such, upon completion of a visual column reorder, no further work is needed to update the position of data items. Conversely, when a visual row reorder is completed, further work is needed to update the data, to ensure that each item is moved to its new position. The primary reason for this is that in the interests of flexibility we have made no assumptions on what types of data users will store within a ShinobiGridView. We do of course provide tools to make this task much simpler. This tool takes the form of an RowReorderDetector.OnRowReorderListener which is passed to the constructor of the RowColumnReorderManager convenience class. To implement your own OnRowReorderListener you might write code something like:

Java

RowReorderDetector.OnRowReorderListener listener = new RowReorderDetector.OnRowReorderListener() {
  ...
  @Override
  public void onRowReorderEnded(int rowIndex, int endRowIndex, GridRecyclerView gridRecyclerView) {
    Person person = people.get(rowIndex);
    people.remove(person);
    people.add(endRowIndex, person);
  }
  ...
}

C#

class Listener : Java.Lang.Object, RowReorderDetector.IOnRowReorderListener
{
  ...
  public void OnRowReorderEnded(int rowIndex, int endRowIndex, GridRecyclerView gridRecyclerView)
  {
    Person person = people[rowIndex];
    people.Remove(person);
    people.Insert(endRowIndex, person);
  }
  ...
}

As you can see, in this simple example we simply remove the Person object and insert it at the new row position. Notice that this operation is performed at the end of the visual row reorder event.

Advanced Gesture Handling

As we have seen, the ShinobiGridView can easily be configured to respond to gestures, such as marking an item as selected, upon a single tap. shinobigrids for Android offers a powerful and flexible toolset which allows users to perform more advanced responses to gestures.

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.