Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set two compound drawables in Android EditText

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

enter image description here

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) {}
}
like image 636
Rafa Avatar asked Nov 17 '22 13:11

Rafa


1 Answers

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) {}
}
like image 76
Rafa Avatar answered Dec 19 '22 15:12

Rafa