Handling User Input
1. Working with EditText
| Property | Description | Values |
|---|---|---|
| android:text | Initial text content | "Hello World" |
| android:hint | Placeholder text when empty | "Enter text here" |
| android:inputType | Keyboard type and behavior | "text", "number", "email" |
| android:maxLength | Maximum number of characters | 100 |
| android:enabled | Whether the view is interactive | true, false |
Example: Setting up EditText in XML
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Initial text"
android:hint="Enter your name"
android:inputType="text"
android:maxLength="50"/>
Example: Reading input from EditText in Kotlin
val editText = findViewById<EditText>(R.id.editText)
val userInput = editText.text.toString()
Example: Setting text programmatically
val editText = findViewById<EditText>(R.id.editText)
editText.setText("New text")
Note: Use
editText.text.toString() to get the user input as a string.Warning: Never use
editText.text directly in logic, as it returns an Editable object.2. Implementing TextWatcher
| Method | Description | Usage |
|---|---|---|
| afterTextChanged | Called after the text has been changed | |
| beforeTextChanged | Called before the text is changed | |
| onTextChanged | Called when the text is changed | |
Example: TextWatcher Implementation
class MyTextWatcher : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Prepare for text change
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// Handle text change in real-time
}
override fun afterTextChanged(s: Editable?) {
// Handle text change after it's applied
}
}
val editText = findViewById<EditText>(R.id.editText)
editText.addTextChangedListener(MyTextWatcher())
Note:
TextWatcher is commonly used for real-time validation or auto-saving.3. Using Input Validation
Example: Basic Input Validation
fun validateEmail(email: String): Boolean {
val emailRegex = Regex("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$")
return emailRegex.matches(email)
}
fun validatePhone(phone: String): Boolean {
val phoneRegex = Regex("^\\+?[1-9]\\d{1,14}$")
return phoneRegex.matches(phone)
}
fun validateAge(age: String): Boolean {
return age.toIntOrNull() != null && age.toInt() in 1..120
}
| Validation Type | Method | Description | Example |
|---|---|---|---|
Regex |
Checks email format using regex pattern | validateEmail("user@example.com") |
|
| Phone Number | Regex |
Validates international phone number format | validatePhone("+1234567890") |
| Age | toIntOrNull() |
Checks if input is numeric and within valid range | validateAge("25") |
Note: Always validate input on both client and server side for security.
Warning: Never trust user input without validation. Always sanitize and escape.
Input Validation Best Practices
- Validate all user inputs before processing
- Use regex patterns for structured data like emails and phone numbers
- Implement server-side validation as a backup to client-side checks
- Define clear error messages for invalid inputs
- Use built-in validation libraries where available (e.g., AndroidX Validation)
4. Handling Soft Keyboard
| Property | Description | Values |
|---|---|---|
| imeOptions | Controls the action button on the soft keyboard | actionDone, actionGo, actionNext, actionSearch, actionSend |
| inputType | Defines the type of input expected (e.g., text, number) | text, number, phone, email, textMultiLine |
Example: Setting Keyboard Options
val editText = findViewById<EditText>(R.id.editText)
editText.imeOptions = EditorInfo.IME_ACTION_DONE
editText.inputType = InputType.TYPE_CLASS_TEXT
Note: Use
imeOptions to customize the action button's label and behavior.
Warning: Ensure
inputType matches the expected input to provide appropriate keyboard layout.
5. Working with CheckBox and RadioButton
| Property | Description | Values |
|---|---|---|
| isChecked | Indicates whether the checkbox/radio button is checked | Boolean |
| text | Text displayed next to the checkbox/radio button | String |
| onCheckedChange | Callback when the state changes | (Boolean) -> Unit |
Example: CheckBox Usage
val checkBox = CheckBox(context)
checkBox.text = "Accept Terms"
checkBox.isChecked = false
checkBox.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
// Handle checked state
} else {
// Handle unchecked state
}
}
Example: RadioButton Usage
val radioButton = RadioButton(context)
radioButton.text = "Option 1"
radioButton.isChecked = false
radioButton.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
// Handle selection
}
}
Note: For grouping RadioButtons, use
RadioGroup to ensure only one is selected at a time.
CheckBox vs RadioButton
- CheckBox: Allows multiple selections
- RadioButton: Allows only one selection from a group
6. Using Spinner
| Property | Description | Values |
|---|---|---|
| android:entries | Defines the array of strings to be used as spinner items | @array/... |
| android:prompt | Text to display when the spinner is empty | @string/... |
| android:spinnerMode | Mode of the spinner (dialog or dropdown) | dialog, dropdown |
Example: Basic Spinner Setup
<Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:entries="@array/choices"
android:prompt="@string/spinner_prompt" />
Example: Spinner with ArrayAdapter
val choices = arrayOf("Option 1", "Option 2", "Option 3")
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, choices)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter
Note: The
spinnerMode attribute affects how the spinner appears on different devices.
7. Implementing Date and Time Pickers
| Component | Description | Usage |
|---|---|---|
| DatePickerDialog | Dialog for selecting a date | Use to allow users to pick a date from a calendar view |
| TimePickerDialog | Dialog for selecting a time | Use to allow users to pick a time (hours and minutes) |
Example: Using DatePickerDialog
val datePicker = DatePickerDialog(this, { _, year, month, dayOfMonth ->
// Handle date selection
val selectedDate = "$year-${month + 1}-$dayOfMonth"
}, 2024, 5, 15)
datePicker.show()
Example: Using TimePickerDialog
val timePicker = TimePickerDialog(this, { _, hourOfDay, minute ->
// Handle time selection
val selectedTime = "$hourOfDay:$minute"
}, 14, 30, true)
timePicker.show()
Note: TimePickerDialog supports both 12-hour and 24-hour formats. Set the last parameter to
true for 24-hour format.
Date and Time Picker Features
- NEW Support for custom date formats
- Easy integration with
Calendarobjects - Localization support for different date/time formats
8. Working with SeekBar and Slider
| Property | Description | Values |
|---|---|---|
| max | Maximum value the slider can reach | Int |
| progress | Current position of the slider | Int |
| thumb | Custom drawable for the slider thumb | Drawable |
| onProgressChangeListener | Callback when progress changes | OnSeekBarChangeListener |
Example: Basic SeekBar Usage
val seekBar = findViewById<SeekBar>(R.id.seekBar)
seekBar.max = 100
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
Log.d("SeekBar", "Progress: $progress")
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
Example: Custom Slider with Thumb Drawable
val slider = findViewById<Slider>(R.id.slider)
slider.valueFrom = 0f
slider.valueTo = 100f
slider.stepSize = 1f
slider.thumbDrawable = ContextCompat.getDrawable(this, R.drawable.custom_thumb)
Note:
Slider is part of AndroidX Material Components and requires the dependency com.google.android.material:material.
Warning: Ensure that
max is set before setting progress to avoid unexpected behavior.
9. Handling Touch Events
| Event Type | Description | Usage |
|---|---|---|
| MotionEvent.ACTION_DOWN | First finger touches the screen | Start of gesture recognition |
| MotionEvent.ACTION_MOVE | Finger moves on the screen | Track movement during gesture |
| MotionEvent.ACTION_UP | Last finger lifts off the screen | End of gesture, trigger action |
| MotionEvent.ACTION_CANCEL | Gesture is cancelled (e.g., system interrupt) | Reset or cancel ongoing touch handling |
Example: Basic Touch Event Handling
class MainActivity : AppCompatActivity() {
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Handle touch start
Log.d("Touch", "Down at (${event.x}, ${event.y})")
}
MotionEvent.ACTION_MOVE -> {
// Handle touch movement
Log.d("Touch", "Moving to (${event.x}, ${event.y})")
}
MotionEvent.ACTION_UP -> {
// Handle touch end
Log.d("Touch", "Up at (${event.x}, ${event.y})")
}
}
return true
}
}
Note: Returning
true from onTouchEvent indicates that the event was consumed and should not be passed to other views.
Example: Detecting Swipe Gesture
class SwipeDetector(private val listener: OnSwipeListener) : View.OnTouchListener {
private var startX = 0f
private var startY = 0f
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
startX = event.x
startY = event.y
}
MotionEvent.ACTION_UP -> {
val endX = event.x
val endY = event.y
val deltaX = endX - startX
val deltaY = endY - startY
if (abs(deltaX) > abs(deltaY)) {
if (deltaX > 50) listener.onSwipeRight()
else if (deltaX < -50) listener.onSwipeLeft()
} else {
if (deltaY > 50) listener.onSwipeDown()
else if (deltaY < -50) listener.onSwipeUp()
}
}
}
return true
}
interface OnSwipeListener {
fun onSwipeLeft()
fun onSwipeRight()
fun onSwipeUp()
fun onSwipeDown()
}
}
Touch Event Lifecycle
- ACTION_DOWN: Initial touch point detected
- ACTION_MOVE: Finger moved on screen
- ACTION_UP: Last finger lifted off screen
- ACTION_CANCEL: Gesture interrupted or cancelled
10. Implementing Gesture Detection
| Gesture | Detector Class | Description | Usage |
|---|---|---|---|
| Single Tap | GestureDetector.SimpleOnGestureListener |
Detects single tap events | |
| Double Tap | GestureDetector.SimpleOnGestureListener |
Detects double tap events | |
| Scroll | GestureDetector.SimpleOnGestureListener |
Detects scroll (fling) gestures | |
| Fling | GestureDetector.SimpleOnGestureListener |
Detects fling gestures (quick swipe) | |
Example: Detecting Swipe Gesture
class SwipeGestureDetector : GestureDetector.SimpleOnGestureListener() {
private val SWIPE_THRESHOLD = 100
private val SWIPE_VELOCITY_THRESHOLD = 100
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = e2.x - e1.x
val diffY = e2.y - e1.y
if (abs(diffX) > SWIPE_THRESHOLD && abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) {
// Swipe right
} else {
// Swipe left
}
return true
}
return false
}
}
Note: Use
onFling for swipe detection and onScroll for continuous scroll gestures.11. Using ScaleGestureDetector
| Property | Description | Notes |
|---|---|---|
| ScaleGestureDetector | Class for detecting scale gestures (pinch-to-zoom) on touch screens. | Part of Android's gesture detection system. |
| onTouchEvent(MotionEvent) | Method to pass touch events to the detector. | Must be called from the view's onTouchEvent method. |
| isInProgress() | Checks if a scale gesture is currently in progress. | Useful for determining whether to process the event further. |
Example: Basic ScaleGestureDetector Usage
class ZoomableImageView : ImageView() {
private val scaleDetector = ScaleGestureDetector(context, ScaleListener())
override fun onTouchEvent(event: MotionEvent): Boolean {
scaleDetector.onTouchEvent(event)
return true
}
private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val scale = detector.scaleFactor
// Apply zoom transformation to the view
scaleX *= scale
scaleY *= scale
return true
}
}
}
Note: The
ScaleGestureDetector works best with multi-touch input and requires handling of the MotionEvent in a custom view.