Skip to main content Skip to footer

Making the Xuni FlexGrid Adaptive

When developing mobile apps, you should keep in mind that some users will view your app on a tiny phone and others on a larger tablet. The good news is that Xuni FlexGrid was designed to handle different mobile layout scenarios, so you just need to know your options. In this blog post I will describe the challenges to creating a good mobile design with FlexGrid, and then I describe two features that help make FlexGrid more adaptive: star-sizing and row details. FlexGrid_Adaptive_RowDetail

FlexGrid Challenges for Good Mobile Design

If you’ve already decided to use FlexGrid in your mobile app then you’re clearly looking for some data grid features that go beyond a typical ListView. FlexGrid is not intended to replace the ListView; it’s a completely different control. One major difference between FlexGrid and ListView is FlexGrid has columns that grow horizontally and, as a result, can introduce horizontal scrolling. Horizontal and vertical scrolling simultaneously is considered bad practice in mobile design. Typically we reserve one direction for scrolling and the other direction for selection or some other UI action. But if you’re going for a spreadsheet-like experience then it’s probably OK. So the first question to ask yourself is do you want horizontal scrolling like a spreadsheet? And second, will your users still be able to fully use your app? If yes, then the solution is to use FlexGrid as-is and it will adapt nicely to any sized device. Horizontal scrolling is there to view any columns that don’t fit on the user’s screen. If all the columns fit the scrolling is turned off automatically.

For collection-related screens, and especially for textual information, vertically scrolling lists are often the most straightforward and familiar kind of interface.

But let’s say you are cool and hip and you want to design an interface that checks off all the mobile design best practices. You probably want to avoid horizontal scrolling because, according to the Android documentation, it’s typically not used for scrolling through textual information. You are more likely to see horizontal scrolling for media elements like a camera roll or photo gallery. We considered these challenges and designed a couple features to make adaptive FlexGrid apps easier to make.

FlexGrid Solutions for Adaptive Design

We know that vertical and horizontal scrolling simultaneously is not an ideal design in a mobile app. We shouldn’t eliminate vertical scrolling because we want our lists to be as long as necessary. But we do have options to eliminate horizontal scroll. FlexGrid offers two unique features to do this and they can help you create more adaptive displays that obey best-practices:

  1. Proportional sized columns (known as star-sizing)
  2. Collapsible row details that can include overflow column data

Star-sizing allows you to set column widths proportionally so they can stretch to fill the available space, and thus prevent horizontal scrolling. It’s useful if the number of columns can fit nicely even on the smallest form factor such as a portrait 5-inch phone. If you have a lot of columns this won’t be enough and horizontal scrolling will still be required. You can check out my previous blog post where I discuss star-sizing in greater detail. Xuni_FlexGrid_StarSizing_sm Row details is a newer feature of FlexGrid that allows you to display relational data in a collapsible detail cell below each row in the grid. You can use row details in many different ways, but one common scenario is to create an overflow cell that includes values from all columns that don’t fit nicely across the screen. FlexGrid_RowDetails_iOS1 Next, I will walk through a simple implementation of the row details feature that also uses star-sizing to create an adaptive, mobile-friendly FlexGrid app.

Row Details Example

The row details feature allows us to display additional, detail data in a collapsible cell below each row. The content is not automatically generated so you must create it yourself. This gives you complete freedom to display and format anything in the row detail cell. Consider this FlexGrid app with 7 columns displayed on a 10 inch tablet. FlexGrid_RowDetails_Droid10a Star-sizing alone is an acceptable solution for tablets of this size. When the device is rotated the 7 columns still fit nicely. But if we ran this on a smaller device the columns would become either unreadable, or horizontal scrolling would kick in (it’s a fallback feature to prevent columns from becoming way too small by going no smaller than their minimum width). After adding row details to this app, when we run it on a 7 inch tablet we are able to measure and move some columns to the row detail cell. FlexGrid_RowDetails_Droid7c And when we run this app on a 5 inch phone we can only display 3 or 4 columns nicely while the rest are moved to the row detail cell dynamically. FlexGrid_RowDetails_Droid5dThis FlexGrid is also using star-sizing. The columns that fit in the grid are all proportionally sized so that they stretch and take up the full horizontal space. It looks nice on any device! Let’s look at the code for this app. With this example I’ve written and provided a complete sample for Xamarin.Android written in C#, which you can download at the bottom of this post. I can add iOS and Xamarin.Forms samples to this post in the future, so drop a comment below if you are interested. First, let’s initialize an array of columns.


GridColumn[] colArray;  

Each FlexGrid column is programmatically added to the array - not the FlexGrid just yet. The following code goes in the OnCreated event which fires even when the user rotates the device.


// initialize new columns  
colArray = new GridColumn[7];  

GridColumn firstNameColumn = new GridColumn(this._flexGrid, "First Name", "First");  
firstNameColumn.Width = "*";  
colArray[0] = firstNameColumn;  

// repeat 6 more times  

Next, let’s measure and calculate the number of columns that will fit nicely across the screen.


// determine how many columns will nicely fit the device width  
var metrics = Resources.DisplayMetrics;  
var widthInDp = ConvertPixelsToDp(metrics.WidthPixels);  
_fitColumns = widthInDp / 100;  

On Android we have to take pixel density into consideration so it’s important to convert the display metrics into density independent pixels using a simple method like below. If you’re working in Xamarin.Forms or iOS you have it easier.


private int ConvertPixelsToDp(float pixelValue)  
{  
    var dp = (int)((pixelValue) / Resources.DisplayMetrics.Density);  
    return dp;  
}  

We now know the rounded number of columns that will fit by assuming that 100dp is a good width for each column. Next, let’s add the fit columns into FlexGrid by cycling through the array of columns.


if(_fitColumns >= colArray.Length)  
{  
    // all columns fit, no row details necessary  
    _fitColumns = colArray.Length;  
}  
else  
{  
    // TODO: initialize the row details provider  
}  

// add columns that fit  
for(int j = 0; j < _fitColumns; j++)  
{  
    this._flexGrid.Columns.Add(colArray[j]);  
}  

Notice the IF statement is just making sure our number of fit columns doesn’t exceed the actual numbers of columns. Arrays hate it when you do that. Within the ELSE statement let’s initialize the row detail provider, because if all the columns fit we don’t need the row details.


else  
{  
    // initialize the row details provider  
    \_detailsProvider = new FlexGridDetailProvider(\_flexGrid);  
    _detailsProvider.DetailVisibilityMode = DetailVisibilityMode.ExpandMulti;  
    \_detailsProvider.DetailCellCreated += \_detailsProvider_DetailCellCreated;  
}  

The FlexGridDetailProvider class is initialized by passing it the FlexGrid. There’s one useful property to set on the detail provider, DetailVisibilityMode, which has several different options:

  • ExpandMulti – multiple row details can be expanded at one time
  • ExpandSingle – only one row detail can be expanded at one time
  • Selection – if SelectionMode = Row, then selecting a row will expand the detail cell. Typically you will use this option alongside hiding the expand/collapse icon by setting ShowExpandButton = False.

Finally, let’s add the overflow columns as labels into the row detail cell. This is performed in the DetailCellCreated event. The UI generation code is the most different on each platform, but the concept is the same. Here we are generating a stacked layout and adding a label for each column that was calculated to not fit. These are known as the overflow columns.


///  

<summary>  
/// DetailCellCreated fires when the user expands a row detail cell  
/// </summary>  

private void \_detailsProvider\_DetailCellCreated(object sender, DetailCellCreatedEventArgs e)  
{  
    View detailView = e.Content;  
    if(detailView == null)  
    {  
        Customer customer = (Customer)(e.Row as GridRow).DataItem;  
        detailView = new FrameLayout(this.BaseContext);  
        detailView.SetPadding(10, 10, 10, 10);  
        LinearLayout layout = new LinearLayout(this.BaseContext);  
        layout.Orientation = Orientation.Vertical;  
        // calculate number of overflow columns and add them to the detail row view  
        var overflowColumns = colArray.Length - _fitColumns;  
        for(int j = colArray.Length - overflowColumns; j < colArray.Length; j++)  
        {  
            TextView txt = new TextView(_flexGrid.Context);  
            // reflection is used to dynamically get the Customer member that the column is bound to  
            txt.Text = colArray[j].Name + ": " + customer.GetType().InvokeMember(colArray[j].Binding, BindingFlags.GetProperty, Type.DefaultBinder, customer, null).ToString();  
            txt.TextSize = 17.0f;  
            layout.AddView(txt);  
        }  
        ((FrameLayout)detailView).AddView(layout);  
    }  
    e.Content = detailView;  
}  

A lot is going on in this code. The key and final step is to set the e.Content parameter to your custom View. In this example, reflection is used to dynamically get the Object’s member that the column is bound to. It’s just a simpler way that avoids having a bunch of IF statements or a Switch that handles the overflow status for each individual column. Also, we can more easily swap the data object (in this example it’s Customer).

Conclusion

To create a mobile-friendly FlexGrid we determined the number of columns that fit nicely and then moved the overflow fields into the row detail cell. We used star-sizing on the fit columns so they filled the width nicely as well. By using these features together we are able to prevent horizontal scrolling, which is usually bad design when used simultaneously with vertical scrolling. Of course, with row details you lose features like editing and sorting on these overflow fields. Though, you could put anything imaginable in the row detail cell so you could get back some of this functionality. It all depends on what is more important to your app. Download FlexGridAdaptive_Android.zip >>> (Xamarin.Android) I’ve provided a complete Xamarin.Android implementation of the demonstrated app. We also have a more basic row details sample in FlexGrid101 for each platform. You can browse all the Xuni samples on GitHub.

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus