'Edittext togglepassword dots changes to '*' for the first time and then reappears again as dots

I have used Textinputlayout and edittext inside it. And have used one method to change the default icon for dots to * , but once clicked on eye icon it again changes it to dots only. How to manage this?

I want to implement like shown in the image for everytime it clicks the eye icon

Below is my code:-

.java file

oldpw=(EditText) findViewById(R.id.oldpw);
oldpw.setTransformationMethod(new AsteriskPasswordTransformationMethod());

public class AsteriskPasswordTransformationMethod extends PasswordTransformationMethod {
    @Override
    public CharSequence getTransformation(CharSequence source, View view) {
        return new PasswordCharSequence(source);
    }

    private class PasswordCharSequence implements CharSequence {
        private CharSequence mSource;
        public PasswordCharSequence(CharSequence source) {
            mSource = source; // Store char sequence
        }
        public char charAt(int index) {
            return '*'; // This is the important part
        }
        public int length() {
            return mSource.length(); // Return default
        }
        public CharSequence subSequence(int start, int end) {
            return mSource.subSequence(start, end); // Return default
        }
    }
};

xml file

<android.support.design.widget.TextInputLayout
    android:layout_marginTop="15dp"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:layout_marginBottom="15dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:passwordToggleEnabled="true"
    app:passwordToggleDrawable="@drawable/show_password_selector"
    app:passwordToggleTint="#989898"
    android:id="@+id/et1"
    android:padding="0dp">

    <EditText

        android:hint="Old Password"
        android:inputType="textPassword"
        android:id="@+id/oldpw"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>


</android.support.design.widget.TextInputLayout>


Solution 1:[1]

Below code worked for me.

    class MainActivity : AppCompatActivity() {
    
        var tiet_password:TextInputEditText? = null
        var til_password: TextInputLayout? = null
        var isPasswordVisible = false
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            tiet_password = findViewById(R.id.tiet_password)
            til_password = findViewById(R.id.til_password)
    
            tiet_password?.transformationMethod = AsteriskPassword()
    
            with(til_password) { this?.addEndIconClickListener() }
    
        }
    }
    
    private fun TextInputLayout.addEndIconClickListener() {
        var isPasswordVisible = false
        this.setEndIconOnClickListener {
            if (isPasswordVisible) {
                isPasswordVisible = false
                this.editText!!.transformationMethod = AsteriskPasswordTransformationMethod()
            } else {
                isPasswordVisible = true
                this.editText!!.transformationMethod = HideReturnsTransformationMethod()
            }
        }
    }
    
    
    class AsteriskPassword : PasswordTransformationMethod() {
        override fun getTransformation(source: CharSequence, view: View): CharSequence {
            return PasswordCharSequence(source)
        }
    
        private inner class PasswordCharSequence(private val mSource: CharSequence) :
            CharSequence {
    
            override val length: Int
                get() = mSource.length
    
            override fun get(index: Int): Char {
                return '*'
            }
    
            override fun subSequence(start: Int, end: Int): CharSequence {
                return mSource.subSequence(start, end)
            }
        }
    }


    
    private const val DOT = '*'
    
    class AsteriskPasswordTransformationMethod : PasswordTransformationMethod() {
    
        private val ACTIVE: Any = NoCopySpan.Concrete()
    
        override fun getTransformation(source: CharSequence?, view: View?): CharSequence {
            if (source is Spannable) {
                val vr = source.getSpans(0, source.length, ViewReference::class.java)
                vr.forEach { source.removeSpan(it) }
    
                removeVisibleSpans(source)
                source.setSpan(ViewReference(view!!), 0, 0, Spannable.SPAN_POINT_POINT)
            }
            return PasswordCharSequence(source!!)
        }
    
        private fun removeVisibleSpans(sp: Spannable) {
            val old = sp.getSpans(0, sp.length, Visible::class.java)
            old.forEach { sp.removeSpan(it) }
        }
    
        private inner class PasswordCharSequence(private val mSource: CharSequence) : CharSequence, GetChars {
    
            override val length: Int
                get() = mSource.length
    
            override fun get(index: Int): Char {
                if (mSource is Spanned) {
                    var st = mSource.getSpanStart(ACTIVE)
                    var en = mSource.getSpanEnd(ACTIVE)
    
                    if (index in st until en) return mSource[index]
    
                    val visible = mSource.getSpans(0, mSource.length, Visible::class.java)
                    visible.forEach {
                        if (mSource.getSpanStart(it.mTransformer) >= 0) {
                            st = mSource.getSpanStart(it)
                            en = mSource.getSpanEnd(it)
                            if (index in st until en) return mSource[index]
                        }
                    }
                }
                return DOT
            }
    
            override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
                val buf = CharArray(endIndex - startIndex)
                getChars(startIndex, endIndex, buf, 0)
                return String(buf)
            }
    
            override fun toString(): String = subSequence(0, length).toString()
    
            override fun getChars(start: Int, end: Int, dest: CharArray?, destoff: Int) {
                TextUtils.getChars(mSource, start, end, dest, destoff)
    
                var st = -1
                var en = -1
                var nvisible = 0
                var starts: IntArray? = null
                var ends: IntArray? = null
    
                if (mSource is Spanned) {
                    st = mSource.getSpanStart(ACTIVE)
                    en = mSource.getSpanEnd(ACTIVE)
    
                    val visible = mSource.getSpans(0, mSource.length, Visible::class.java)
                    nvisible = visible.size
                    starts = IntArray(nvisible)
                    ends = IntArray(nvisible)
    
                    for (i in 0 until nvisible) {
                        if (mSource.getSpanStart(visible[i].mTransformer) >= 0) {
                            starts[i] = mSource.getSpanStart(visible[i])
                            ends[i] = mSource.getSpanEnd(visible[i])
                        }
                    }
                }
    
                for (i in start until end) {
                    if (i !in st until en) {
                        var visible = false
    
                        for (a in 0 until nvisible) {
                            if (i >= starts!![a] && i < ends!![a]) {
                                visible = true
                                break
                            }
                        }
    
                        if (!visible) dest?.set(i - start + destoff, DOT)
                    }
                }
            }
        }
    
        private inner class Visible(private val mText: Spannable, val mTransformer: AsteriskPasswordTransformationMethod) : Handler(
            Looper.getMainLooper()), UpdateLayout, Runnable {
            init {
                postAtTime(this, SystemClock.uptimeMillis() + 1000)
            }
    
            override fun run() {
                mText.removeSpan(this)
            }
        }
        private inner class ViewReference(v: View) : WeakReference<View>(v), NoCopySpan
    
    }

XML file is:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/til_email"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:layout_marginTop="32dp"
        android:hint="Email">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/tiet_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textEmailAddress"
            android:paddingBottom="20dp"
            android:singleLine="true" />
    </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/til_password"
            android:layout_width="match_parent"
            android:layout_height="90dp"
            android:hint="Password"
            android:longClickable="false"
            android:layout_gravity="center"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"
            android:gravity="center"
            app:passwordToggleEnabled="true">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/tiet_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textWebPassword"
                android:paddingBottom="20dp"
                android:drawableEnd="@drawable/password_enabled_eye"/>

        </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:paddingStart="50dp"
        android:paddingEnd="50dp"
        android:text="Login"/>

</LinearLayout>

Solution 2:[2]

Use this method:

In you XML layout

<EditText
    android:id="@+id/etPassword"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="Enter Password"
    android:inputType="textPassword"
    android:drawableEnd="@drawable/eye" <!--setting eye icon here-->
    /> 

Then in your Java class.

set Boolean globally.

Boolean isPasswordVisible = false;

Then add this in onCreate()

 etPassword.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            final int DRAWABLE_RIGHT = 2;

            if(event.getAction() == MotionEvent.ACTION_UP) {
                if(event.getRawX() >= (etPassword.getRight() - etPassword.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
                    if (isPasswordVisible) {
                        isPasswordVisible = false;
                        etPassword.setTransformationMethod(new PasswordTransformationMethod());
                    } else {
                        isPasswordVisible = true;
                        etPassword.setTransformationMethod(new HideReturnsTransformationMethod());
                    }
                    return true;
                }
            }
            return false;
        }
    });

Hope this helps you !

Solution 3:[3]

I have made this custom class with my modification. It works even you click on Password Toogle Icon. It works every time.

Custom Password Transformation Class:


private const val DOT = '?'

class AsteriskPasswordTransformationMethod : PasswordTransformationMethod() {

    private val ACTIVE: Any = NoCopySpan.Concrete()

    override fun getTransformation(source: CharSequence?, view: View?): CharSequence {
        if (source is Spannable) {
            /*
             * Remove any references to other views that may still be
             * attached.  This will happen when you flip the screen
             * while a password field is showing; there will still
             * be references to the old EditText in the text.
             */
            val vr = source.getSpans(0, source.length, ViewReference::class.java)
            vr.forEach { source.removeSpan(it) }

            removeVisibleSpans(source)
            source.setSpan(ViewReference(view!!), 0, 0, Spannable.SPAN_POINT_POINT)
        }
        return PasswordCharSequence(source!!)
    }

    private fun removeVisibleSpans(sp: Spannable) {
        val old = sp.getSpans(0, sp.length, Visible::class.java)
        old.forEach { sp.removeSpan(it) }
    }

    private inner class PasswordCharSequence(private val mSource: CharSequence) : CharSequence, GetChars {

        override val length: Int
            get() = mSource.length

        override fun get(index: Int): Char {
            if (mSource is Spanned) {
                var st = mSource.getSpanStart(ACTIVE)
                var en = mSource.getSpanEnd(ACTIVE)

                if (index in st until en) return mSource[index]

                val visible = mSource.getSpans(0, mSource.length, Visible::class.java)
                visible.forEach {
                    if (mSource.getSpanStart(it.mTransformer) >= 0) {
                        st = mSource.getSpanStart(it)
                        en = mSource.getSpanEnd(it)
                        if (index in st until en) return mSource[index]
                    }
                }
            }
            return DOT
        }

        override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
            val buf = CharArray(endIndex - startIndex)
            getChars(startIndex, endIndex, buf, 0)
            return String(buf)
        }

        override fun toString(): String = subSequence(0, length).toString()

        override fun getChars(start: Int, end: Int, dest: CharArray?, destoff: Int) {
            TextUtils.getChars(mSource, start, end, dest, destoff)

            var st = -1
            var en = -1
            var nvisible = 0
            var starts: IntArray? = null
            var ends: IntArray? = null

            if (mSource is Spanned) {
                st = mSource.getSpanStart(ACTIVE)
                en = mSource.getSpanEnd(ACTIVE)

                val visible = mSource.getSpans(0, mSource.length, Visible::class.java)
                nvisible = visible.size
                starts = IntArray(nvisible)
                ends = IntArray(nvisible)

                for (i in 0 until nvisible) {
                    if (mSource.getSpanStart(visible[i].mTransformer) >= 0) {
                        starts[i] = mSource.getSpanStart(visible[i])
                        ends[i] = mSource.getSpanEnd(visible[i])
                    }
                }
            }

            for (i in start until end) {
                if (i !in st until en) {
                    var visible = false

                    for (a in 0 until nvisible) {
                        if (i >= starts!![a] && i < ends!![a]) {
                            visible = true
                            break
                        }
                    }

                    if (!visible) dest?.set(i - start + destoff, DOT)
                }
            }
        }
    }

    private inner class Visible(private val mText: Spannable, val mTransformer: AsteriskPasswordTransformationMethod) : Handler(Looper.getMainLooper()), UpdateLayout, Runnable {
        init {
            postAtTime(this, SystemClock.uptimeMillis() + 1000)
        }

        override fun run() {
            mText.removeSpan(this)
        }
    }

    /**
     * Used to stash a reference back to the View in the Editable so we
     * can use it to check the settings.
     */
    private inner class ViewReference(v: View) : WeakReference<View>(v), NoCopySpan

}

Use it in TextInputor EditText like this. I created an Extension for this. Check:

fun TextInputLayout.addEndIconClickListener() {
    var isPasswordVisible = false
    this.setEndIconOnClickListener {
        if (isPasswordVisible) {
            isPasswordVisible = false
            this.editText!!.transformationMethod = AsteriskPasswordTransformationMethod()
        } else {
            isPasswordVisible = true
            this.editText!!.transformationMethod = HideReturnsTransformationMethod()
        }
    }
}

Then in Fragment or Activity, set Transformation Method like this.

        binding.etPin.editText?.transformationMethod = AsteriskPasswordTransformationMethod()
        binding.etPin.addEndIconClickListener()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Nafis Kabbo