Handling User Events
You’ve seen how to do basic event handling in some of the previous control examples. For instance, you know how to handle when a user clicks on a button. There are a number of other events generated by various actions the user might take. This section briefly introduces you to some of these events. First, though, we need to talk about the input states in Android.
Listening for Touch Mode Changes
The Android screen can be in one of two states. The state determines how the focus on View controls is handled. When touch mode is on, typically only objects such as EditText get focus when selected. Other objects, because they can be selected directly by the user tapping on the screen, won’t take focus but instead trigger their action, if any. When not in touch mode, however, the user can change focus among even more object types. These include buttons and other views that normally need only a click to trigger their action.
Knowing what mode the screen is in is useful if you want to handle certain events. If, for instance, your application relies on the focus or lack of focus on a particular control, your application might need to know whether the device is in touch mode because the focus behavior is likely different.
Your application can register to find out when the touch mode changes by using the addOnTouchModeChangeListener() method in the android.view.ViewTreeObserver class. Your application needs to implement the ViewTreeObserver.OnTouchModeChangeListener class to listen for these events. Here is a sample implementation:
View all = findViewById(R.id.events_screen); ViewTreeObserver vto = all.getViewTreeObserver(); vto.addOnTouchModeChangeListener( new ViewTreeObserver.OnTouchModeChangeListener() { public void onTouchModeChanged( boolean isInTouchMode) { events.setText("Touch mode: " + isInTouchMode); } });
In this example, the top-level View in the layout is retrieved. A ViewTreeObserver listens to a View and all its child View objects. Using the top-level View of the layout means the ViewTreeObserver listens to events in the entire layout. An implementation of the onTouchModeChanged() method provides the ViewTreeObserver with a method to call when the touch mode changes. It merely passes in which mode the View is now in.
In this example, the mode is written to a TextView named events. We use this same TextView in further event handling examples to show on the screen which events our application has been told about. The ViewTreeObserver can enable applications to listen to a few other events on an entire screen.
By running this sample code, we can demonstrate the touch mode changing to true immediately when the user taps on the touchscreen. Conversely, when the user chooses to use any other input method, the application reports that touch mode is false immediately after the input event, such as a key being pressed.
Listening for Events on the Entire Screen
You saw in the last section how your application can watch for changes to the touch mode state of the screen using the ViewTreeObserver class. The ViewTreeObserver also provides other events that can be watched for on a full screen or an entire View and all of its children. Some of these are:
- Drawor PreDraw: Get notified before the View and its children are drawn.
- GlobalLayout: Get notified when the layout of the View and its children might change, including visibility changes.
- GlobalFocusChange: Get notified when the focus in the View and its children changes.
Your application might want to perform some actions before the screen is drawn. You can do this by calling the method addOnPreDrawListener() with an implementation of the ViewTreeObserver.OnPreDrawListener class interface or by calling the method addOnDrawListener() with an implementation of the ViewTreeObserver.OnDrawListener class interface.
Similarly, your application can find out when the layout or visibility of a View has changed. This might be useful if your application dynamically changes the display contents of a View and you want to check to see whether a View still fits on the screen. Your application needs to provide an implementation of the ViewTreeObserver.OnGlobalLayoutListener class interface to the addGlobalLayoutListener() method of the ViewTreeObserver object.
Finally, your application can register to find out when the focus changes between a View control and any of its child View controls. Your application might want to do this to monitor how a user moves about on the screen. When in touch mode, though, there might be fewer focus changes than when touch mode is not set. In this case, your application needs to provide an implementation of the ViewTreeObserver.OnGlobalFocusChangeListener class interface to the addGlobalFocusChangeListener() method.
Here is a sample implementation of this:
vto.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() { public void onGlobalFocusChanged( View oldFocus, View newFocus) { if (oldFocus != null && newFocus != null) { events.setText("Focus \nfrom: " + oldFocus.toString() + " \nto: " + newFocus.toString()); } } });
This example uses the same ViewTreeObserver, vto, and TextView events as the previous example. It shows that both the currently focused View object and the previously focused View object are passed to the listener as method parameters. From here, your application can perform needed actions.
If your application merely wants to check values after the user has modified a particular View object, though, you might need to register to listen for focus changes only of that particular View object. This is discussed later in this chapter.
Listening for Long Clicks
You can add a context menu or a contextual action bar to a View that is activated when the user performs a long click on that View. A long click is typically when a user presses on the touchscreen and holds a finger there until an action is performed. However, a long press event can also be triggered if the user navigates there with a non-touch method, such as via a keyboard or a button. This action is also often called a press-and-hold action.
Although the context menu is a great typical use case for the long-click event, you can listen for the long-click event and perform any action you want. However, this is the same event that triggers the context menu. If you’ve already added a context menu to a View, you might not want to listen for the long-click event as other actions or side effects might confuse the user or even prevent the context menu or contextual action bar from showing. As always with good user interface design, try to be consistent for usability’s sake.
Your application can listen to the long-click event on any View. The following example demonstrates how to listen for a long-click event on a Button control:
Button long_press = (Button) findViewById(R.id.long_press); long_press.setOnLongClickListener(new View.OnLongClickListener() { public boolean onLongClick(View v) { events.setText("Long click: " + v.toString()); return true; } });
First, the Button object is requested by providing its identifier. Then the setOnLongClickListener() method is called with our implementation of the View.OnLongClickListener class interface. The View on which the user long-clicked is passed in to the onLongClick() event handler. Here again we use the same TextView as before to display text saying that a long click occurred.
Listening for Focus Changes
We have already discussed listening for focus changes on an entire screen. All View objects, though, can also trigger a call to listeners when their particular focus state changes. You do this by providing an implementation of the View.OnFocusChangeListener class to the setOnFocusChangeListener() method. The following is an example of how to listen for focus change events with an EditText control:
TextView focus = (TextView) findViewById(R.id.text_focus_change); focus.setOnFocusChangeListener(new View.OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { if (mSaveText != null) { ((TextView)v).setText(mSaveText); } } else { mSaveText = ((TextView)v).getText().toString(); ((TextView)v).setText(""); } }
In this implementation, we also use a private member variable of type String for mSaveText. After retrieving the EditText control as a TextView, we do one of two things. If the user moves focus away from the control, we store the text in mSaveText and set the text to empty. If the user changes focus to the control, though, we restore this text. This has the amusing effect of hiding the text the user entered when the control is not active. This can be useful on a form on which a user needs to make multiple, lengthy text entries but you want to provide an easy way for the user to see which one to edit. It is also useful for demonstrating a purpose for the focus listeners on a text entry. Other uses might include validating text a user enters after the user navigates away or prefilling the text entry the first time the user navigates to it with something else entered.