Codepath

Basic Event Listeners

Overview

Event Listening in Android development is largely centered around the View object.

View Event Listeners

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 clicked
  • setOnDragListener - Callback when the view is dragged
  • setOnFocusChangeListener - Callback when the view changes focus
  • setOnGenericMotionListener - Callback for arbitrary gestures
  • setOnHoverListener - Callback for hovering over the view
  • setOnKeyListener - Callback for pressing a hardware key when view has focus
  • setOnLongClickListener - Callback for pressing and holding a view
  • setOnTouchListener - Callback for touching down or up on a view

Using 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 setmethod. 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
}

AdapterView Event Listeners

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 clicked
  • setOnItemLongClickListener - Callback when an item contained is clicked and held
  • setOnItemSelectedListener - Callback when an item is selected

This 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.

EditText Common Listeners

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 changed
  • setOnEditorActionListener - Fires when an "action" button on the soft keyboard is pressed

TextChangedListener

If 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.

OnEditorActionListener

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.

Input View Listeners

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.

References

Fork me on GitHub