Blog

Back to Blog

Creating custom columns with editable items in shinobigrids for Android – Part 2

Posted on 14 Oct 2015 Written by Kai Armer

In part 1 of my blog I showed you how to quickly create shinobigrids with editable cells. If you followed this blog you will recall that we added a new column which allowed us to edit the address field of our Person object. You may also recall that whilst this column served a purpose, its styling left little to be desired! In this short blog I will show you how to fix that and get our new column looking more like part of our grid.

If you haven’t yet had a look at part 1 of this blog, it would be a good idea to check it out, as understanding styling will be much easier.

Let’s Add Some Style!

When comparing our new column to its stablemates, you will spot a few things wrong with the style. The font and size of the text does not match, likewise the background and alternate background colors are wrong. Let’s fix that by taking a look at our EditTextColumnSpec class. At a high level, the EditTextColumnSpec will initially set up style holder objects with the various values needed to style the Views within the Column. The style properties are then applied to the Views as they are bound to their data. Let’s code!

We first need to make some declarations:

// TextStyle indices
private static final int NORMAL = 0;
private static final int SANS = 1;
private static final int SERIF = 2;
private static final int MONOSPACE = 3;

// Defaults to use if values cannot be found in the given grid theme (i.e. last resort)
private static final float DEFAULT_TEXT_SIZE_SP = 18.0f;
private static final float DEFAULT_HEADER_TEXT_SIZE_SP = 20.0f;
private static final int INVALID_RESOURCE_ID = 0;
private final CharSequence columnTitle;
private WritablePropertyBinder<CharSequence> propertyBinder;
private TextColumnStyle defaultStyle;
private TextColumnStyle alternateStyle;
private TextColumnStyle headerStyle;
private TextColumnStyle selectedStyle;

You’ll notice that we use the TextColumnStyle object to hold our style properties for each type of style.

Next we need to initialize these in our constructor:

public EditTextColumnSpec(CharSequence columnTitle, WritablePropertyBinder<CharSequence> propertyBinder) {

    if (propertyBinder == null) {
        throw new IllegalArgumentException("The propertyBinder parameter cannot be null");
    }

    this.columnTitle = columnTitle;
    this.propertyBinder = propertyBinder;

    defaultStyle = new TextColumnStyle();
    alternateStyle = new TextColumnStyle();
    headerStyle = new TextColumnStyle();
    selectedStyle = new TextColumnStyle();

}

The next step is to populate these holder objects with the various style properties which we are going to use. We will retrieve these properties from the built in themes provided with shinobigrids but you can certainly use your own if you wish. In many cases you will need a Context to retrieve these properties and as such, onColumnAdded is a good place for this work:

@Override
public void onColumnAdded(Context context) {
    TypedArray styledAttributes = 
            context.obtainStyledAttributes(com.shinobicontrols.grids.R.styleable.ShinobiGridTheme);

    defaultStyle.setBackgroundColor(
            styledAttributes.getColor(
                    com.shinobicontrols.grids.R.styleable.ShinobiGridTheme_sg_itemBackgroundColor, 
                    Color.TRANSPARENT));

    //Apply alternate style background color using sg_itemAlternateBackgroundColor
    alternateStyle.setBackgroundColor(...
    //Apply header style background color using sg_headerItemBackgroundColor
    headerStyle.setBackgroundColor(...
    //Apply selected style background color using sg_selectedItemBackgroundColor
    selectedStyle.setBackgroundColor(...

    defaultStyle.setGravity(retrieveGravity(
            context, styledAttributes, 
                    com.shinobicontrols.grids.R.styleable.ShinobiGridTheme_sg_itemGravityStyle));
    //Apply alternate style gravity using sg_itemAlternateGravityStyle
    alternateStyle.setGravity(...
    //Apply header style gravity using sg_headerItemGravityStyle
    headerStyle.setGravity(...
    //Apply selected style gravity using sg_selectedItemGravityStyle
    selectedStyle.setGravity(...
...

Here as you can see we populate the holder objects with properties for background color, gravity and default text size (you can find the complete code for this on github). We then use helper methods to set up the specific size, color and typeface for the text which will be shown on our editable column:

private void setTextAppearanceProperties(Context context, int textAppearanceResId,
            TextColumnStyle textColumnStyle) {
    int[] attrs = {android.R.attr.textSize, android.R.attr.textColor,
            android.R.attr.textStyle, android.R.attr.typeface};
    Arrays.sort(attrs);

    TypedArray textAppearanceAttributes = context.obtainStyledAttributes(textAppearanceResId,
            attrs);

    String familyName = null;
    int styleIndex = NORMAL;
    int typefaceIndex = Typeface.NORMAL;

    int attrCount = textAppearanceAttributes.getIndexCount();
    for (int i = 0; i < attrCount; i++) {
        int indexIntoAttrs = textAppearanceAttributes.getIndex(i);

        switch (attrs[indexIntoAttrs]) {
            case android.R.attr.textSize: {
                textColumnStyle.setTextSize(textAppearanceAttributes.getDimension(
                        indexIntoAttrs, textColumnStyle.getTextSize()));
                break;
            }...

To those of you who are unfamiliar with technique of applying theme-based styling – this is not a concept unique to shinobigrids. The code above demonstrates the standard Android pattern for performing theme-based styling. If you would like to learn more about themes in Android, a good place to start is here.

What a Bind

With the TextColumnStyle holder objects now populated, all that is left to do is apply these styles to the Views as they are bound to the data. Let’s revisit our onBindViewHolder method:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int rowIndex) {
    QuickStartActivity.EditTextViewHolder editTextViewHolder =
            (QuickStartActivity.EditTextViewHolder) holder;
    CharSequence text = propertyBinder.bind(rowIndex);
    TextColumnStyle textColumnStyle;
    if (rowIndex % 2 == 0) {
        textColumnStyle = defaultStyle;
    } else {
        textColumnStyle = alternateStyle;
    }

    editTextViewHolder.editText.setBackgroundColor(textColumnStyle.getBackgroundColor());
    editTextViewHolder.editText.setGravity(textColumnStyle.getGravity());
    editTextViewHolder.editText.setTextColor(textColumnStyle.getTextColor());
    editTextViewHolder.editText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
            textColumnStyle.getTextSize());
    editTextViewHolder.editText.setTypeface(textColumnStyle.getTypeface());
    editTextViewHolder.editText.setAlpha(textColumnStyle.getAlpha());

    //Prevent old watcher firing upon initial load
    editTextViewHolder.setWatcher(null);
    editTextViewHolder.editText.setText(text);

    //save back edited data
    editTextViewHolder.setWatcher(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            propertyBinder.write(s, rowIndex);
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });
}

We need to do something similar for the header row, which we do in the onBindHeaderViewHolder method (you can find this method on github).

Let’s Try It

If you build and run your application, you should now see a much nicer looking editable column:

styled-editable-grid

Some of the keen eyed may notice that the gravity of the EditTexts in our editable column is such that the vertical position of the text is slightly greater than that of the non-editable fields. You may also notice that in styling our EditText we have lost the horizontal line which indicates to the user that the field is editable. Both of these factors are due to the internal functions of the EditText class. I won’t be covering this as it is outside the scope of this blog, but from a UI perspective it would make sense to provide the user with some clue of the editable nature of the such fields.

Taking Things a Little Further

As you have just seen, the various styling properties which we apply to our editable cells are retrieved from the built in themes provided with shinobigrids. This is quite nice, as any theme changes will be applied to our editable column. What if we wish to change our styling at run time instead? In this case you could expose the different TextColumnStyle objects used within the EditTextColumnSpec allowing them to be modified. You would need to ensure that any styling changes made would trigger a redraw of the ShinobiGridView, so that these changes could be seen by the user.

A way to achieve this is to have the EditTextColumnSpec class implement the OnStyleChangedListener interface. This interface provides a single method, onStyleChanged which you would use to tell the ShinobiGridView it needs to redraw. If you have read part 1 of this blog you will recall we briefly mentioned the Column.Callback class. This callback provides a useful method for us to inform the ShinobiGridView of changes – notifyDataSetChanged.

Wrapping things up

In the second part of this short blog we built upon the editable grid which we created in the first part. We worked with our EditTextColumnSpec to set several styling properties, such as background color and typeface. The result of this is that the Views within our editable column have a similar look and feel to their non-editable neighbours.

I hope that you have enjoyed reading this blog and that it inspires some of you to use shinobigrids to help you build amazing apps. As before, you can find a working demo app on github. If you haven’t yet done so, why not download a free trial of shinobigrids.

Back to Blog