Exploring Android ListView

Some time ago I summarized some things I’d discovered about headers and footers on ListView in Android. Oddly enough, despite the dearth of real content there, it is consistently one of the top-viewed pages on this rather esoteric and scatter-brained blog. I think the simple reason is this: ListView is frustratingly obtuse if you try to do anything that’s not explicitly described in the docs, and because everyone needs to use Listview to display lists of information, everyone is looking for solutions to fairly common usage problems.

Recently I’ve had reason to revisit ListView’s oddities with a vengeance. I created some example code I could play with. I think this may be useful to others, so I created a github repo to share it – it will evolve as I try things out / figure things out. Let me know if it’s useful to you or if you have any better ideas for how to accomplish what I’m attempting.

Continue reading

Advertisements

Restricting text to one line with EditText/TextView

I’ve let slide my habit of writing about what I’m doing. Let’s try to pick it back up.

I spent some time style-izing some things in WhenDidI. For one thing, I wanted to restrict text fields to just one line (not allowing newlines). It’s not enough to have android:lines=”1″ on the EditText. That keeps the display to one line, but doesn’t keep the user from using newlines to create a multi-line entry. This was worth creating a style for:

    <style name=”NameEditor” parent=”@android:style/Widget.EditText”>
      <item name=”android:layout_height”>wrap_content</item>
      <item name=”android:layout_width”>fill_parent</item>
      <item name=”android:lines”>1</item>
      <item name=”android:inputType”>textCapSentences|textAutoCorrect|textAutoComplete</item>
    </style>

The inputType attribute allows many different values, so check them all out. Somewhere in there is the one that lets the on-screen keyboard suggest words while you’re typing. Anyway, the point here is – if you specify inputType but don’t include textMultiLine, the user doesn’t get to use newlines.
N.B. it can be nice to put layout_height and layout_width in a style to reduce clutter – of course this isn’t a good idea all the time.
So the user can’t enter multiline text now, but they can still enter giant amounts of text, and I’m wanting these values to display in one line in a ListView item. How do you cut text short in display? Well, there’s the android:ellipsize attribute, but this doesn’t actually seem to work. That bug report contains a workaround, however, without even using any deprecated attributes:
    <style name=”EllipseText” parent=”@android:style/Widget.TextView”>
      <item name=”android:lines”>1</item>
      <item name=”android:scrollHorizontally”>true</item>
      <item name=”android:ellipsize”>end</item>
   </style>

This does the trick, although in my scrolling ListView the ellipses seem to only show two dots instead of three. Maybe judicious use of padding / margins will fix that.

filtering listviews like the contacts app

I thought it would be nice to provide filtering capabilities in my ListViews – they could get long. It would be nice if this was kind of done automatically for you when you put android:textFilterEnabled=”true” on the ListView, but this is not the case; I suppose in the end it’s better to have to be explicit about exactly what’s being filtered and how, and this is Java after all.

Enabling a filter as above just enables the view to gather text the user types; it doesn’t actually do anything with it unless I set up the adapter to use it. I got a pointer from this thread. My ListAdapter is a subclass of SimpleCursorAdapter so all I had to do in my ListActivity is:

  adapter.setFilterQueryProvider(this);
  // implement FilterQueryProvider
  public Cursor runQuery(CharSequence constraint) {
        Cursor cur = mDba.fetchTrackers(mCurrentGroupId, constraint.toString());
        startManagingCursor(cur);
        return cur;
  }

I’m really hoping this doesn’t leak cursors or something. Anyway, having properly implemented fetchTrackers such that it returns a filtered query, that’s all there is to it – typing next now filters the list items.

Next problem: what happens when I type in a filter, select an item to start another activity, and then return? In the Contacts app (my model for this), the filter actually remains in place, which is a little odd. In my app, the filter *text* remains, but the items in the list are not filtered unless I start typing again! Very odd. I would like to either clear the filter or have the persistent filter applied when I resume the Activity.

There’s pretty much zippo on d.android.com about this. A Google search found me this interesting tidbit – not actually relevant to my problem I don’t think but it gives me a lead on something else I discovered the other day: if I used an alternate IME (say the chinese one) on a textfield, the text entered didn’t seem to get through to the OnKeyListener I set on the EditText. I’ll have to try a TextWatcher.

So anyway, my best bet is probably looking through the Contacts app – open source, right? Yay. Except I sometimes wonder if making something open source leaves less incentive to provide decent documentation. In any case, a quick look through the Contacts app didn’t yield any clues and I’m gonna have to stop for the night.

Movable list items?

Has anyone ever seen an Android app where the user can rearrange the order of items in a list? If they’re using a ListView I expect this is difficult; there’s no way to do drag. The only thing I can think of that’s even remotely like this is what the home screen does with a long-press grab and drag. Looking for a good design pattern to follow with a scrolling list.

Android: Summary of ListView header/footer findings

Note: This page gets a surprising percentage of traffic. If the subject interests you, you may find better answers at my later entry where I develop an app to try various layouts with ListView.

The issues

  • Scrolling with the list
  • Visibility
  • Ability to navigate without touchscreen (Dpad, trackball)
  • Ability to include interactive elements (text entry, buttons)
  • Doing with code vs. inside resources

Strategies

Strategies for headers and footers on ListView and pros/cons:

Static header: Element in your layout that precedes the ListView. Doesn’t scroll with list, so it takes up space. But is always visible and you can navigate to it and use interactive elements. Also can be done inside resources.

List header: Element added to the ListView with addHeaderView. Scrolls with the list, but is invisible if an empty view is being shown. Can be navigated to but interactive elements on it can’t be. Can’t be done within resources, currently must be attached within code.

Empty view: Standard ListView layout feature.  Only visible when the list is empty, and can’t be a reuse of another view in the activity (so any interactive elements have to be wired up separately from e.g. a header). Interactive elements can’t be navigated.

List footer: Element added to the ListView with addFooterView. Scrolls with the list and is visible even with an empty view. Can be navigated to but interactive elements on it can’t be, and the virtual keyboard is likely to cover the view if the list has even a few items. Can’t be done within resources, currently must be attached within code.

Static footer: Element in your layout that comes after the ListView. This is a non-starter because once your list is as long as the screen, this element can’t be scrolled to at all.

My conclusion

If you’re trying to have a nice interactive element in your list for adding list items, probably the best bet is to make it a non-interactive list header or footer that opens a dialog on click or a context menu on long-click. If you really want to avoid popping things up and want to have interactive elements, then go ahead and put them in a list header, as most everyone will be using the touch screen anyway, just be aware of the navigational issues.

Activity odds n ends, and eclipse platform source attach

It’s worth noting this bit of oddity about adding headers/footers to ListView and the ListView getting a wrapped adapter. On the other hand, he implements no empty view. I went back and looked at my reference app for a lot of how I want things to work, gtasks (wish the developer would open source it!). And wouldn’t ya know it – the text entry field that he has as the header? can’t be reached by navigation. I don’t feel so bad now :-) Though it’s still a nifty implementation I want to copy.

Another thought about having an “Add item” list header/footer – if it’s in the footer, the virtual keyboard will helpfully cover it up so the user can’t see what s/he is typing. So that’s a good argument for having it in the header. Though I think I’m gonna just try having a header element that acts like a list item and goes elsewhere to create a new item; I don’t think it’ll even require a custom adapter.

An interesting tidbit: Activity.finish() evidently is not a trap door like “exit” in C or bash; whatever method you’re in continues execution, and only when you complete whatever callback you’re in the middle of (since *everything* in an Activity is driven by some callback) does the Activity see that it’s time to leave and heads for the exits in an orderly fashion (first calling onPause() where you can helpfully check isFinishing() to perform any last-minute actions, then onStop, then onDestroy). I guess it seems obvious now that I think about it, but it was surprising at first.

Checking for isFinishing() in onPause() is probably a better way to handle the back key than onKeyDown() which gets called on every type of keypress.

I need to make a note to read this post on styles and themes more thoroughly. For now I’m just gonna use it to find out what I can put in textAppearance :-)

Ooh! A more coherent post about attaching platform source in Eclipse! It’s not trivial (which is good to know) but doesn’t look too bad. IRC folks helped me with that… damn IRC folks can be helpful! This was written quite some time ago, so I think it could use a little updating. Notably, you want to get a version of the repos that matches what you’re developing against. It’s not immediately evident which branch names to use – there doesn’t seem to be a logical scheme like “release-1.0, release 1.1, release 1.5…” When repo gets a project, it lists the branches and tags for it, which look something like this:
 * [new branch]      cupcake    -> korg/cupcake
 * [new branch]      cupcake-release -> korg/cupcake-release
 * [new branch]      donut      -> korg/donut
 * [new branch]      donut-release -> korg/donut-release
 * [new branch]      eclair     -> korg/eclair
 * [new branch]      master     -> korg/master
 * [new branch]      release-1.0 -> korg/release-1.0
 * [new tag]         android-1.0 -> android-1.0
 * [new tag]         android-1.5 -> android-1.5
 * [new tag]         android-1.5r2 -> android-1.5r2
 * [new tag]         android-1.5r3 -> android-1.5r3
 * [new tag]         android-1.5r4 -> android-1.5r4
 * [new tag]         android-1.6_r1 -> android-1.6_r1
 * [new tag]         android-1.6_r1.1 -> android-1.6_r1.1
 * [new tag]         android-1.6_r1.2 -> android-1.6_r1.2

…but often with a lot of clutter that varies from project to project. Now if you try to do repo -b with one of the tags (which seem logically named), it complains, probably because tags aren’t branches. Maybe there’s a flag to access those. But to get a branch, the way to go seems to be getting it by codename, i.e. cupcake, donut, eclair. Those are probably moving targets, while -release ones will be old. (You can’t win.)

And then when you’ve finally downloaded it, it needs moving around before you can even use it. And then there’s the poor guy saying his download never finishes. You know what? After I’ve got this figured out, maybe I’ll upload snapshots of zipped-up attachable code for each release to my free web hosting space for others to use without all the fuss :-)

preferences, more headers/footers on listview

Java tutorial entries here and here helped clear up my understanding of generics and what’s going on with my last post. Basically what I was trying to do with an array is not supposed to be done, for esoteric type-safety reasons. I tried refactoring to use List<Class<Activity>> instead of an array but the cascading errors/warnings I got from that were puzzling. Maybe try that again later.

Annoyance: my preferences class has to use PreferenceManager.getDefaultSharedPreferences(context) all over its static methods. Surely there’s a way to cache the result of this verbosity? Probably in an Application object. I tried creating an Application object and caching getSharedPreferences() during its constructor (so that it could go in a final member) but this gave me a null pointer error:
  java.lang.NullPointerException
     at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:134)

Looks like the line it’s talking about is this:
     return mBase.getSharedPreferences(name, mode);
but mBase is set in the constructor so I don’t know how that would be null. I don’t know, but I did find out that if I called it in onCreate in the application, that worked fine. So I guess something is in place there that isn’t during the constructor.

I futzed a little bit more with setting headers/footers on the ListView. Here’s what I recall finding so far:

  • Forget about putting buttons or editors or anything like that which receives focus in headers/footers; that’s not going to work with navigation without some major magic (though they’ll be accessible by touch)
  • If I have header and an “empty” view, the header won’t show when the empty view is shown (list empty). I think the footer does, strangely. If I don’t define an empty view, headers will be shown all the time.
  • I can put stuff in my layout after the listview, and it looks kind of like a footer. But it can’t be navigated to, and more importantly if the list is longer than the screen it can’t even be seen.

I’m planning to just define adapters and views for custom list elements to do what I want (e.g. a “create a new entry” list item, no need for empty view), and otherwise use menus.