Android中如何在EditText右侧同时显示清除图标与密码切换图标
我之前也遇到过类似的图标重叠问题,核心是要把两个图标分配到不同的区域/控件上,避免互相抢占位置。这里有几种靠谱的解决方法,推荐第一种最省心的实现方式:
方法一:利用TextInputLayout内置endIcon做密码切换,EditText自身drawableEnd做清除图标
这种方式借助TextInputLayout的原生能力处理密码切换,把清除图标交给EditText负责,两者位置自然分离,不需要复杂的自定义逻辑。
1. 布局代码
<!-- 你的自定义LoginInputLayout(继承自TextInputLayout) --> <com.yourpackage.LoginInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" <!-- 开启密码切换图标,用原生属性控制 --> app:passwordToggleEnabled="true" app:passwordToggleDrawable="@drawable/your_password_toggle_selector" app:endIconPadding="8dp"> <com.google.android.material.textfield.TextInputEditText android:id="@+id/et_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textPassword" <!-- 设置清除图标 --> android:drawableEnd="@drawable/ic_clear" android:drawablePadding="8dp" <!-- 给右侧的密码切换图标留出空间,避免重叠 --> android:paddingEnd="48dp"/> </com.yourpackage.LoginInputLayout>
2. 处理清除图标点击事件
在代码里给EditText添加触摸监听,判断点击区域是否在清除图标上:
val etPassword = findViewById<TextInputEditText>(R.id.et_password) etPassword.setOnTouchListener { v, event -> val drawableEndIndex = 2 // 对应EditText的END方向Drawable val clearDrawable = (v as EditText).compoundDrawables[drawableEndIndex] ?: return@setOnTouchListener false if (event.action == MotionEvent.ACTION_UP) { // 判断点击位置是否在清除图标范围内 if (event.rawX >= v.right - clearDrawable.bounds.width()) { v.setText("") return@setOnTouchListener true } } false }
方法二:组合两个Drawable为一个(适合必须把图标都放在EditText内的场景)
如果一定要把两个图标都作为EditText的drawableEnd,可以创建一个组合Drawable,手动控制两个图标的位置:
1. 创建组合Drawable文件 combined_end_drawables.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 清除图标在左侧,给右侧密码图标留位置 --> <item android:drawable="@drawable/ic_clear" android:right="40dp"/> <!-- 密码图标在右侧,和清除图标保持间距 --> <item android:drawable="@drawable/your_password_toggle_selector" android:left="24dp"/> </layer-list>
2. 布局中使用组合Drawable
<com.google.android.material.textfield.TextInputEditText android:id="@+id/et_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textPassword" android:drawableEnd="@drawable/combined_end_drawables"/>
3. 处理两个图标的点击事件
需要计算每个图标的点击区域,区分清除和密码切换操作:
etPassword.setOnTouchListener { v, event -> if (event.action == MotionEvent.ACTION_UP) { val combinedDrawable = (v as EditText).compoundDrawables[2] ?: return@setOnTouchListener false val clearIconWidth = resources.getDrawable(R.drawable.ic_clear).bounds.width() val passwordIconWidth = resources.getDrawable(R.drawable.your_password_toggle_selector).bounds.width() val totalDrawableWidth = combinedDrawable.bounds.width() val clickX = event.rawX - v.right + totalDrawableWidth when { // 点击清除图标区域 clickX <= clearIconWidth -> { v.setText("") return@setOnTouchListener true } // 点击密码切换图标区域 clickX >= totalDrawableWidth - passwordIconWidth -> { // 切换密码可见性 val newInputType = if (v.inputType == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD } else { InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD } v.inputType = newInputType // 保持光标在文本末尾 v.setSelection(v.text?.length ?: 0) return@setOnTouchListener true } } } false }
方法三:封装到自定义LoginInputLayout(复用性更高)
如果你的项目里多个页面需要这个效果,可以把逻辑封装到自定义的LoginInputLayout中,对外暴露简单的属性控制:
class LoginInputLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.textInputLayoutStyle ) : TextInputLayout(context, attrs, defStyleAttr) { private lateinit var editText: TextInputEditText init { editText = TextInputEditText(context).apply { layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT) // 预设右侧padding,给密码图标留空间 setPadding(paddingLeft, paddingTop, 48.dpToPx(context), paddingBottom) setOnTouchListener { v, event -> handleClearIconClick(v as EditText, event) } } addView(editText) // 读取自定义属性,判断是否启用密码模式 attrs?.let { val typedArray = context.obtainStyledAttributes(it, R.styleable.LoginInputLayout) val isPassword = typedArray.getBoolean(R.styleable.LoginInputLayout_isPassword, false) if (isPassword) { isPasswordToggleEnabled = true passwordToggleDrawable = ContextCompat.getDrawable(context, R.drawable.password_toggle_selector) editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD } typedArray.recycle() } } private fun handleClearIconClick(editText: EditText, event: MotionEvent): Boolean { val clearDrawable = editText.compoundDrawables[2] ?: return false if (event.action == MotionEvent.ACTION_UP && event.rawX >= editText.right - clearDrawable.bounds.width()) { editText.setText("") return true } return false } private fun Int.dpToPx(context: Context): Int { return (this * context.resources.displayMetrics.density).toInt() } // 对外暴露EditText,方便获取文本等操作 fun getEditText(): TextInputEditText = editText }
然后在attrs.xml中添加自定义属性:
<declare-styleable name="LoginInputLayout"> <attr name="isPassword" format="boolean"/> </declare-styleable>
使用时只需要在布局里写:
<com.yourpackage.LoginInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:isPassword="true" app:endIconPadding="8dp"/>
内容的提问来源于stack exchange,提问作者Rafa
相关产品推荐
相关产品推荐

