Monday, April 28, 2008

Custom views

Now that I entertained myself (and hopefully the readers of the blog as well) with custom adapters and animations, I set out to an even more adventurous exercise. In Android, entirely new view classes could be created and used in conjunction with XML layout files, similarly to built-in views. Views have their own lifecycle. Intererested readers should consult the documentation of android.view.View class. Very shortly: views may handle the event when their inflation is finished, when they are being measured by the layout manager, when they are being laid out, when their size change, when they gain or lose focus, when they are attached to and detached from a window and when the window they are attached to changes visibility. This is pretty complex so I chose an easier way to introduce custom views by extending a built-in view.

The scenario which absolutely requires custom views is the following: we have a list of alarm events and some of them requires the user's acknowledgement. There is a flashing exclamation mark beside such alarms. Like this (except that the exclamation mark is flashing:)


You can download the example program from here.

We will support the list with a custom adapter, as we have seen previously. A single row for this adapter looks like this (located under res/layout/alarm_row.xml, XML mangling because of blog engine limitation).

[?xml version="1.0" encoding="utf-8"?]
[LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"]

[TextView android:id="@+id/alarmtext"
android:textSize="16px"
android:layout_width="280px"
android:layout_height="wrap_content"/]

[aexp.customview.AlarmingView
android:id="@+id/alarming"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/]
[/LinearLayout]

TextView is our old friend but the second view in the row is our own. If you check the aexp.customview.AlarmingView class, you will see that it is a descendant of ImageView. We still have to define three constructors (one for the creation from code, 2 others for inflating from XML) but the rest of the view lifecycle handling is inherited from ImageView. The flashing effect is achieved by periodically updating the drawable resource displayed by the underlying ImageView.

There is one point worth highlighting. There is a single private class handling the periodic flashing (coded as singleton for all AlarmingView instances to save resources and achieve synchronous flashing) but that class does not use the java.lang.Thread mechanism. If you code it that way (I did first :-( ), the application is shut down with an error message that views are not thread-safe and can be manipulated only from the thread that created them. Hence, we create the private class instance when the first AnimatedView is created and therefore the Handler instance in the private class will also be created in the context of the thread that creates the AnimatedView in the first place. Then we request this Handler instance to update the state of our views periodically.

6 comments:

Kovacs said...

Hi!
I did not tried your method, but if you are manipulating your views from another thread, use context.runOnUIThread(new Runnable() {
public void run() {
manipulate here without a problem
}
});

regards,

Gabor Paller said...

Kovacs, the example program uses a Handler created in the application thread's context and invokes postDelayed() to execute tasks in the context of the application thread. There is no problem here, runOnUIThread() does the same internally.

Baker said...

Hi, I have a similar solution but I extend a View. Very simple implementation. When I try to include it in my layout-file, I get an InflateException :/ Any ideas why? Appreciate any help! Mattias

[com.myapp.test.MyView
android:id="@+id/MyView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/]

Baker said...

Hi again, I just realized what it was: I missed the part about implementing all three constructors of View. Thanks for a great tutorial!

jl said...

Hello, thanks for the demonstration. I'm curious if I don't want to extend a View and create a programmatic class for the customized view, is there any other ways to do it?

Thank you!

Gabor Paller said...

jl, it is not easy to find a use case that cannot be satisfied with existing Android view descendant classes (buttons, lists, etc.). If you run into any such case, however, I cannot see how you can avoid subclassing a view class.