Skip to content

Instantly share code, notes, and snippets.

@alvindizon
Forked from ShivamPokhriyal/OTPEditText.java
Last active March 29, 2022 03:09
Show Gist options
  • Save alvindizon/1ee479494356852b20e24f32eb2f9625 to your computer and use it in GitHub Desktop.
Save alvindizon/1ee479494356852b20e24f32eb2f9625 to your computer and use it in GitHub Desktop.
A custom view to handle otp input in multiple edittexts. It will move focus to next edittext, if available, when user enters otp and it will move focus to the previous edittext, if available, when user deletes otp. It will also delegate the paste option, if user long presses and pastes a string into the otp input.
<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<declare-styleable name="OTPView">
<!-- Defines the next view to give focus to when the text is filled.-->
<attr name="nextView" format="reference"/>
<!-- Defines the previous view to give focus to when backButton is pressed.-->
<attr name="prevView" format="reference"/>
</declare-styleable>
...
</resources>
package com.yourapp.ui
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.View
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.widget.doAfterTextChanged
import com.yourapp.design.R
class VerificationCodeView : AppCompatEditText {
private var nextViewId = 0
private var previousViewId = 0
private var onPasteListener: ((String) -> Unit)? = null
private var nextView: View? = null
get() {
if (field != null) {
return field
}
if (nextViewId != NO_ID && parent is View) {
field = (parent as View).findViewById(nextViewId)
return field
}
return null
}
private var previousView: View? = null
get() {
if (field != null) {
return field
}
if (previousViewId != NO_ID && parent is View) {
field = (parent as View).findViewById(previousViewId)
return field
}
return null
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
fun setPasteListener(listener: ((String) -> Unit)) {
onPasteListener = listener
}
override fun onTextContextMenuItem(id: Int): Boolean {
return if (id == android.R.id.paste) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
// Assumes that this application can only handle one item at a time.
val item = clipboard.primaryClip!!.getItemAt(0)
item.text?.run {
onPasteListener?.invoke(toString())
true
} ?: false
} else {
super.onTextContextMenuItem(id)
}
}
override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
super.onFocusChanged(focused, direction, previouslyFocusedRect)
if (focused) text?.length?.let { setSelection(it) }
}
private fun init(context: Context, attrs: AttributeSet?) {
context.obtainStyledAttributes(attrs, R.styleable.VerificationCodeView).apply {
nextViewId = getResourceId(R.styleable.VerificationCodeView_nextView, NO_ID)
previousViewId = getResourceId(R.styleable.VerificationCodeView_previousView, NO_ID)
recycle()
}
setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_DEL && text?.isEmpty() == true) {
previousView?.requestFocus()
true
} else {
false
}
}
doAfterTextChanged { if (it?.isNotEmpty() == true) nextView?.requestFocus() }
// setOnKeyListener { _, keyCode, event ->
// if (event.action != KeyEvent.ACTION_DOWN) {
// return@setOnKeyListener true
// }
// if (keyCode == KeyEvent.KEYCODE_DEL && text?.isEmpty() == true) {
// previousView?.requestFocus()
// return@setOnKeyListener true
// }
// false
// }
//
// doAfterTextChanged { editable ->
// if (editable?.length == 1 && nextView != null) {
// nextView?.requestFocus()
// } else if (editable?.isEmpty() == true && previousView != null) {
// previousView?.requestFocus()
// }
// }
}
companion object {
private const val NO_ID = -1
}
}
...
private val editTextList by lazy {
with(binding.codeContainer) {
listOf(inputEditText1, inputEditText2, inputEditText3, inputEditText4)
}
}
...
editTextList.forEachIndexed { i, editText ->
// this gets the copied code from clipboard and distributes its digits to each edittext
editText.setPasteListener { pastedText ->
if (pastedText.length == 4) {
pastedText.forEachIndexed { index, c ->
editTextList[index].setText(c.toString())
}
// set cursor position in last EditText
editTextList.last().setSelection(1)
}
}
editText.doAfterTextChanged {
if (it?.isNotEmpty() == true) {
val code = editTextList.fold(StringBuilder()) { sb, et ->
sb.append(et.text)
}.toString()
if (code.length == 4) {
// TODO send code here
}
}
}
}
...
...
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<your_package_name.OTPEditText
android:id="@+id/otp1"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="number"
android:imeOptions="actionNext"
android:maxLength="1"
android:padding="12dp"
android:focusable="true"
app:nextView="@id/otp2"/>
<Space
android:layout_width="12dp"
android:layout_height="wrap_content" />
<your_package_name.OTPEditText
android:id="@+id/otp2"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="number"
android:imeOptions="actionNext"
android:maxLength="1"
android:padding="12dp"
android:focusable="true"
app:prevView="@id/otp1"
app:nextView="@id/otp3" />
<Space
android:layout_width="12dp"
android:layout_height="wrap_content" />
<your_package_name.OTPEditText
android:id="@+id/otp3"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="number"
android:imeOptions="actionNext"
android:maxLength="1"
android:padding="12dp"
android:focusable="true"
app:prevView="@id/otp2"
app:nextView="@id/otp4" />
<Space
android:layout_width="12dp"
android:layout_height="wrap_content" />
<your_package_name.OTPEditText
android:id="@+id/otp4"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="number"
android:imeOptions="actionDone"
android:maxLength="1"
android:padding="12dp"
android:focusable="true"
app:prevView="@id/otp3" />
</LinearLayout>
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment