Android App Polishing: Adding Subtle Attention Seekers

With the release of Capitaine Train for Android[0], several people recently asked me how we implemented some of the tips & tricks available in the application (some of these tricks are barely visible but remember, I love details :p). In order to showcase some of the most interesting application bits of code, I thought it could be helpful to Android developers to start a small series of posts. Feel free to comment this article if you want me to describe something you found nice in the Capitaine Train application.

During the entire development of the Capitaine Train Android app, we kept one important thing in mind: be subtle but still understandable. Although this rule is easy to understand it is always hard to implement correctly.

Some great examples of adding subtle but yet understandable visual tricks are the search form and the passenger detail screen. Indeed, when launching a new train search with some missing required fields, the field animates to indicate the user something is wrong. An equivalent is the "Add a passenger" ActionBar button than wobble when the user is tapping the empty screen indicating she/he has no registered passengers yet. This wobbling button is like saying "Hey, guy, tap me to create a new passenger!". The gif attached to this post demonstrates the animation but let's be honest I'm not a gif expert :s I highly encourage you to have a look at the Capitaine Train and try it out directly ;).

When used appropriately, attention seekers, are way better than these pretty ugly Toasts displayed right in the middle of the screen. Why? Mostly because they are subtle and require less time to be scanned/parsed than a long and difficult to read text.

But how to implement such an animation? It generally consists on several "back and forth"... We could have obviously implemented this using a sequence of ObjectAnimators or a pretty weird TimeInterpolator but that would have been quite painful. The trick is simply to leverage the animation framework by using the Keyframe[1] & PropertyValuesHolder[2] objects.

A Keyframe is an object that holds a time/value pair describing a particular state in an animation. For instance Keyframe.ofInt(.5f, Color.RED) indicates the color should be Color.RED at 50% of the animation duration.

A PropertyValuesHolder is a class that associates a property to some values. It is particularly useful whenever you want to animate several properties at once with a single ValueAnimator.

When developing the Capitaine Train application we created several attention seekers and tried them all. We finally ended up using only 2 different animations:
  
  • The "nope" animation: used whenever a field is invalid (this is the case when trying to start a search with some missing mandatory fields)
  • The "tada" animation: used to indicate the user the action she/he is looking for is probably a tap-away from this button (this is the case when tapping the empty state with no passenger and/or when tapping the "no cards" cell on the passenger detail screen)


public static ObjectAnimator tada(View view) {
    return tada(view, 1f);
}

public static ObjectAnimator tada(View view, float shakeFactor) {

    PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofKeyframe(View.SCALE_X,
            Keyframe.ofFloat(0f, 1f),
            Keyframe.ofFloat(.1f, .9f),
            Keyframe.ofFloat(.2f, .9f),
            Keyframe.ofFloat(.3f, 1.1f),
            Keyframe.ofFloat(.4f, 1.1f),
            Keyframe.ofFloat(.5f, 1.1f),
            Keyframe.ofFloat(.6f, 1.1f),
            Keyframe.ofFloat(.7f, 1.1f),
            Keyframe.ofFloat(.8f, 1.1f),
            Keyframe.ofFloat(.9f, 1.1f),
            Keyframe.ofFloat(1f, 1f)
    );

    PropertyValuesHolder pvhScaleY = PropertyValuesHolder.ofKeyframe(View.SCALE_Y,
            Keyframe.ofFloat(0f, 1f),
            Keyframe.ofFloat(.1f, .9f),
            Keyframe.ofFloat(.2f, .9f),
            Keyframe.ofFloat(.3f, 1.1f),
            Keyframe.ofFloat(.4f, 1.1f),
            Keyframe.ofFloat(.5f, 1.1f),
            Keyframe.ofFloat(.6f, 1.1f),
            Keyframe.ofFloat(.7f, 1.1f),
            Keyframe.ofFloat(.8f, 1.1f),
            Keyframe.ofFloat(.9f, 1.1f),
            Keyframe.ofFloat(1f, 1f)
    );

    PropertyValuesHolder pvhRotate = PropertyValuesHolder.ofKeyframe(View.ROTATION,
            Keyframe.ofFloat(0f, 0f),
            Keyframe.ofFloat(.1f, -3f * shakeFactor),
            Keyframe.ofFloat(.2f, -3f * shakeFactor),
            Keyframe.ofFloat(.3f, 3f * shakeFactor),
            Keyframe.ofFloat(.4f, -3f * shakeFactor),
            Keyframe.ofFloat(.5f, 3f * shakeFactor),
            Keyframe.ofFloat(.6f, -3f * shakeFactor),
            Keyframe.ofFloat(.7f, 3f * shakeFactor),
            Keyframe.ofFloat(.8f, -3f * shakeFactor),
            Keyframe.ofFloat(.9f, 3f * shakeFactor),
            Keyframe.ofFloat(1f, 0)
    );

    return ObjectAnimator.ofPropertyValuesHolder(view, pvhScaleX, pvhScaleY, pvhRotate).
            setDuration(1000);
}

public static ObjectAnimator nope(View view) {
    int delta = view.getResources().getDimensionPixelOffset(R.dimen.spacing_medium);

    PropertyValuesHolder pvhTranslateX = PropertyValuesHolder.ofKeyframe(View.TRANSLATION_X,
            Keyframe.ofFloat(0f, 0),
            Keyframe.ofFloat(.10f, -delta),
            Keyframe.ofFloat(.26f, delta),
            Keyframe.ofFloat(.42f, -delta),
            Keyframe.ofFloat(.58f, delta),
            Keyframe.ofFloat(.74f, -delta),
            Keyframe.ofFloat(.90f, delta),
            Keyframe.ofFloat(1f, 0f)
    );

    return ObjectAnimator.ofPropertyValuesHolder(view, pvhTranslateX).
            setDuration(500);
}

Once again, attention seekers are great but they should be used with parsimony. The less you use them, the greater their influence is.

#gde #android  #article  #AndroidAppPolishing  

[0]: https://play.google.com/store/apps/details?id=com.capitainetrain.android
[1]: http://developer.android.com/reference/android/animation/Keyframe.html
[2]: http://developer.android.com/reference/android/animation/PropertyValuesHolder.html
Animated Photo
Shared publiclyView activity