Multi-Column Text Displays in Android

December 31st, 2012

Recently, I was looking at creating two columns of text in an Android activity. It was not as straightforward as it first appeared to determine the number of characters that would appear in a TextView on screen but there is a simple solution. This article describes how to use the message queue of a TextView to access the on-screen dimensions in order to determine the number of characters that will fit in the TextView in your code.

In Android applications, it is a common practice to load a TextView during the onCreate call to an Activity. For example:


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView)findViewById(R.id.text);
text = “Some Text”;
tv.setText(text);
}

In most applications this works just fine. A problem does arise if you need to determine the height, in pixels, of the TextView to perform more sophisticated actions. As an example, supposed you want to have two TextViews side by side to create a multi-column text screen like in an old-fashion newspaper. The layout for this activity would be:


android:layout_width="match_parent"
android:layout_height="match_parent" >

android:id="@+id/textl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/hello_world" />

android:id="@+id/textr"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/hello_world" />


How to determine where to split up the text for the left and right TextViews? The obvious answer is to determine the height of the TextView and divide by the height of a line of text. Then you can add just enough text to the left TextView and the rest to the right TextView. Easy enough:


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tvl = (TextView)findViewById(R.id.textl);
final TextView tvr = (TextView)findViewById(R.id.textr);
final String text = readTextFile();

// Get number of lines of text that will fit on the screen
int linesPerScreen = tvl.getHeight()/(tvl.getLineHeight() + (int)tvl.getLineSpacingExtra());

// Measure how much text will fit across the TextView
Paint paint = tvl.getPaint();
int textWidth = paint.breakText(text, 0, text.length(),
true, tvl.getWidth(), null);

// Total amount of text to fill the TextView is
// approximately:
int totalText = textWidth * (linesPerScreen - 1);

String leftText = text.substring(0,totalText);
String rightText = text.substring(totalText,
text.length());

tvl.setText(leftText);
tvr.setText(rightText);
}

However, when you try this you find that tv.getHeight() returns 0 at this time. The reason is that the view for the activity has not been laid out yet so the TextView has no height. How to deal with this? There are several ways but the easiest is to post a Runnable to the TextView’s message queue. The Runnable will be called when after the TextView layout is complete. So our onCreate becomes:


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tvl = (TextView)findViewById(R.id.textl);
final TextView tvr = (TextView)findViewById(R.id.textr);
final String text = readTextFile();

tvl.post(new Runnable() {
@Override
public void run() {
TextMeasure(text,tvl,tvr);
}
});
}

The TextMeasure method called from the Runnable does the actual work of breaking up the text.


private void TextMeasure(String text,
TextView tvl,TextView tvr) {
// Get number of lines of text that will fit on the screen
int linesPerScreen = tvl.getHeight()/(tvl.getLineHeight() + (int)tvl.getLineSpacingExtra());

// Measure how much text will fit across the TextView
Paint paint = tvl.getPaint();
int textWidth = paint.breakText(text, 0, text.length(),
true, tvl.getWidth(), null);

// Total amount of text to fill the TextView is
// approximately:
int totalText = textWidth * linesPerScreen;

String leftText = text.substring(0,totalText);
String rightText = text.substring(totalText,
text.length());

tvl.setText(leftText);
tvr.setText(rightText);
}

The TextMeasure method is called after the TextView has been measured in the LinearLayout. So by deferring the measure of the TextView to the Runnable we have access to the height of the TextView in the layout. Since both TextViews are the same height as specified in the Layout we need only get the height of one of them to determine the number of lines per screen. The breakText method in the TextView’s Paint object will help us determine the number of characters that will fit across the TextView. So the number of total characters in the TextView is approximately textWidth * linesPerScreen.

In a real app, breaking up text is not quite this simple. You would need to account for things like newlines in the text and breaking up text on spaces between words. But I have found that posting the runnable to the TextView’s queue allows access to the information needed to do some pretty sophisticated textual displays.

Steve Smith

Steve has been a software developer for over 20 years, first developing in assembly language and Pascal on the Apple IIe. He went to RIT for Computer Engineering, decided to stay on the software side of computers, and taught himself C and C++. Steve spent many years as a Windows developer and recently learned to develop in Objective-C for Mac OSX and iOS. Before joining Accella, he was a freelance iPhone developer for 18 months. He is now working on Android development with Java.

2 Responses to “Multi-Column Text Displays in Android”

  1. robert says:

    Hello,

    nice post. I am also trying to display a text into columns in my app exactly like you, but I am kinda stuck at some point.
    Do you have a code/project sample that you could send to me please?
    also, in order to wait for the textview creation, why using “view.post” and not using an “OnGlobalLayoutListener” ?

    cheers

  2. [...] am trying to calculate a view’s visibility according to this case study, and to convert it to monodroid specific (using xamarin framework). However I [...]

Leave a Reply

Categories