As a versatile mobile software engineer, I consider myself as a pretty lucky guy. I’m working both on iOS and Android platforms. Let’s be honest: I’m spending a lot of my professional and personal time working on Android and trying to spread everything I know about it via this blog, some conferences, some open-source projects, etc. But I’m also very close to the iOS ecosystem and some iOS developers. Talking to them everyday help me remember iOS and Android are not so different eventually. They both have to learn from each other.

Most multi-skilled (and with no preconception) mobile developers will assure you developing on Android is as great and easy as developing on iOS. Almost everything that is possible on iOS can be done on Android and vice-versa. Depending on the problem you are solving, it can be easy to make it work on Android or iOS. For instance, I consider developing adaptive and responsive layouts (I’m talking about adaptive layouts and not fixed/absolute layouts created with Interface Builder1.) in iOS is a pain in the ass compared to Android. This usually results in having 80% of the iPhone applications running only on portrait orientation and having fixed UITableViewCell heights. On the other hand, animating objects was (prior to Android 3.0 which introduced property-based animations) quite boring and creating smooth custom Views can be complex on Android because of various reasons (garbage collector, asynchronous measure/layout, UI toolkit sometimes slow compared to iOS, etc.)

I’m often talking to developers or reading some articles on the Internet and I noticed lots of people are complaining about the Android SDK. Most of the time they argue the SDK does not let them do as many things as iOS does. They complain about the fact it is impossible to implement things on Android, that the Android SDK is too poor, that the Android SDK is not permissive enough etc. When it comes to things I love, I can be fairly impulsive so I get angry. It drives me mad because I’m sick of looking at blind people. They are often too lazy or stupid to look at the documentation or source code (RTFM!). Most of the time, they did it on iOS but couldn’t find an equivalent to this technique on Android. I strongly believe their problem is they just don’t get it! They simply have to “think different”. This mantra was the Apple slogan a few years ago but it looks like some iOS developers are relatively new and don’t get the original Apple way of thinking.

You probably wonder why I have decided to write this article. The reason is pretty simple and relies on the new version of Path. Path is a Facebook-like social network. The company recently released a second version of their application. I will be frank with you: the iOS version is astonishing. It’s laggy but in term of UI/UX, I consider it as a reference. It contains a lot of brand new UI patterns that has been designed with pixel perfection. When looking at the iOS app, I decided to update my app on Android and I got mad ^^. Yes, as I said before, I am mercurial when it comes to things I consider important. I really don’t understand why Path decided to publish such a crappy app on Android. Unskilled developers? Few resources? iOS porters? Android aversion/detestation? An aggregate of all those factors? I really don’t know but I consider (and that’s just my own point of view) publishing nothing is better than releasing an application working correctly only on hdpi devices (handling screen densities is so easy thanks to the Android framework - not apprehending the resources switching system indicates a total ignorance of Android itself). Finally, the application is a complete port of the iOS version. They did not create a design that is dedicated to Android - you can have a look at the new Android Design website and especially the ‘Pure Android’ page. To conclude, apart from those two points, Path respected my motto “Do less but do it insanely great”. The application is very poor compared to the iOS version but the available features are working correctly.

In order to show you Android is as capable as iOS I decided to create the UI widget you can see in the Path timeline. Path decided to prioritize the information on screen by displaying the current timestamp only when needed. As a result instead of adding often useless timestamps everywhere, they attached a information panel to the scroll indicator (this is the name given to scrollbars in iOS). This is only visible when the UITableView is scrolling. Android users can see this as a fast scroller (like the one in the latest People app) but you can’t directly interact on it. The screenshot below gives you an example of the result:

The iOS implementation

In order to look at the implementation, I started looking for open source projects and some websites describing the technique. I finally found a blog post describing how to attach an info panel to a scroll indicator. The problem is this technique is a complete hack. To sum up, it consists on getting the latest subview of the UIScrollView (for your information, in iOS, UITableView - ListView equivalent - inherits UIScrollView - ScrollView equivalent). It appears this subview is currently an UIImageView used to draw the vertical scroll indicator. Thanks to it, you can determine the bounds of the scroll indicator or directly add a layer to it.

The problem relies on the fact Apple could change the order of the subviews or completely change how scroll indicators are drawn. They could and they have the right to as this is not part of the public API. Let’s imagine, the future release of iOS (iOS 6?) draws scroll indicators manually (with no use of an UIImageView but directly using a UIImage or CGImage), the application would be completely broken and the info panel would be attached to something else, something unknown. When you have millions of users out there, you can’t afford using hacks like this one. If your application is broken, it could take weeks to fix the issue (be notified the application is broken, find a way to bypass the problem, implement the solution, wait for the AppStore review process, etc.).

I don’t know how Path developed it but I hope they did not use this hack and found a greater implementation. Here are the possibilities I can think of:

  • They have developed their own UIScrollView: I strongly believe that is not the Path’s option. Designing a UIScrollView is extremely complex and would require a lot of time to get inertia-scrolling, edge bouncing working exactly as the framework-based UIScrollView.
  • They found a way to get a reference to the scroll indicator. I don’t know the documentation by heart but I’m pretty sure that’s impossible. Getting a scroll indicator is not part of the API in iOS. The only possible thing you can do is to change the style of a scroll indicator using a predefined enumeration of styles. You know nothing about the actual implementation.
  • They compute the position of the info panel on their own: UIScrollView has contentOffset, contentInset and contentSize properties. Using those properties in addition to the height of the UIScrollView (via bounds.size.height) gives you the ability to determine the position and size of the scroll indicators. I sincerely hope they used this technique and I invite people related to the Path iOS app to write a comment that states they did not hacked iOS at the end of this post.

Here we are: we know it is possible to implement such a UI widget in iOS. So, why have they chosen not to implement it on Android? I don’t know and I think I will never know so we will just suppose they think that is not possible on Android. Because of this they simply removed the scrollbars from the timeline! Actually the framework provides everything you need to do such a thing starting from Android 2.0 (API level 5) (the current minimum requirement for Path Android is 2.1, API level 7)

Implementing a scroll panel on Android

Seeing such an awful version of Path on my Android device, I started thinking about a way to implement an info panel attached to the scrollbars on Android. In the following lines we will simply get an overview of all methods that could be useful to implement such a behavior

Getting the current scrollbar position and size

Where iOS uses a UIImageView as scroll indicator, Android draws scrollbars using a Drawable (or, to be more specific, a ScrollBarDrawable that is not part of the public API). Getting the bounds (using getBounds()) of the Drawable would have let us know the current position of the scrollbar within the View. Unfortunately, the View API has no getScrollBarDrawable() method. This reminds me the iOS SDK …

Of course, we could have hacked something more horrible than the first iOS implementation we talked about. We could use introspection/reflection on the View class to get a ScrollBarDrawable instance but once again we fall into the public VS private debate and we are not these kind of guys!

The actual technique consists on using computeVerticalScroll[Extent|Offset|Range](). These methods are used by the system to determine the current position and size to apply to the vertical scrollbar. The only problem we have is those methods are protected and therefore can only be accessed from a class that inherits View. As a result we will have to extend View (or ListView in our case) to get those values.

Note : Remember the Java language authorizes methods visibility extension. In other words, it means a protected method can be made public by a child class. In the previous example we could made public all computeVerticalScroll[Extend|Offset|Range]() methods so they can be used from everywhere.

You may also need getVerticalScrollbarPosition() and getVerticalScrollbarWidth() to get the position of the vertical scroll bar within the screen as well as its position within the screen (left or right).

Dismissing the info panel at the appropriate moment

If you look closely to the scrollbars, you will notice they appear instantly once the user starts scrolling. After a delay without user interaction, they fade away. This behavior is a direct consequence of a call to awakenScrollBars (int startDelay, boolean invalidate). Listening to this method will let you know when to display the scrollbar as well as the delay after which the scrollbars must fade away. Using a very simple Handler you can schedule a Runnable that will perform the dismissing animation on your info panel after the given startDelay. This method has been introduced in API level 5. This is the reason why this overall technique requires at least Android 2.0.

Getting the scrollbar fade duration

A consistent way of dismissing the info panel is to use an animation which duration is equal to the one used when View is fading the scrollbars away. You can get this duration thanks to the ViewConfiguration class. This class contains a lot of constants and scaled dimensions that may be used when developing custom View. To get the scrollbar fade duration you can use the getScrollBarFadeDuration() static method. This method is available starting from API level 5 which is not a problem as this is our minimum API requirement.

Knowing the ListView is scrolling

There are several ways to do it. The easiest way is to consider a call to awakenScrollBars as an indication the ListView has been scrolled. It’s okay but I prefer attaching a dedicated OnScrollListener to the ListView using the setOnScrollListener(OnScrollListener) method. This listener will have an onScroll(AbsListView, int, int, int) method that will be called every time the ListView scrolls. The only problem with ListView is you can’t attach several listeners at the same time. If you need to attach several OnScrollListeners (for instance one from your custom ListView and another one in your Activity’s code), a great option is to implement an observer pattern or a forward mechanism.

Conclusion

We now have all the data needed to create our scrolling info panel. The implementation uses no hack at all as all methods we are using are part of the public API. As a result, we are sure the code will run perfectly on future versions of the platform. I have created a demonstration application which screencast can be seen below (I’m sorry for the crappy screencast, but I don’t have the appropriate equipment to do this):

Some of you may ask “Why aren’t you sharing the code?”. Actually the current code I implemented is working like a charm but I don’t want to share it right now. I’m always taking care of the snippets of code I’m sharing and I don’t consider this as proper enough to be shared, especially to developers that may reuse it without thinking about the way it’s working ^^. Moreover, the current implementation only works for Android 3.0 because I was to lazy to animate Views on my own. I preferred using the new animation APIs. Just note, it would be relatively easy to do it starting from Android 2.0 by manually animating the Views.

In a nutshell, do always consider everything as ‘implementable’ on Android. I haven’t either talked about the Path ’swipable’ sidemenu (really similar to the one Facebook recently added to their application), but it’s also possible to implement it on Android (I consider the Facebook implementation as very disappointing). I can’t say more about this now but I will keep you informed soon … I assure you Android is a really great and capable OS. You just have to think differently, address issues differently from what you used to. Be open and open-minded! I assure you Android has almost no limit. Stop doing crappy work2, be a perfectionist user and developer, develop greatly, live your apps, create things people will admire, applications people will remember.

1: Interface Builder is the name of the WYSIWYG editor you can use to create UI on iOS. It layouts UI widgets in an absolute manner.
2: An approximative translation for an expression of mine : “ArrĂȘter de faire du petit boulot !”. It describes something that should not have been done, something unreasonable in term of quality, something that is not great enough to be a sold/shared as a product.

Note : This article may target a Google product, it has to be read in a more global manner. It does not focus an a particular problem but rather on a large set of problems I encounter everyday. I decided to use this case because I believe the considerations given in this article should apply particularly to this specific case.

Most of my readers probably consider this blog as a technical blog. Let’s be honest: you’re right … at least partially! I agree I mostly write technical articles dealing with Android development. Nevertheless, you may have noticed I’m also always studying things in minute detail: source code, user experience, user interfaces, design, communication, etc. Thanks to this quality (some may consider this as a flaw because it makes me complain all the time and forces me to spend a lot of time attentively looking at things), I have discovered an amazing news!

Being obsessed by detail (to be honest I don’t consider myself as actually “obsessed” … I just think I see things, how greatly or badly designed they are, etc.), I have a huge news for you today! Indeed, for the past two months, Google has been lying to us. There is not one, but two Galaxy Nexus!!! Don’t you believe me? Go to the ‘Features’ page of the official Nexus website and have a look at the proof.

Don’t you see? Google is featuring two different sizes of Galaxy Nexus … I’m sure they will soon announce a new model of Galaxy Nexus with a different screen ratio … maybe … or maybe not … OK I’m just kidding. I just wanted to teach you how details are important. They demonstrate the attention and passion you’re giving to your product and everything that is gravitating around it. The more care you give to a product, the greater it is.

Let’s go back to our case. This morning, I noticed Google has updated the Nexus website. It drove me mad when I saw Google has scaled some of the images with no respect of the ratio. Because of this, they are creating a new and virtual Android device. I’ve rapidly looked at the source code and it looks like the problem lies in the fact the original image is not scaled properly in the HTML source code. I really don’t understand how such an error can happen. What could have come into developer’s head when he/she decided to scale the image without respecting the original ratio? So please guys, always respect the ratio of a picture. Morevover try not to let the browser scale the images for you. You don’t know about the resampling algorythm used by the browser (nearest neighbor, bilinear, bicubic, etc.). Resampling images on your own will allow to find the best option.

I have also found some other mistakes. Here are some of them:

  • A magic but very weird blue line appears on the right of the screen. Do you really want to buy an Android device constantly showing a blue line on the right?

  • All Galaxy Nexus pictures are consistant in term of “notification area”. They all give 4:00, a full battery and a full 4G reception. All but one … We also have a strange surrounding blue corner on this screen.

  • I don’t have a Galaxy Nexus nor Ice Cream Sandwich on my device but I doubt GMail decided to use the full screen mode in order to remove the notification bar to the ‘Compose screen’:

The conclusion of this article is obviously to pay attention to details. Apart from the fact you may make me die of a heart attack, you are neglecting your customers. Stop looking at a product (an application, a device, etc.) as a big black box. A product is always way larger than itself. Do consider a product as a large open box in which thousands of people are going to look into. This box does not just contain the product itself. It contains images, videos, slogans, colors, etc. that reflect your product, spread the spirit of your product all around the globe. As a result, if you really want to have an amazing impact on your audience and to spread how insanely great your product is, you have to make sure the bits of information are as perfectly designed as the original product. There is no such thing as a small or minor detail.

Note: When I started writing this post I decided to entitle it ‘Add accessories to your itemviews’. The biggest problem with this title is it doesn’t give an easily understandable overview of the post’s content (except maybe to iOS developers). The word ‘accessory’ is actually extracted from my iOS experience: it defines a view (clickable or not) that can be added to a UITableViewCell(base class for itemviews in iOS) and is often used to give an indication, a state or an access to any secondary information. Even if this word is not clear at first glance, it is insanely concise. As a result, I decided to use it in the rest of this article. To sum up, Android developers can consider ‘accessories’ as ’secondary Views in itemviews

Note: As usual the source code of all examples given in this series of articles is available on GitHub. I strongly suggest you to clone the repository and have a look at it while you are reading this post. You can clone it, fork it or directly download a zip file of it using the following link:

http://github.com/cyrilmottier/ListViewTipsAndTricks

As you may have imagined reading this series of tips and tricks articles, ListView is probably one of the most used widgets in the Android framework. Most of the time, a ListView displays a large set of similar data: contacts, text messages, emails, etc. Of course, you can have a lot of actions associated with each item in the ListView. In a contact application for instance, you may want to look at the details of a given contact or simply send him/her an email. The most common approach to perform such an action on Android is obviously to let the user click on the item and to display a new screen giving all the contact’s information: the detail screen. The latter finally lets you access to secondary actions and allow you to directly send him/her an email.

The biggest issue with the workflow previously described is it is pretty cumbersome to send an email to one of your contacts: first you need to find him/her in the list, then click on the associated itemview, search the email link in the detail screen and finally click on that email link … Reeeeeaaalllly borrriiiing! A great solution on the first versions of Android would have been to use the ‘long press’ gesture. A user long-pressing an item could access to important actions. Unfortunately, I consider this gesture as an anti UI pattern because it is far from being user-friendly. Indeed, people are incapable of finding this gesture as nothing indicates it on screen. With the increase of screen sizes on Android devices, another possible pattern is to add a clickable area in your itemviews: an accessory

A lot of Google applications uses this accessory UI pattern. For instance, the Contacts application allows the client to directly call a person from the contacts list. The Clock application also contains a ListView giving a list of the available alarms. You can (de)-activate an alarm directly from the list. Additionally, other examples of ListViews with clickable areas are applications letting the user star an item directly from the list.

Cheeses … again!?!

Even if I don’t like cheese in general, I decided to continue playing with a large set of them for the purpose of this article. In order to get a pretty interesting example, I have decided to create a layout for each itemview containing three Views: a CheckBox that can be used to star a particular cheese, a TextView displaying the name of the cheese and a “Buy it!” Button. The first version of our layout (res/layout/accessories_item.xml) is given below:

<?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"
    android:padding="6dp">
 
    <CheckBox
        android:id="@+id/btn_star"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:button="@android:drawable/btn_star" />
 
    <TextView
        android:id="@+id/content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:ellipsize="end"
        android:paddingLeft="6dp"
        android:paddingRight="6dp"
        android:singleLine="true"
        android:textAppearance="?android:attr/textAppearanceLarge" />
 
    <Button
        android:id="@+id/btn_buy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:text="@string/buy_it" />
 
</LinearLayout>

The code used to populate the ListView and bind data to the layout introduced above is given below.

package com.cyrilmottier.android.listviewtipsandtricks;
 
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
 
import static com.cyrilmottier.android.listviewtipsandtricks.data.Cheeses.CHEESES;
 
public class AccessoriesListActivity extends ListActivity {
 
    private static final String STAR_STATES = "listviewtipsandtricks:star_states";
 
    private AccessoriesAdapter mAdapter;
    private boolean[] mStarStates;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        if (savedInstanceState != null) {
            mStarStates = savedInstanceState.getBooleanArray(STAR_STATES);
        } else {
            mStarStates = new boolean[CHEESES.length];
        }
 
        mAdapter = new AccessoriesAdapter();
        setListAdapter(mAdapter);
    }
 
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBooleanArray(STAR_STATES, mStarStates);
    }
 
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        showMessage(getString(R.string.you_want_info_about_format, CHEESES[position]));
    }
 
    private class AccessoriesViewHolder {
        public CheckBox star;
        public TextView content;
    }
 
    private class AccessoriesAdapter extends BaseAdapter {
 
        @Override
        public int getCount() {
            return CHEESES.length;
        }
 
        @Override
        public String getItem(int position) {
            return CHEESES[position];
        }
 
        @Override
        public long getItemId(int position) {
            return position;
        }
 
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
 
            AccessoriesViewHolder holder = null;
 
            if (convertView == null) {
                convertView = getLayoutInflater().inflate(R.layout.accessories_item, parent, false);
 
                holder = new AccessoriesViewHolder();
                holder.star = (CheckBox) convertView.findViewById(R.id.btn_star);
                holder.star.setOnCheckedChangeListener(mStarCheckedChanceChangeListener);
                holder.content = (TextView) convertView.findViewById(R.id.content);
 
                ((Button) convertView.findViewById(R.id.btn_buy)).setOnClickListener(mBuyButtonClickListener);
 
                convertView.setTag(holder);
            } else {
                holder = (AccessoriesViewHolder) convertView.getTag();
            }
 
            holder.star.setChecked(mStarStates[position]);
            holder.content.setText(CHEESES[position]);
 
            return convertView;
        }
    }
 
    private void showMessage(String message) {
        Toast.makeText(AccessoriesListActivity.this, message, Toast.LENGTH_SHORT).show();
    }
 
    private OnClickListener mBuyButtonClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Cyril: Not implemented yet!
        }
    };
 
    private OnCheckedChangeListener mStarCheckedChanceChangeListener = new OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
         // TODO Cyril: Not implemented yet!
        }
    };
}

Even though some methods still need to be implemented and the code does not work properly, you can already launch it. Once the AccessoriesListActivity is displayed on screen we can observe the rendering is as expected:

Getting the position of a View in a ListView

The first problem we may encounter when implementing the OnClickListener as well as the OnCheckedChangeListener is to determine the position (in the Adapter’s data set) of a Button/CheckBox. Because of its reuse mechanism, the ListView creates a pool of N itemviews where N is roughly equal to the maximum number of visible itemviews at a given time. This mechanism minimizes allocation of Views and prevents the application from crashing with an annoying OutOfMemoryException. Reusing objects results in a significant performance boost but may makes the code more difficult to write, use, understand and debug. Fortunately, the ListView API provides a lot of very handy methods. In our case, the method whose signature is int getPositionForView(View) will help us. It returns the position within the Adapter’s data set for the given View. The given View should be an itemview or one of its descendants. This method may look ‘magical’ but you have to respect a particular contract when using it. As a result, I strongly encourage you to have a look at the method’s description in the Android documentation

Thanks to the getPositionForView(View) method, we can now easily implement our listeners:

private OnClickListener mBuyButtonClickListener = new OnClickListener() {
    @Override
    public void onClick(View v) {
        final int position = getListView().getPositionForView(v);
        if (position != ListView.INVALID_POSITION) {
            showMessage(getString(R.string.you_want_to_buy_format, CHEESES[position]));
        }
    }
};
 
private OnCheckedChangeListener mStarCheckedChanceChangeListener = new OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        final int position = getListView().getPositionForView(buttonView);
        if (position != ListView.INVALID_POSITION) {
            mStarStates[position] = isChecked;
        }
    }
};

If you start playing with the new code (especially with the stars Buttons), you will probably end up with a crash. This crash actually comes from the neglecting to entirely read the protocol to follow for using getPositionForView(View). The description states that the given View must be visible in the AdapterView at the time of the call. When a View is being reused (it has not yet been reattached to the ListView and is not visible), getView sets the state of the CheckBox to the one stored at the correct position in mStarStates. The CheckBox source code will automatically callback the OnCheckedChangeListener in case the state changes. Therefore, our code may call getPositionForView(View) on a View that is not yet attached to the ListView.

Naturally, fixing this problem could be done by wrapping the getPositionForView(View) in a try-catch block where the catch clause does nothing. To be honest, I think this is a really hacky way to fix the problem and it shouldn’t be natural. Using Exceptions to bypass an issue you don’t understand is usually an evidence of your mediocrity. Therefore, I prefer explaining you how to do it in a more ‘correct’ manner. The code below shows you how to temporarily stop listening to the OnCheckedChangeListener.

holder.star.setOnCheckedChangeListener(null);
holder.star.setChecked(mStarStates[position]);
holder.star.setOnCheckedChangeListener(mStarCheckedChanceChangeListener);

To be honest, I don’t like this snippet of code either but I think the problem actually comes from the fact the Android API is incomplete. The onCheckedChanged API should have an additional boolean argument flagging whether the change has been done programmatically or after a user action.

Why, the hell, are my itemviews no longer clickable?

Our Activity is now almost completely functional. You can star a cheese in the list, buy it using the “Buy it!” button but you can’t have information about a cheese in particular. The regular way to do it would be to click on the itemview. Unfortunately, if you try to do so, you will notice it is no longer clickable (as shown on the graphic below). Actually none of the itemviews can be clicked. This behavior is often considered as a bug by Android developers (issue #3414). Personally I consider it as half of a bug …

Android has been primarily designed for a large set of input methods. The entire system is completely capable of working with no touch screen. To navigate through the UI, the user can use a directional pad which focuses Views after Views if and only if those Views are focusable. By default, all Android controls are focusable. In order to prevent having controls that are not focus-reachable, the ListView will simply prevent the selection (and click) of an itemview. By design, the ListView blocks clicks of itemview containing at least one focusable descendant but it doesn’t make the content focus-reachable calling setItemsCanFocus(true).

The trick to overcome this design issue consists of removing the focusability property of all itemview’s descendants. You can obviously do this by adding android:focusable="false" on all of your controls but the preferred, clearest and easiest way to do is to block focusability from the itemview root ViewGroup. Focusability can be blocked using the android:descendantFocusability XML attribute:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="6dp"
    android:descendantFocusability="blocksDescendants">
 
    <!-- -->
 
</LinearLayout>

Note: Because of this trick, your layout no longer works using a directional pad or a trackball. Indeed, we cannot select a accessories of an itemview. I don’t consider this as a major problem because most Android devices are now based on a touch-screen and the Android Market automatically adds a hardware requirement for a touchscreen to all applications (android.hardware.touchscreen). If you want your code to be safe, you can add a <uses-hardware /> tag in your AndroidManifest.xml

Don’t press everything!

We finally did it! Our ListView runs magnificently!. Thanks to the code we have previously implemented, we have 3 possible actions for each itemview: star, buy and get info. This is great but that is not enough from my ‘perfectionist’ point of view. Indeed, many of you now know I do not consider an application or some code as finished if it is not well designed nor user-friendly. A lot of bad Android developers prefer focusing on functionality rather than the design. To be honest, I think this is one of the biggest danger the Android Market has to confront. Developers don’t understand users care more about the way features are designed (in term of UI and UX) in an application rather than the number of available feature. In our case something might have drawn your attention away. When pressing an itemview the CheckBox and Button turn yellow. The issue is demonstrated on the following screenshot:

The reason of this is Android, by default, recursively dispatches the pressed state of a ViewGroup to all of its children. Even if this behavior is completely natural most of the time, it may make your UI less polished and understandable. As we have seen in this example, each itemview features 3 actions. Each of those actions are completely independent. As a result, interacting with one should not act on the others.

The first, easiest, but bad solution would be to make sure the Drawables used by the Buttons don’t depend on the current View state. This means we would have to remove all of the ColorStateLists and StateListDrawables from our Buttons. It would prevent the Buttons from being highlighted when the user presses the itemview … but also when the user presses the Button itself! In other words, the user would have no feedback when interacting with our accessories which is a really poor user experience. As a conclusion: DO NOT do this!

The correct solution is to make sure our Buttons don’t ‘accept’ the pressed state when the parent is already in the pressed state. This can be done very easily by creating a new class extending Button and overriding the setPressed(boolean) method. Let’s call these new classes DontPressWithParentButton and DontPressWithParentCheckBox:

package com.cyrilmottier.android.listviewtipsandtricks.widget;
 
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
 
public class DontPressWithParentButton extends Button {
 
    public DontPressWithParentButton(Context context) {
        super(context);
    }
 
    public DontPressWithParentButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
    public DontPressWithParentButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
 
    @Override
    public void setPressed(boolean pressed) {
        if (pressed && getParent() instanceof View && ((View) getParent()).isPressed()) {
            return;
        }
        super.setPressed(pressed);
    }
 
}

To conclude, we need to use those new classes in the layout used for each itemview:

<?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"
    android:padding="6dp"
    android:descendantFocusability="blocksDescendants">
 
    <com.cyrilmottier.android.listviewtipsandtricks.widget.DontPressWithParentCheckBox
        android:id="@+id/btn_star"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:button="@android:drawable/btn_star" />
 
    <TextView
        android:id="@+id/content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:ellipsize="end"
        android:paddingLeft="6dp"
        android:paddingRight="6dp"
        android:singleLine="true"
        android:textAppearance="?android:attr/textAppearanceLarge" />
 
    <com.cyrilmottier.android.listviewtipsandtricks.widget.DontPressWithParentButton
        android:id="@+id/btn_buy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:text="@string/buy_it" />
 
</LinearLayout>

Polishing the user experience

The accessory UI pattern drastically enhances user experience. However, a bad implementation of the pattern can be terrible as the user may have a lot of difficulties to tap on a clickable View. As a result, when developing an accessory, you need to make sure it is easily clickable. The best way to do it is to use large Buttons. In general, consider a square of 30dp as the minimum clickable area you can add to a layout.

Having large Buttons in your UI will obviously improve user experience but it may make your application design look rough. A common trick when designing your accessories is to use an expanded touchable frame. The expanded touchable frame concept consist on surrounding the current design with transparent pixels. This frame enlarges the touchable area. Consequently, your design can stay sharp and tiny while the touchable area remains large enough to be easily clicked. You can enlarge a touchable area simply by adding a frame of transparent pixels around your design or simply using the padding attributes of a View.

In our example, the biggest problem rely on the difficulty to click on the star. In order to expand the touchable region, we will simply change the way the CheckBox is laid out on screen. Instead of vertically wrapping to the content and centering into the parent, we can fill the parent (fill_parent or match_parent for API level 8+) and set the content gravity to Gravity.CENTER_VERTICAL (which is the default vertical gravity in CheckBox).

<com.cyrilmottier.android.listviewtipsandtricks.widget.DontPressWithParentCheckBox
    android:id="@+id/btn_star"
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:button="@android:drawable/btn_star" />

Another great optimization could be to move the padding from the itemview to the CheckBox to get a touchable area as large as possible without changing the overall UI. To my despair, it is not possible because the CheckBox widget is poorly developed: it doesn’t take into account the padding when no text has been set and it draws the button Drawable regardless of the horizontal gravity. As a result, here are your options:

  • Add a 6dp transparent pixels frame directly in your star Drawable and stop using the padding attribute. Unfortunately, it implies modifying a lot of Drawables and you will end up having some new resources in your res/ directory. Moreover, you will decrease your application ‘platform consistency’ because your Drawables will no longer come from the system you are currently running on.
  • Create a custom View that measures/draws itself correctly. This is the best solution to our problem that could also allow you to fix the listener problem by adding a new ‘fromCode’ parameter to the callback method.

The graphic below sums up how the touchable area changes depending on the implementation method:

Conclusion

That’s all folks! You now know how to correctly add clickable Views in itemview. The ‘accessory’ UI pattern can be pretty helpful but take your time when implementing it. The time you are spending polishing it will assist your users and make them happy. Prior to integrating the accessory UI pattern to your ListView, try to focus on the actual purpose of your application: What’s really important for my users? Do they need a list which is clear or functional?. Indeed, the accessory UI pattern probably speeds up the completion of a task but it may also ruin the clearness and simplicity of a UI.

To finish, I would like to thank the people who helped me fixing errors from the original texts. They made my posts more pleasant to read and, most of all, more professional. With this fourth article, I have finished talking about all the techniques I had in mind when I started the ListView Tips & Tricks series. I sincerely hope you loved it as much as I enjoyed writing it: it was a very demanding task but I consider this series as my contribution to the refinement of all Android applications. I can’t wait to see great Android ListView-based applications on the Android Market.

Note: This article is not primarily related to Android development. I know this weblog is mainly targeted to developers and deal with UI/UX, optimizations, etc. from an Android developer point of view. However, I believe reading articles like this one may help you to apprehend some do’s and don’ts in Android application development

I completely agree with you! Yes I do! This post’s title means almost nothing. You are probably wondering how a nationality can describe the quality of an Android application? The answer to that question takes place in London … Being in the United Kingdom capital for the Droidcon UK 2011, I have heard of lot of things about being French. Frenchies are supposed to lose at rugby (which is totally false … I’m sure the English rugby team is currently in a plane to Europe at the time of the writing ^^). The Froggies (just for your information, I have never eaten a frog in my life … but I do love snails) are also known as spending half of their time complaining and the other half being on strike. To be honest, I kinda agree with this. My only issue regarding this prejudice is it doesn’t completely describe me. I have never been (and I think I will never be) on strike. As a result, I have no other choice than spending 100% of my time complaining! In other words, this post describes how French I am ^^.

Last week I received a brand new version of the Android Market (build 3.1.5). Let’s be clear about this new version: I’m very disappointed about it and I wanted to give you some precise explanations. The following review being pretty much a list of “I like” and “I don’t like”, I have decided to present the rest of this post as a list made of two obvious sections: “What’s good” and “What’s bad”. Let’s start by the negative side of the review.

Notice: Everybody in my circles know I’m a huge fan of Google and its services in general. I’m also a daily user of Android & the Android Market application. Blaming an operating system and/or an application you are using everyday and actively promoting during international events may seem odd. However I’m strongly convinced criticism is the path to improvement so I assume this complete review of the new version of the Android Market will help you not fall into those traps when developing. Hopefully, it may also be used by the Android Market developers to enhance the next version of the application. The Android Market is probably one of the most used app on Android devices. Making it perfect is a necessity. I do think Google has to take care more of the Android Market!

What’s bad

  • My biggest disappointment with the new Android Market is how poorly responsive, smooth, fast the application is. Google is releasing new versions quite often with the overall purpose to enhance the usability by refreshing the UI. Unfortunately, I strangely have the feeling the application is getting slower much faster than getting fancier and usable! The current version is so laggy, it is almost unusable on some devices. The content may take a lot of time to be entirely loaded. Moreover, most of the application animations are not smooth or even worse look like frozen sometimes. I sincerely think having un-fluid scrolling/flinging animations with widgets such as ListViews, ScrollViews, ViewPager, etc. is not acceptable on a decent device like mine (Nexus S). A great tip to prevent lags due to background threads loading content is to reduce their priority. This can easily be done using android.os.Process class and its setThreadPriority(int) method. In general, reducing the impact on the UI thread to a minimum will keep the application smooth and responsive.
  • Android Market has a lack of consistency compared to the other Google-provided Android applications especially regarding the menu icon style. I’m pretty sure the differences lie in the fact the new Android Market is Ice Scream Sandwich-ready. In ICS, Drawables from the menu will be used both in the ActionBar (use the MenuItem.showAsAction(int) method) and in the menu. The image below shows the difference between a menu icon from the GMail app and one from the new Android Market.

Menu button comparison

  • Sometimes elements are not centered in their parent. Android makes it easy to center content either using android:layout_gravity or android:gravity. Centering elements makes UI symetrical … which is pretty natural and user-friendly. Be also careful not over-centering.

Content not centered

  • I love the workspace UI pattern. It lets you switch very quickly between different types of content in your application. The Android Market uses this UI pattern intensively. Unfortunately the workspace from the Android Market (I don’t know if the application uses the ViewPager from the Android Compatibility Package but it looks like Google+ has the same problem) is not polished at all. The widget acts weirdly regarding user finger movements. It has a very annoying behavior: when swiping the page a little bit it always goes to the next page regardless of the scroll-distance or the velocity of your finger. The correct behavior would be to scroll back to the original position (as you can see on the launcher, the news application or the GreenDroid’s PagedView). Even worse, the widget used in the application screenshot gallery to mimic the worspace UI pattern has a correct behavior. Is Google developing internally the same kind of widgets over and over again, reducing, at the same time, source code consistency?
  • Transitions between screens are not consistent with Android itself. I assume Android Market uses the new Fragment API and this is the reason why translation animations are replaced by alpha animations. Unfortunately, those new animations are not in accordance with the transition animations of the rest of the applications. When developing an Android application try to focus on the future of your application which will prevent you from following a path you would regret in the future. Personally, my motto is “do less but do it great”.
  • The Android Market application sometimes uses resources which original density is not in accordance with the screen density. Starting API 4 (Android 1.6), Android supports multiple screen densities: ldpi, mdpi, hdpi, xhdpi (the latter has been introduced in Gingerbread). The framework helps the developer selecting the correct resources by defining a set of qualifiers. As a result, you can put hdpi images in the res/drawable-hdpi directory. This is very important as your images will look sharper on high density screens. The image shows you some exemples of Android Market screens not using appropriate images

Use appropriate resources

  • Layouts may look different depending on orientation (landscape & portrait). Given “as it”, it is not an actual problem. The problem comes from the fact some functionalities are not accessible depending on the current screen orientation. Developing such a layout will force the user to rotate his device which is, let’s be honest, far from being intuitive.

Available actions should not be orientation-dependant

  • Error screens are nor attractive nor accessible. The screenshot below compares error screens from the Android Market and from the desktop version of Safari. It is clear that the Safari error screens are more graphical, hence easier to understand at a glimpse. Most of the time, you should use a regular ImageView representing the current state (error and/or empty) of the screen in addition to a TextView describing literally the issue. The loading state should be represented in the same manner using an indeterminate ProgressBar and a “Loading” TextView.

More graphical error screens

  • The “Featured” screen is not consistent with the rest of the application. If you look attentively at the entire application you will notice a margin is never added at the top of the screens (between the content of the screen and the ViewPager selector). The “Featured” screen strangely has one at the top … but none at the bottom

Useless margin

  • The search screen displays a header giving the number of results for the current search. Unfortunately, when scrolling it fades away into the white color. I usually enjoy the fading edge effect with dark colors but I have to admit it is not that great with light colors. As you can see, in this case, it is even worse. A great solution would be to remove it through the application by adding android:fadingEdge="none" to the default ListView style. Another solution would have been to put the list and the header in a LinearLayout which would have made the header fixed and prevent a visible but useless divider between the header and the first actual itemview. You can also play with the android:cacheColorHint property.

Make sure your fading edges render properly

  • Having an ActionBar completely uncorrelated with the content of the application helps the user understand the ActionBar is not part of the content. It is also a way to demonstrate the ActionBar is a container for the navigation and/or action buttons. The Android Market application sometimes features a shadow below the ActionBar which acts as the limit between the navigation/actions and the content. Sometimes it does not:

An uncorrelated ActionBar

  • When performing a search, results are given in a ListView which itemviews always have the same height. Doing so is okay when the content of each itemviews is similar. The following screenshots give you an example of a search result. It is clear that some itemviews contain a lot a space (especially those that have not been rated yet). This is actually a consequence of using fixed-height itemviews. Android allows developers to use dynamic layouts in ListViews either using the framework provided layouts (ViewGroup) or creating a custom ViewGroup. However, when doing so, keep in mind having a close minimum and maximum heights is preferable. Indeed having very variable height in a ListView makes the scrollbars height vary, which looks awful.

Use dynamic-layout when possible

  • The “Categories” screen is pretty rough. To my mind, it is probably the ugliest screen in the entire Android Market app. The ListView is using dividers made of 3 pixels in height lines (on an hdpi device) which makes the UI unpolished. A divider is not supposed to be part of the content. It should be as unobtrusive as possible. Prefer using 1dp tall lines as dividers for all densities (1px for [l|m|h]dpi and 2px for xhdpi). Moreover, add a Drawable for each category whenever possible as the Settings application does (an icon representing the category, the icon of the most downloaded application of that category, etc.)
  • Some itemviews contain useless dividers. If you look at the result of an Android Market search or at your installed applications, you’ll notice a grey line on the right. Making sure your UI is pixel-perfect can be easily done using Hierarchy Viewer and its Pixel Perfect perspective.

Make sure your UI is pixel-perfect

  • Reflect the pressed and focused states. This is usually done by changing the appearance of the background. For instance, the Android Market changes the background to a blue color when an itemview is pressed. In the “Featured” screen, the application acts differently by adding a blue overlay. Responding to user actions is a good practice as it indicates the application is responsive. However, make sure the appearance changes do not interfere with the application usability. The screenshot below gives you an example of a pressed itemview being almost unreadable (look at the globe icon). In order to prevent such a behavior, you can use classes like ColorStateList and/or StateListDrawables.

Reflect the pressed/focused states

  • The new Android Market shows applications in a “block-manner”. As a result, ListView are made of blocks, each block representing an application. In portrait mode, each line is made of two blocks (3 in landscape). There are some situations where the actual number of blocks per line is different from the possible number of blocks per line. This makes the ListView like “broken” which is disturbing for a user. A solution would have been either to make sure lines are complete or to create a block representing the empty state.

A 'broken' ListView

  • If you go to the “My apps” screen (via the menu), you will notice application icons are loaded asynchrounously. Loading images (from the network or the filesystem) from a background Thread is often the good way to go as it prevents blocking the main thread (this thread is used to dispatch drawing events, touch events, etc.). Unfortunately the first time an icon is loaded in the “My apps” screen, it is displayed at a small size. If you scroll enough to make the icon disappear and scroll back to the original position, the icon size will have changed. Very weird, isn’t it?

What’s good

  • ListViews are not ListViews but rather true scrollable content! The complete redesign gives a new innovative look to the application. Whenever possible, try to design your itemviews to get away from the regular title/subtitle pattern. The Android Market made it awesome by creating blocks in which important information (title, icon) are more attractive (black over lightgrey) than secondary information (author, rating).
  • I love the new detail screen. It is clear, simple and contains everything you need to get a quick overview of the application information.
  • The new Android Market uses a lot of images. The best example is probably the “Featured” screen which is very attractive and eye-candy.
  • When updating an application that require new permissions, the application now shows the differences between those permissions. This is a very great feature that was previously missing (thanks to @foxykeep for pointing me out this addition)
  • As a very demanding user and developer, I think Android Market contains a lot of features that make the application innovative. I won’t give you an explanation of those features here but you can have a look at the excellent blog of Kirill Grouchnikov (an engineer working on the Android Market application at Google). Among those features: reflections, synchronized scrolling, images cross-fading, swipey tabs, etc.

I think that’s all folks! I hope you will follow the advices I have given in this post while developing your applications. As usual feel free to leave a comment below if you are want to discuss about some of the points.

Some of you may know I was at Droidcon UK 2011. It is a two full days event where you can meet hundreds of Android lovers and attend amazing Android-related presentations (development, UI, UX, business, etc.). I spent two lovely days at Droidcon UK 2011 meeting amazing people. I would like to thank WIP and Skillmatters for the amazing event they cooked us!

Droidcon UK was also the occasion for me to give a talk entitled “Responsive, smooth & user-friendly UIs: GreenDroid primer”. The talk I prepared was at least one hour and a half long but I had only 40 minutes to give it … As a result I skipped some interesting parts. If you are interested in looking at the complete presentation or simply want to have a look at the slides, you can use one of the link below. You can also find a video of my talk on the Skillmatters website