Profile cover photo
Profile photo
Xeodou Li
18 followers
18 followers
About
Posts

Post has attachment

Post has shared content
Android App Polishing: Preserving Context to Help Users

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.

Just like every other applications, The Capitaine Train app contains several forms: sign in/up form, user info forms, etc. Forms are usually a pain in the ass to develop because you need to make sure they are clean enough not to annoy the user but verbose enough to let her/him understand what kind of information she/he is currently filling in.

When implementing the first internal versions of the application we obviously used EditText as our favorite class. Unfortunately, we rapidly faced an issue with the default design of EditText: the user loses context once at least one character is typed into a field. Because EditText uses the "hint" string as the field's description, we had some pre-filled fields that were almost impossible to understand.

In order to overcome this issue, we decided to implement an emerging pattern introduced by Matt D. Smith on Dribble[1] and described by Brad Frost on his blog[2]: the float label pattern. We actually called it internally (i.e the classes are named after): the floating hint pattern.

The implementation is rather simple and consists on a base class we called FloatingHintControl. FloatingHintControl is an abstract container that is made of a TextView and requires an additional 'interactable' View (i.e. the control). The floating hint is displayed/animated depending on the control state as reported by subclasses. In the current public build of Capitaine Train, we have several FloatingHintControl subclasses:

  • FloatingHintAutoCompleteTextView: an AutoCompleteTextView with a floating hint
  • FloatingHintButton: this UI widget is actually a button that displays a date picker dialog when tapped
  • FloatingHintEditText: the floating hint equivalent of an EditText
  • FloatingHintSpinner: A Spinner that always displays a floating hint description

Preserving user's context is both essential and difficult to do on mobile applications. Because screens sizes are generally small you always have to weight the pros and cons of adding information on screen. If you are not really satisfied about the built-in EditText, the floating hint may be a great compromise when designing forms in your Android applications.

#gde   #android   #AndroidAppPolishing  

[O]: https://play.google.com/store/apps/details?id=com.capitainetrain.android
[1]: https://dribbble.com/shots/1254439--GIF-Float-Label-Form-Interaction?list=users&offset=28
[2]: http://bradfrostweb.com/blog/post/float-label-pattern/
Animated Photo
Add a comment...

Post has shared content
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

[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
Add a comment...

Post has shared content
Announcing the SDK Manager plugin for Gradle Android builds.

Local SDK missing? API level not downloaded? Support library repository out-of-date?

These are all typical problems which you shouldn't have to deal with. This is especially painful when you have multiple developers on a project or a CI machine that you have to keep up-to-date.

This Gradle plugin will manage these SDK dependencies for you automatically. Simply apply the plugin before the normal 'android' plugin:

  apply plugin: 'android-sdk-manager'
  apply plugin: 'android'

Supported functionality:

* local.properties will be created if missing. The ANDROID_HOME environment variable will be used if present. Otherwise ~/.android-sdk will be used.
* The platform-specific SDK will be downloaded if missing.
* Compilation API declared in compileSdkVersion will be downloaded if missing.
* If any dependencies are declared on support libraries, the support repository will be downloaded if missing. If the revision of the support repository does not contain the version declared it will be updated.
* If any dependencies are declared on Google Play Services, the Google repository will be downloaded if missing. If the revision of the Google repository does not contain the version declared it will be updated.

See more info on how to include in your project by following the link below.

#AndroidDev
Add a comment...

Post has shared content
Announcing the SDK Manager plugin for Gradle Android builds.

Local SDK missing? API level not downloaded? Support library repository out-of-date?

These are all typical problems which you shouldn't have to deal with. This is especially painful when you have multiple developers on a project or a CI machine that you have to keep up-to-date.

This Gradle plugin will manage these SDK dependencies for you automatically. Simply apply the plugin before the normal 'android' plugin:

  apply plugin: 'android-sdk-manager'
  apply plugin: 'android'

Supported functionality:

* local.properties will be created if missing. The ANDROID_HOME environment variable will be used if present. Otherwise ~/.android-sdk will be used.
* The platform-specific SDK will be downloaded if missing.
* Compilation API declared in compileSdkVersion will be downloaded if missing.
* If any dependencies are declared on support libraries, the support repository will be downloaded if missing. If the revision of the support repository does not contain the version declared it will be updated.
* If any dependencies are declared on Google Play Services, the Google repository will be downloaded if missing. If the revision of the Google repository does not contain the version declared it will be updated.

See more info on how to include in your project by following the link below.

#AndroidDev
Add a comment...

Post has shared content
It's been a big year for Android Design in Action; thanks to everyone who tuned in this year (600K+ views across our 2013 episodes)! In today's episode, +Nick Butcher, +Mike Denny and I recap the best of #ADiA in 2013, including inspirational examples, friendly rants, best practices, and mockups.

Check out the full 56 minute recap video on YouTube: Android Design in Action: 2013 Recap or browse through the attached slides to flip through the different episode highlights.

And remember, you can watch all 30+ episodes of #ADiA on YouTube at androiddesigninaction.com.

#AndroidDesign
PhotoPhotoPhotoPhotoPhoto
Android Design in Action: 2013 Recap
32 Photos - View album
Add a comment...

Post has attachment
Add a comment...

必须要说 google plus 竟然可以用嘞
Add a comment...

Post has shared content
把政治搞乱整死一批人,把官员搞贪害死一批人,把司法搞黑冤死一批人,把经济搞虚骗死一批人,把股市搞崩套死一批人,把楼市搞疯坑死一批人;执政数十年,除了变着招数剥削民众,就是想着法子贪腐享受,欺骗民众推翻三座大山却压上一座高原,至今不思悔改,变本加厉欺压奴役,如此伪光症,不死没天理!
Photo
Add a comment...

Post has shared content
PhotoPhotoPhotoPhotoPhoto
杜甫哥哥 (27 photos)
27 Photos - View album
Add a comment...
Wait while more posts are being loaded