Showing posts with label UI. Show all posts
Showing posts with label UI. Show all posts

Wednesday, January 26, 2011

Plugins with user interface

A while ago I wrote about plugins and now I received a mail from somebody asking about plugins that extend the UI of an application. From one side, these "plugins" are the cornerstone of Android application design: activity invocation can be considered a "UI plugin mechanism". But what if somebody wants to plug in UI elements into an application's screen so that the UI elements provided by the plugin appear on the application's own screen? Limited screen estate makes these sorts of plugins less relevant than in case of PC applications but there is a precedent: widgets plug into the Launcher's screen this way. I decided therefore to play with the idea and created a prototype.

Click here to download the application.

You will find 3 subdirectories in the package (respluginapp, resplugin1, resplugin2), each is a separate Android project. Compile and install respluginapp and launch the application. You will see something like this:
Now compile and install resplugin1 and resplugin2. You will notice that the screen of respluginapp dynamically changes as you install the plugin packages. Eventually it will look like this:



The prototype idea is simple. We have 4 rows on the screen. Each row has a default content (some text) but if a plugin is installed, the default row content is replaced by the UI elements provided by the plugin.

Sounds simple but it isn't.
  • Getting the layout from the newly installed plugin package and recognizing the plugin package is the easy part. The plugin package is recognized because it exposes a service with aexp.intent.action.PICK_RESPLUGIN intent action. That service will be used to handle plugin events. There is already a hack here: the name of a layout is available only to the application in the package because the resource compiler generates R.java that maps layout names to layout IDs. As the plugin host application cannot access this R.java, it has no access to layout names. Our plugin host application accesses the plugin's row layout by ID (0x7f030000 is hardcoded). This ID is allocated to the layout whose name is the first alphabetically among the layouts. As our plugins have only one layout in the package, it is not really an issue but this hack can cause quite a nightmare in larger projects.
  • The next hack happens with widget IDs. The resource compiler assigns IDs sequentially. Unfortunately it uses the same ID base for the plugin host application and the plugins so chances are that the IDs in the plugin host application and in the layouts of the plugins overlap. The plugin host application solves it by adding a row-specific offset to the IDs of the UI elements when they are loaded from the layout provided by the plugin package. But the downside is that from this moment, IDs used by the plugin host application and IDs used by the plugin will be different and the plugin host application has to constantly convert back and forth when it sends events to the plugin event handler service.
  • While the UI elements reside in the plugin host application's screen, the event handling is provided by a service exposed by the plugin. The event handling looks like the following: the event is intercepted in the plugin host application, the state of the plugin UI elements is serialized, the plugin event handler service is invoked that processes the event using the serialized plugin UI element state and spits out actions, how the plugin UI elements should be updated. Only some basic functionality is implemented in this prototype. Only button click events are intercepted and the state capture/update only works for TextViews and descendants. But you get the idea how complex it would be to do it properly. While fiddling with this part, I ran into a dead end street because I thought RemoteViews could be used to implement the serialization/deserialization. RemoteViews, however, does not support EditText so this approach had to be abandoned.
Is this something you should do? Well, I am not sure. Pulling UI events between host applications and services are not trivial and also it can be slow because by default the plugins are in a different package therefore in a different Linux process (see further explanation here). Our button click handlers probably do not consume so much CPU time but it can be worse with complex and frequent events. Also, the serialization mechanism presented here is kind of naive, check out the RemoteViews source to check, how deep you can go.

On the other hand, applications that change their screen layout when you install plugins are kind of cool as the Android widgets demonstrate. Decide yourself.

Tuesday, June 30, 2009

Custom adapter in color

I got a comment on this blog with relation to an old sin of mine, the custom adapter example program. That example was about custom views in a list adapter. The comment was innocent enough: is there a way to change the colors of that list? I thought it was a 5 minute task to answer that question, in the end I had to deal with a topic I never wanted to know about: themes.

Click here to download the example program.

It turned out that the color scheme used by ListView is eventually taken from the active theme. It is possible to change it programmatically but it is quite complicated to do so. Meanwhile, with themes, it is relatively easy. If you know the information source. The Android development guide is almost completely useless regarding themes, for example sample themes don't work. This blog entry from Brainflush is much more informative, at least its examples work. The source that helped me the most was the actual Android XML fileset that defines the themes. If you have access to the Android source tree, the Android system resources can be found under the platform\frameworks\base\core\res\res directory. The relevant files are styles.xml under the values subdirectory and the content of the drawable subdirectory.

If you check the styles.xml in the res/values directory in our example program, you will see that modifying the color scheme of the ListView is a quite complicated, multi-step process. First of all, in res/values/styles.xml, the ListView style is overridden with the android:listViewStyle property. The ListViewStyle (MyListView) in turn references a list selector (android:listSelector) that is stored in the res/drawable directory. That selector defines color schemes for all the possible state combination of the list row. To increase the fun, the resource compiler screwed up the image IDs in the R.java file if the weather icon image files were not at the beginning of the file name list, hence the bizarre names in the drawable subdirectory. Now what we need more is a transition that refers a 9-patch background image (block blue in our case) for the selected row and we are ready.

Don't say that it was not easy.

PS: I apologize for the hideous colors. :-)

Tuesday, May 13, 2008

Expandable lists

Multilevel, expandable lists are useful user interface elements and Android does support two-level expandable lists. This fact would not be worth a blog entry if there were no traps using the widget.

Click here to download the example program.

The example program implements a simple, two-level expandable list to display color shade information.



The first thing to note is that our Activity extends android.app.ExpandableListActivity. Otherwise setting up the list is pretty similar to ordinary ListActivity classes. The tricky part is the android.widget.SimpleExpandableListAdapter.

SimpleExpandableListAdapter expListAdapter =
new SimpleExpandableListAdapter(

this,

createGroupList(), // groupData describes the first-level entries

R.layout.child_row, // Layout for the first-level entries
new
String[] { "colorName" }, // Key in the groupData maps to display

new int[] { R.id.childname }, // Data under "colorName" key goes into this TextView
createChildList(), // childData describes second-level entries
R.layout.child_row, // Layout for second-level entries

new String[] { "shadeName", "rgb" }, // Keys in childData maps to display

new int[] { R.id.childname, R.id.rgb } // Data under the keys above go into these TextViews

);


First of all, this adapter requires somewhat complicated (although pretty well-documented) data structures. There are two of them, one describing the first-level group elements, the other describing the second-level string elements.

Here is the first one:
List->Map
Every map describes one first-level group element. The keys in the Map are arbitrary but are specified for SimpleExpandableListAdapter so that it can map the keys in the Map to widget IDs. In our case, the data under key name "colourName" will be set as the value of the TextView with "childname" ID.

The second one is a bit more complicated.
List->List->Map
Every entry in the first List represents a group. Entries in the second List represent entries of the group and each such entry is a Map that describes one child, similarly to the description of groups. In our case, the child Map contains two entries (keys "shadeName" and "rgb") that go to TextViews ("childname" and "rgb").

So far so good. Unfortunately, SimpleExpandableListAdapter has its tricks. First of all, the adapter takes the description of one row from a layout (child_row in our case, located under res/layout/child_row.xml). When the adapter draws the expand button onto the row, it does not consider the content of the row, it simply draws the button over the row. That's the reason child_row.xml defines generous left padding for the first TextView. While this behaviour can be considered just an oddity, the fact that SimpleExpandableListAdapter must be created with identical views for first- and second-level lines is a plain bug that existed at least since m3-rc37a. Having group- and child rows with different styles is a cool and useful feature that is supported by SimpleExpandableListAdapter but does not work.

Replace this fragment:

R.layout.child_row, // Layout for the first-level entries
new String[] { "colorName" }, // Key in the groupData maps to display

new int[] { R.id.childname }, // Data under "colorName" key goes into this TextView


with this one:

R.layout.group_row, // Layout for the first-level entries
new String[] { "colorName" }, // Key in the groupData maps to display

new int[] { R.id.groupname }, // Data under "colorName" key goes into this TextView


and you will see, how the expandable list gets confused.

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.

Tuesday, April 22, 2008

Animated views

I have to admit, I am aesthetically challenged. Creating a nice GUI is not my game and I respected those who could design such things. Because of this limitation of mine, I generally look down on glittering UIs with contempt. But even I was moved - even if only a bit - by Android's animation capabilities.

Android is able to animate any View object and each and every View has a startAnimation() method that launches animation. Animations exist as independent objects that can be applied to the Views. Animation objects can even be populated from XML resources that live in the res/anim directory.

You can download the example program from here.

Let's look at the animation file below (again, XML mangling due to the limitations of the blog engine). This file can be found as res/anim/magnify.xml in the example program bundle.

[?xml version="1.0" encoding="utf-8"?]
[scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="0.5"
android:toXScale="2.0"
android:fromYScale="0.5"
android:toYScale="2.0"
android:pivotX="0%"
android:pivotY="50%"
android:startOffset="0"
android:duration="400"
android:fillBefore="true" /]

This is a scaling animation. The attributes mean the followoing:
  • The content of the view is actually shrinken first (fromXScale, fromYScale=0.5) to half before it starts to grow to double its standard size and 4 times of the initial size (0.5->2.0). This is done both in X and Y axis.
  • The pivot point from which the object grows is 0% in the X axis. This means that the leftmost points of the object will stay in place and the object grows rightward to double size. Meanwhile, the pivot point on the Y axis is the middle of the object (50%), this means that the object will grow up and down from its center line.
  • The animation starts immediately (startOffset=0) and finishes in 400 msec.
Once we have an animation object, we can apply it to any view. In the example program, I created a simple list and applied the animation to the TextView list elements whenever one list element is selected (either by moving the selection with the arrow keys or clicking at list elements). Two points are worth noting:
  • Just because we animate, the list layout is not recalculated. Hence the generous top and bottom paddings around the list elements. There is enough space provided for the TextView to grow.
  • In the list row layout (res/layout/row.xml), the layout_width is set to fill_parent. This is seemingly random choice but actually, the program does not work well with wrap_content as layout_width. Whenever a list element is selected, its color changes to highlight. If the width is wrap_content, then the size of the TextView (hence the higlighted screen area) is just the length of the text. When the animation starts, the text grows past this size and the end of the text becomes unreadable. This is very important therefore to allow the list row to occupy the entire available width because then the entire row will be highlighted and the animation cannot grow larger than the highlighted area.
At last, two pictures about the beginning and the end of the animation.


Sunday, April 20, 2008

Custom widget adapter based on XML layout

Lex from anddev.org pointed out an annoying property of my custom Weather adapter example: the view row the Weather objects are transformed to are encoded in Java instead of being defined in XML layout resource. This makes the weather display style harder to modify and every application using the WeatherAdapter has the same weather screen layout.

You can download the example program from here.

I created a new version of WeatherAdapter that takes an additional parameter in its constructor, an ID of a composite view (a LinearLayout in our case) that has three children views having the IDs "city", "temperature" and "sky" respectively. The code is flexible enough to handle the case when one or more of these child views are missing, in this case the field will simply not set from the Weather object. Check the res/layout/weather_row.xml file for an example.

The new version is indeed more flexible which is demonstrated by the bit more complicated display of weather rows. Thanks for the feedback, Lex, it really made this example program more valuable.


Friday, April 18, 2008

Custom widget adapters

In an earlier post, I wrote about the SimpleAdapter and how SimpleAdapter allows significant flexibility when laying out list items. SimpleAdapter is great but then I became curious what it takes to write an adapter.

Adapters are simple devices. On one side of the adapter is a data structure like a Java object storing data. SimpleAdapter handles Java objects that can be meaningfully translated into Strings by invoking the objects' toString() method (every Java object supports that but for quite many of them, the toString() format is not meaningful for the end user). On the other side of the adapter, there is a View that the data structure was transformed into. That View is displayed to the user. As we use Adapters to supports list views, the Adapter handles lists of Java objects (that are eventually transformed into a list of Views).

Android's built-in adapters are sufficiently versatile but it is often handy to create a custom adapter. Let's look at the following example.



You can download the example code from here.

The example is a simple weather display. A weather entry consists of 3 data items: name of the city (String), temperature in the city in degrees (integer) and an icon showing whether the sky is sunny, overcast or it is raining. The first two entries could be handled using SimpleAdapter but the third cannot: SimpleAdapter does not handle icons. Therefore we create our own adapter, called WeatherAdapter that takes list of Weather objects storing the weather info and turns that list into Views that can be rendered as list rows.

The most important part of the trick happens in a private class in WeatherAdapter.java called WeatherAdapterView. WeatherAdapter does nothing more than manages a list of WeatherAdapterViews. WeatherAdapterView is the View the Weather data object is mapped to. It is itself a composite View, composed by a LinearLayout. The LinearLayout is set up programmatically (as opposed to an XML layout) and contains two TextViews and one ImageView. The ImageView encapsulates the icon. It is worth checking how the icon images are referenced in Weather.java: as the icons are in the res/drawable directory, R.java has integer IDs for them. The getSkyResource() method in the Weather class just returns this resource ID based on the sky member variable of the Weather class.

Sunday, March 16, 2008

My first meeting with the SimpleAdapter widget

Such a shame. Normally, Android developers have their SimpleAdapter adventures right at the beginning of their Android development career, meanwhile I met this beast just now. I wouldn't say that this UI feature is overdocumented and that there are truckloads of example programs out there (if there are, Google does not find them) so I decided to share my experiences with whoever cares to read this blog entry.

It all started with a very simple wish that I wanted to create a list like this:


Each list entry has two lines and the lines are styled differently. Fortunately, there is an example program at the ListActivity documentation page which happens not to work and takes the input behind the list from a database. So it took me some time to find my friend, the SimpleAdapter widget and to find out, how to use it.

You can download the example program from here.

The root of the problem is that each element of this list is a complex view, consisting of two TextViews (XML mangling because of blog engine limitations):

[?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="vertical"]

[TextView android:id="@+id/text1"
android:textSize="16px"
android:textStyle="bold"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/]

[TextView android:id="@+id/text2"
android:textSize="12px"
android:textStyle="italic"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/]
[/LinearLayout]

The corresponding SimpleAdapter maps an element from list of objects to this view.

SimpleAdapter notes = new SimpleAdapter(
this,
list,
R.layout.main_item_two_line_row,
new String[] { "line1","line2" },
new int[] { R.id.text1, R.id.text2 } );

Now this is not a trivial line of code. What it says is:
  • "list" is a reference to an object implementing the List interface. Each element in this list is an object implementing the Map interface. In our implementation example, the list is ArrayList and the elements in this list are HashMaps.
  • The layout describing one row in the main list is defined in layout/main_item_two_line_row.xml file.
  • In each individual HashMap, there are two key-value pairs. The keys are "line1" and "line2", the corresponding values are arbitrary objects whose toString() method yields the value displayed in the list row. In our case, the values are Strings.
  • Values stored with key "line1" will be displayed in the TextView whose id is "text1". Similarly, "line2" Map key is associated with "text2" TextView.
You can try the program following the instructions in the Start here entry. You can add items to the list using the menu.

Sunday, December 23, 2007

XML and programmatic layout

The tutorial taught me how to use XML files (under the layout directory) to define screen layouts. I was curious about the relationship of the XML layout and the Java classes like android.widget.LinearLayout. To verify it, I tried to recreate programmatically the layout of the skeleton project.

Update: download the project bundle from here.

The XML layout file of the skeleton project declares that the application's main view is controlled by a LinearLayout that occupies the entire available screen estate (fill_parent for both the layout_width and layout_height). The LinearLayout is vertical (elements are placed vertically). The layout contains just one text element that fills the entire available width (layout_width=fill_parent) but occupies just the height needed for the content (layout_height=wrap_content).

This XML file is inflated into instances of Java objects. The following code is equivalent in functionality with the XML file (except for the text of the TextView instance).

public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
TextView t = new TextView( this );
t.setText( "Hello, world, programmatic layout" );
LinearLayout.LayoutParams tlp =
new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT );
LinearLayout l = new LinearLayout( this );
l.setOrientation( LinearLayout.VERTICAL );
t.setPreferredWidth( LayoutParams.FILL_PARENT );
t.setPreferredHeight( LayoutParams.FILL_PARENT );
l.addView( t, tlp );
setContentView( l );
}

While playing with this example, I ran into an application error.



This error was a great opportunity to try out the adb logcat command which dumps the emulator's log. By browsing the blog, I was able to find the offending exception (excerpts):

E/AndroidRuntime( 634): at android.view.ViewGroup.addView(ViewGroup.java
:792)
E/AndroidRuntime( 634): at android.view.ViewGroup.addView(ViewGroup.java
:780)
E/AndroidRuntime( 634): at aexp.h2.H2.onCreate(H2.java:25)
E/AndroidRuntime( 634): at android.app.Instrumentation.callActivityOnCre
ate(Instrumentation.java:786)

Hot App Todays

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Best Buy Coupons