Custom Controls / Spans / StyleSpan / Themes

Custom Controls – Part 1

In a recent article we looked at how to apply themes to Android apps. In order to further explore how themes work, it is necessary to try to understand precisely how styles and themes are applied to controls. To this end, we will look at building a simple custom control, and then learn how to apply styles and themes to it.

Let’s begin by creating a new Android 2.3.3 project named CustomControl with a package name of com.stylingandroid.customcontrol, and create an activity named MainActivity.

First let’s change res/values/strings.xml to have a different “hello” string:



    This is some text to demonstrate HighlightedTextView and show how it works by highlighting particular words.
    CustomControl

Next we’re going to create a custom control which will extend TextView and allow us to automatically highlight any text which matches a specific regular expression. Create a new class in com.stylingandroid.customcontrol.HighlightedTextView which extends android.widget.TextView, add some attributes for a regex pattern and text style, and then the necessary getters and setters for these attribute:

package com.stylingandroid.customcontrol;

import java.util.regex.Pattern;

import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.widget.TextView;

public class HighlightedTextView extends TextView
{
	private Pattern pattern;
	private int highlightStyle = Typeface.BOLD;
	
	public HighlightedTextView( Context context )
	{
		this( context, null );
	}

	public HighlightedTextView( Context context, AttributeSet attrs )
	{
		this( context, attrs, 0 );
	}

	public HighlightedTextView( Context context, AttributeSet attrs, int defStyle )
	{
		super( context, attrs, defStyle );
	}

	public String getPattern()
	{
		return pattern.pattern();
	}

	public void setPattern( String pattern )
	{
		this.pattern = Pattern.compile( pattern );
	}

	public int getHighlightStyle()
	{
		return highlightStyle;
	}

	public void setHighlightStyle( int highlightStyle )
	{
		this.highlightStyle = highlightStyle;
	}
}

Hopefully this should all make sense. The only things to note are:

  1. We have set a default highlight style as bold.
  2. We are parsing the regex pattern without any kind of error checking. This is not a good idea, and could cause some difficult to diagnose problems in a real app, but for simplicity so that we can get to the styling stuff we’ll live with it.

The next thing that we need to do is to create a method that will create the necessary spans to highlight our text, and call it whenever anything changes which will cause the the text formatting to change:

	public void setPattern( String pattern )
	{
		this.pattern = pattern == null ? null : Pattern.compile( pattern );
		updateText( getText() );
	}
	.
	.
	.
	public void setHighlightStyle( int highlightStyle )
	{
		this.highlightStyle = highlightStyle;
		updateText( getText() );
	}
	.
	.
	.	
	public void updateText( CharSequence text )
	{
		Spannable spannable = new SpannableString( text );
		if( pattern != null )
		{
			Matcher matcher = pattern.matcher( text );
			while( matcher.find() )
			{
				int start = matcher.start();
				int end = matcher.end();
				StyleSpan span = new StyleSpan( highlightStyle );
				spannable.setSpan( span, start, end, spanned.SPAN_INCLUSIVE_INCLUSIVE );
			}
		}
		super.setText( spannable, BufferType.SPANNABLE );
	}

If you have read the Introduction to Spans article, then this should be fairly straightforward. The matcher is finding the bound of all occurrences of our regular expression within the text and applying a span which sets the highlight style to each occurrence. This method gets called whenever the pattern of highlight style changes.

Now we need to ensure that our method is triggered whenever the text changes. We can do this by overriding setText( CharSequence text, BufferType type ):

	@Override
	public void setText( CharSequence text, BufferType type )
	{
		updateText( text );
	}

It is worth noting that there is an instance where this may not get called. If you are programatically calling setText (char[] text, int start, int len), this method is bypassed. However, for simplicity and because we’ll be setting the text via an attribute in our XML layout, we’ll ignore this.

That’s our control written, the next thing we need to do is modify res/layout/main.xml to use this control:



	

All that’s left is to modify the onCreate() method in MainActivity to set a pattern:

package com.stylingandroid.customcontrol;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity
{
	@Override
	public void onCreate( Bundle savedInstanceState )
	{
		super.onCreate( savedInstanceState );
		setContentView( R.layout.main );
		
		HighlightedTextView htv = (HighlightedTextView)findViewById( R.id.HighlightedText );
		htv.setPattern( "([Hh]ighlight)" );
	}
}

This will cause our control to apply our highlight style to any text within our control which is either “Highlight” or “highlight”.

That’s our basic control written, and we can see that it works when we run our app:

Basic Control
Basic Control

That does what we want, but we can only control things programatically. In the next article we’ll look at how we can extend things so that we can control it from attributes within our XML layout.

Apologies that this article has not contained much in in the way of styling (as per the title of the blog), but hopefully it will be useful nonetheless. It is necessary for us to get a basic custom control working before we can get on to the all important styling in the next article.

As ever, the final source can be found here.

© 2011, Mark Allison. All rights reserved.

Copyright © 2011 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

4 Comments

  1. i am currently developing android UI on Tv.I want to develop screens for 720 and 1080 resolutions.How can the same layout be used for both the resolutions.
    I tried by putting dip in the screens .eg consider 2 buttons one below the other.In 720,the distance between the buttons is 36dip but in 1080 it is 48 dip.Now if I design xml using distance as 48 dip and then run the layout on a resolution of 720 the distance observed will be more between the buttons.

    is there any way in which i can give the distance such that 36 dip becomes 48 dip in 1280.Please let me know if this problem can be solved

    1. I don’t offer developer support on this site, but I’ll look at writing some future articles which will cover these issues. It may take about 4-6 weeks because I have other articles in progress at the moment. If you need more immediate support, then I would suggest going to stackoverflow.com.

      Regards,

      Mark

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.