Event Listening in Android development is largely centered around the View
object.
Any View (Button, TextView, etc) has many event listeners that can be attached using the setOnEvent
pattern which involves passing a class that implements a particular event interface. The listeners available to any View
include:
setOnClickListener
- Callback when the view is clickedsetOnDragListener
- Callback when the view is draggedsetOnFocusChangeListener
- Callback when the view changes focussetOnGenericMotionListener
- Callback for arbitrary gesturessetOnHoverListener
- Callback for hovering over the viewsetOnKeyListener
- Callback for pressing a hardware key when view has focussetOnLongClickListener
- Callback for pressing and holding a viewsetOnTouchListener
- Callback for touching down or up on a viewUsing Java
In Java Code, attaching to any event works roughly the same way. Let's take the OnClickListener
as an example. First, you need a reference to the view and then you need to use the set
method associated with that listener and pass in a class implementing a particular interface. For example:
Button btnExample = findViewById(R.id.btnExample);
btnExample.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Do something here
}
});
// Kotlin Android extensions allow findViewById() lookups
val btnExample = btnExample
// No parenthesis is needed for lambda expression -- https://kotlinlang.org/docs/reference/coding-conventions.html#lambda-formatting
btnExample.setOnClickListener { v ->
// do something
}
Alternatively, it is sometimes useful to have your class implement the listener directly, in which case you would add the listener implementation to your class and pass a reference to your class to the set
method. For Example:
class MyClass extends Fragment implements View.OnClickListener {
// Elsewhere in a method in the class that sets up the Button ...
Button btnExample = findViewById(R.id.btnExample);
btnExample.setOnClickListener(this);
@Override
public void onClick(View v) {
// Do something here
}
}
class MyClass : Fragment(), View.OnClickListener {
// Elsewhere in a method in the class that sets up the Button ...
val btnExample = btnExample
btnExample.setOnClickListener(this)
override fun onClick(v: View) {
// Do something here
}
}
This pattern works for any of the view-based event listeners.
Using XML
In addition onClick
has a unique shortcut that allows the method to specified within the layout XML. So rather than attaching the event manually in the Java, the method can be attached in the view. For example:
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/button_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_send"
android:onClick="sendMessage" />
Within the Activity that hosts this layout, the following method handles the click event:
/** Called when the user touches the button */
public void sendMessage(View view) {
// Do something in response to button click
}
fun sendMessage(view: View) {
// Do something in response to button click
}
In addition to the standard View listeners, AdapterView
descendants have a few more key event listeners having to do with their items:
setOnItemClickListener
- Callback when an item contained is clickedsetOnItemLongClickListener
- Callback when an item contained is clicked and heldsetOnItemSelectedListener
- Callback when an item is selectedThis works similarly to a standard listener, simply implementing the correct AdapterView.OnItemClickListener interface:
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Do something here
// The position is the index of the item pressed
// If the third item in a list was pressed, position is `2`
}
});
lv.onItemClickListener = OnItemClickListener { parent, view, position, id ->
// Do something here
// The position is the index of the item pressed
// If the third item in a list was pressed, position is `2`
}
This works similarly for the setting up a "long click" where an item is pressed and held down using the OnItemLongClickListener:
lvItems.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> adapter, View item, int pos, long rowId) {
// Do something here
}
});
lvItems.onItemLongClickListener = AdapterView.OnItemLongClickListener { adapter, item, pos, rowId ->
// Do something here
// return true/false
return@OnItemLongClickListener true
}
Troubleshooting: Item Click Not Firing If the item is more complex and does not seem to be properly responding to clicks after setting up the handler, the views inside the item might be drawing the focus. Check out this stackoverflow post and add the property android:descendantFocusability="blocksDescendants"
to the root layout within the template for the item.
In addition to the listeners described above, there are a few other common listeners for input fields in particular.
addTextChangedListener
- Fires each time the text in the field is being changedsetOnEditorActionListener
- Fires when an "action" button on the soft keyboard is pressedIf you want to handle an event as the text in the view is being changed, you only need to look as far as the addTextChangedListener method on an EditText (or even TextView):
EditText etValue = findViewById(R.id.etValue);
etValue.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Fires right as the text is being changed (even supplies the range of text)
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// Fires right before text is changing
}
@Override
public void afterTextChanged(Editable s) {
// Fires right after the text has changed
tvDisplay.setText(s.toString());
}
});
val etValue = findViewById(R.id.etValue) as EditText
etValue.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
// Fires right as the text is being changed (even supplies the range of text)
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// Fires right before text is changing
}
override fun afterTextChanged(s: Editable) {
// Fires right after the text has changed
tvDisplay.setText(s.toString())
}
})
This is great for any time you want to have the UI update as the user enters text.
Another case is when you want an action to occur once the user has finished typing text with the Soft Keyboard. Keep in mind that this is especially useful when you can see the virtual keyboard which is disabled by default in the emulator but can be enabled as explained in this graphic.
First, we need to setup an "action" button for our text field. To setup an "action button" such as a Done button on the soft Keyboard, simply configure your EditText with the following properties:
<EditText
android:inputType="text"
android:singleLine="true"
android:imeOptions="actionDone"
android:id="@+id/etValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<requestFocus />
</EditText>
In particular, singleLine
and imeOptions
are required for the Done button to display. Now, we can hook into a editor listener for when the done button is pressed with:
etValue.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
String text = v.getText().toString();
Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
});
etValue.setOnEditorActionListener(OnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
val text = v.text.toString()
Toast.makeText(this@MainActivity, text, Toast.LENGTH_SHORT).show()
return@OnEditorActionListener true
}
false
})
This is often great whenever a user needs to type text and then explicitly have an action performed when they are finished. There are many imeOptions for different situations.
Similarly to EditText, many common input views have listeners of their own including NumberPicker has setOnValueChangedListener and SeekBar has setOnSeekBarChangeListener which allow us to listen for changes:
NumberPicker npValue = findViewById(R.id.npValue);
npValue.setOnValueChangedListener(new OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// Changes based on number here
}
});
val npValue = findViewById(R.id.npValue) as NumberPicker
npValue.setOnValueChangedListener { picker, oldVal, newVal ->
// Changes based on number here
}
Almost all input views have similar methods available.