I have the below TextInputEditTexts
nested inside custom TextInputLayouts
and I'm wanting to have both the "x" icon and the password toggle visible at the same time. However, the eye toggle is overriding the "x" icon.
I have a custom TextInputLayout
called LoginInputLayout
where I'm trying to add two drawables over on the right hand side of the password editText, but I keep only getting the eye icon.
How can I add the two drawables over on the right hand side and not have one override the other? Like in the image below.
This is the design that I'm trying to get to
In the Android implementation for the parent LayoutInputTextView
it looks like the first child is actually a FrameLayout
and a child of that FL is the TextInputEditText
.
When the password toggle (which causes the eye to appear) is set to show, it looks like the android implementation inflates the view for the toggle, and sets it inside the FrameLayout like below.
if (shouldShowPasswordIcon()) {
if (mPasswordToggleView == null) {
mPasswordToggleView = (CheckableImageButton) LayoutInflater.from(getContext())
.inflate(R.layout.design_text_input_password_icon, mInputFrame, false);
mPasswordToggleView.setImageDrawable(mPasswordToggleDrawable);
mPasswordToggleView.setContentDescription(mPasswordToggleContentDesc);
mInputFrame.addView(mPasswordToggleView);
mPasswordToggleView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
passwordVisibilityToggleRequested(false);
}
});
}
The only thing is, the mFrameLayout member variable is private and I can't add more children in there or control where they're placed. Which is why I feel like I'm limited to trying the compound drawable way.
<com.ge.cbyge.view.LoginInputTextLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
app:error="@{viewModel.emailError}">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_fragment_email_text"
android:focusable="true"
android:focusableInTouchMode="true"
android:inputType="textEmailAddress"
android:maxLines="1"
android:text="@={viewModel.email}"/>
</com.ge.cbyge.view.LoginInputTextLayout>
<com.ge.cbyge.view.LoginInputTextLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/placeholder_dimen"
android:maxLines="1"
app:error="@{viewModel.passwordError}"
app:passwordToggleEnabled="true">
<android.support.design.widget.TextInputEditText
android:id="@+id/password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_fragment_password_text"
android:focusable="true"
android:focusableInTouchMode="true"
android:text="@={viewModel.password}"/>
</com.ge.cbyge.view.LoginInputTextLayout>
This is my custom implementation of the TextInputLayout
class LoginInputTextLayout : TextInputLayout, TextWatcher {
lateinit var clearTextIcon: Drawable
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams) {
super.addView(child, index, params)
if(child is EditText) {
Timber.d("$TAG child was an editText")
if (editText != null) {
Timber.d("$TAG initializing the clearText")
init(context)
}
}
}
private fun init(context: Context) {
val drawable = ContextCompat.getDrawable(context, R.drawable.abc_ic_clear_material)
DrawableCompat.setTint(drawable, editText!!.currentHintTextColor)
clearTextIcon = drawable
clearTextIcon.setBounds(0, 0, clearTextIcon.intrinsicHeight, clearTextIcon.intrinsicHeight)
setClearIconVisible(false)
editText!!.transformationMethod = PasswordTransformationMethod.getInstance()
editText!!.setOnTouchListener(onTouchListener)
editText!!.setOnFocusChangeListener(focusChangeListener)
editText!!.addTextChangedListener(this)
}
private val onTouchListener: View.OnTouchListener = OnTouchListener { v, event ->
val x = event.x.toInt()
if (clearTextIcon.isVisible && x > width - paddingRight - clearTextIcon.intrinsicWidth) {
if (event.action == MotionEvent.ACTION_UP) {
editText?.setText("")
}
return@OnTouchListener true
}
return@OnTouchListener false
}
private val focusChangeListener: View.OnFocusChangeListener = OnFocusChangeListener { v, hasFocus ->
if (hasFocus) {
setClearIconVisible(editText!!.text.isNotEmpty())
} else {
setClearIconVisible(false)
}
}
private fun setClearIconVisible(visible: Boolean) {
clearTextIcon.setVisible(visible, false)
val compoundDrawables = TextViewCompat.getCompoundDrawablesRelative(editText!!)
TextViewCompat.setCompoundDrawablesRelative(
editText!!,
compoundDrawables[0],
compoundDrawables[1],
if (visible) clearTextIcon else null,
compoundDrawables[3])
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (editText!!.isFocused) {
setClearIconVisible(s.isNotEmpty())
}
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun afterTextChanged(s: Editable) {}
}
I was able to get this to work, straying away from the composite disposable right. I was able to snag the FrameLayout
in the addView()
method, detect if it had two children (meaning the editText AND the eye logo) and if it did, set the "x" marker to the left of the eye.
class LoginInputTextLayout : TextInputLayout, TextWatcher {
private lateinit var clearTextIcon: ImageView
private lateinit var frameLayout: FrameLayout
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams) {
super.addView(child, index, params)
if (child is FrameLayout) {
frameLayout = child
}
if(child is EditText) {
if (editText != null) {
init()
}
}
}
private fun init() {
initClearTextIcon(isPasswordVisibilityToggleEnabled)
editText!!.setOnFocusChangeListener(focusChangeListener)
editText!!.addTextChangedListener(this)
}
private fun initClearTextIcon(passwordToggleEnabled: Boolean) {
val drawable = ContextCompat.getDrawable(context, R.drawable.abc_ic_clear_material)
DrawableCompat.setTint(drawable, editText!!.currentHintTextColor)
clearTextIcon = LayoutInflater.from(context).inflate(R.layout.design_text_input_password_icon, frameLayout, false) as ImageView
clearTextIcon.maxHeight = editText!!.height
clearTextIcon.setImageDrawable(drawable)
clearTextIcon.setOnClickListener {
editText?.setText("")
}
if (passwordToggleEnabled) {
val shiftedClearTextIcon = clearTextIcon
shiftedClearTextIcon.setPadding(0,0, passwordVisibilityToggleDrawable!!.intrinsicWidth * 2, 0)
frameLayout.addView(clearTextIcon)
editText!!.transformationMethod = PasswordTransformationMethod.getInstance()
} else {
frameLayout.addView(clearTextIcon)
}
setClearIconVisible(false)
}
private val focusChangeListener: View.OnFocusChangeListener = OnFocusChangeListener { v, hasFocus ->
if (hasFocus) {
setClearIconVisible(editText!!.text.isNotEmpty())
} else {
setClearIconVisible(false)
}
}
private fun setClearIconVisible(visible: Boolean) {
if (visible) clearTextIcon.visibility = View.VISIBLE else clearTextIcon.visibility = View.GONE
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (editText!!.isFocused) {
setClearIconVisible(s.isNotEmpty())
}
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun afterTextChanged(s: Editable) {}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With