Friday, March 04, 2011

Android's Haptic Feedback

Android has some stock haptic controls, but if these don't satisfy your needs you can gain control of the device's vibrator for custom feedback.  Custom control of the device's vibrator requires permission, this is something you'll have to explicitly list in your AndroidManifest.xml (example below).  If you're paranoid about asking for permission or if you already have a long list of permissions, you may want to use the stock Android haptic feedback options.

Please note, the Motorola Xoom doesn't have a vibrator, therefore the examples below will compile and run, but you will not receive haptic feedback.

I'll start by showing the more complicated example first, custom haptic feedback.

Custom haptic feedback using the device's vibrator

1.) First Step, request the permission. Add the following line to you AndroidManifest.xml.

<uses-permission android:name="android.permission.VIBRATE" />

2.) Now define a listener to respond to touch events. It's not shown here but the CustomHapticListener class is actually a private non-static inner class of my Activity. This is because it needs access to the Context.getSystemService(...) method.

private class CustomHapticListener implements OnTouchListener {

  // Duration in milliseconds to vibrate
  private final int durationMs;
  
  public CustomHapticListener( int ms ) { 
    durationMs = ms;
  }   

  @Override
  public boolean onTouch( View v, MotionEvent event ) {
    if( event.getAction() == MotionEvent.ACTION_DOWN ){
      Vibrator vibe = ( Vibrator ) getSystemService( VIBRATOR_SERVICE );
      vibe.vibrate( durationMs );
    }
    return true;
  }
}

Lines 13 and 14 are the important ones. Line 13 gets a reference to the Vibrator service and line 14 vibrates the device. If you have requested the vibrate permission, line 14 will throw an exception.

3.) Lastly, register the listener. In your Activity's onCreate(...) method. You'll need to get a reference to the GUI element you want to attach haptic feedback to and then register the OnTouchListener we defined above.

@Override
public void onCreate( Bundle savedInstance ) {
  Button customBtn = ( Button ) findViewById( R.id.btn_custom );
  customBtn.setOnTouchListener( new CustomHapticListener( 100 ) );
}

That's it you're now in control of the haptic feedback, now onto using stock Android haptic feedback.

Stock Haptic Feedback Events

First thing's first, to use stock Android haptic feedback events you must enable it on View-by-View basis. That is, you must explicitly enable haptic feedback for each View. Enabling haptic feedback can be done declaratively in your layout file or programmatically in Java. To enable haptic feedback in your layout, simply add the android:hapticFeedbackEnabled="true" attribute to your View(s). Here's an abbreviated example:


Here's how you do the same thing in code:

Button keyboardTapBtn = ( Button ) findViewById( btnId );
keyboardTapBtn.setHapticFeedbackEnabled( true );

Now that haptic feedback has been enabled, the next step is to register an OnTouchListener and then perform the actual feedback. Below is an example of registering an OnTouchListener and performing haptic feedback when a user touches the View.

// Initialize some buttons with the stock Android haptic feedback values
private void initializeButtons() {
    // intialize the buttons with the standard Haptic feedback options
    initializeButton( R.id.btn_keyboardTap, HapticFeedbackConstants.KEYBOARD_TAP );
    initializeButton( R.id.btn_longPress,   HapticFeedbackConstants.LONG_PRESS );
    initializeButton( R.id.btn_virtualKey,  HapticFeedbackConstants.VIRTUAL_KEY );
}

// helper method to initialize single buttons and register an OnTouchListener
// to perform the haptic feedback
private void initializeButton( int btnId, int hapticId ) {
    Button btn = ( Button ) findViewById( btnId );
    btn.setOnTouchListener( new HapticTouchListener( hapticId ) );     
}    

// Class to handle touch events and respond with haptic feedback
private class HapticTouchListener implements OnTouchListener {

    private final int feedbackType;
     
    public HapticTouchListener( int type ) { feedbackType = type; }
     
    public int feedbackType() { return feedbackType; }
     
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // only perform feedback when the user touches the view, as opposed
        // to lifting a finger off the view
        if( event.getAction() == MotionEvent.ACTION_DOWN ){
            // perform the feedback
            v.performHapticFeedback( feedbackType() );
        }
        return true;
    }
}

You'll notice on lines 3 - 5 I'm initializing 3 different buttons with three different haptic feedback constants. These are Android's stock values, 2 of the 3 seem to provide exactly the same feedback. The example code above is part of a test app I wrote to demonstrate haptic feedback and I could not tell the difference between HapticFeedbackConstants.LONG_PRESS and HapticFeedbackConstants.KeyboardTap. Also, HapticFeedbackConstants.VIRTUAL_KEY didn't provide any feedback, maybe I did something wrong...maybe not.

Line 31 is where the haptic feedback is performed. All in all, providing haptic feedback is pretty simple, if you want control of the device's vibrator make sure you request permission in your AndroidManifest.xml. If you choose to use the stock Android haptic feedback options, make sure you enable haptic feedback for your Views either in the layout or programmatically.

3 comments:

tzuhan said...

Hi Adrian,

We haven't met before, but I wanted contact you to thank your for your Haptic feedback recipe on AndroidCookbook.com.

I've utilized it as part of our CloudStack Android Client app, Cumulus (you can find it at https://github.com/creationline/Cumulus),
which is open sourced under the Apache v2.0 license. The haptic feedback is used when the user presses a blue button that brings up a quick action menu. Since the button is embedded inside a list item, the haptic feedback is useful in this case to differentiate between whether you have touched the button or the item itself.

I've included you and AndroidCookbook.com in a "thank you" section of the app's About screen and the readme.
If you would like me to change this, please don't hesitate to contact me.
Your recipe saved me a lot of time and I really appreciate it. Thanks for offering it for everybody's education and use!

Tzu-Han

tension said...

Hello, I appreciate the credit very much, I'm glad you found my code snippet useful!

Anonymous said...

I have learn several good stuff here. Definitely price bookmarking for
revisiting. I surprise how so much attempt you set
to create such a great informative web site.
http://asss.tv/tv/read_blog/86763/enhance-attraction-to-extra-instagram-followers
Here is my site ... mamann