Android 自定义EditText

文章目录

Android 自定义EditText

概述

定义一款可清空内容的 ClearEditText 和可显示密码的 PasswordEditText,支持修改提示图标和大小、背景图片等。

源码

基类:

kotlin 复制代码
open class BaseEditText @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.editTextStyle
) : androidx.appcompat.widget.AppCompatEditText(context, attrs, defStyleAttr) {

    companion object {
        @JvmStatic
        protected val DEFAULT_DRAWABLE = ColorDrawable(0xFFFFFFFF.toInt())
    }

    init {
        gravity = Gravity.CENTER_VERTICAL
        background = DEFAULT_DRAWABLE
    }

    override fun setLayoutParams(params: ViewGroup.LayoutParams) {
        if (params.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            params.width = ViewGroup.LayoutParams.MATCH_PARENT
        }
        super.setLayoutParams(params)
    }
}

可清空内容的EditText

定义属性:

xml 复制代码
<declare-styleable name="ClearEditText">
    <attr name="cet_deleteIcon" format="reference" />
    <attr name="cet_deleteIconSize" format="dimension" />
    <attr name="cet_tipDefaultIcon" format="reference" />
    <attr name="cet_tipSelectedIcon" format="reference" />
    <attr name="cet_tipIconSize" format="dimension" />
    <attr name="cet_defaultBg" format="color|reference" />
    <attr name="cet_selectedBg" format="color|reference" />
</declare-styleable>

定义ClearEditText:

kotlin 复制代码
class ClearEditText @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : BaseEditText(context, attrs) {

    private val deleteIconDrawable: Drawable?
    private val tipIconDefaultDrawable: Drawable?
    private val tipIconSelectedDrawable: Drawable?
    private val bgDefaultDrawable: Drawable?
    private val bgSelectedDrawable: Drawable?

    init {
        val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.ClearEditText)
        val deleteIconSize =
            a.getDimensionPixelSize(R.styleable.ClearEditText_cet_deleteIconSize, 0)
        deleteIconDrawable = a.getDrawable(R.styleable.ClearEditText_cet_deleteIcon)
        deleteIconDrawable?.let { it ->
            if (deleteIconSize > 0) {
                it.setBounds(0, 0, deleteIconSize, deleteIconSize)
            } else {
                it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
            }
        }
        val tipIconSize = a.getDimensionPixelSize(R.styleable.ClearEditText_cet_tipIconSize, 0)
        tipIconDefaultDrawable = a.getDrawable(R.styleable.ClearEditText_cet_tipDefaultIcon)
        tipIconDefaultDrawable?.let { it ->
            if (tipIconSize > 0) {
                it.setBounds(0, 0, tipIconSize, tipIconSize)
            } else {
                it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
            }
        }
        tipIconSelectedDrawable = a.getDrawable(R.styleable.ClearEditText_cet_tipSelectedIcon)
        tipIconSelectedDrawable?.let { it ->
            if (tipIconSize > 0) {
                it.setBounds(0, 0, tipIconSize, tipIconSize)
            } else {
                it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
            }
        }
        bgDefaultDrawable = a.getDrawable(R.styleable.ClearEditText_cet_defaultBg)
        bgSelectedDrawable = a.getDrawable(R.styleable.ClearEditText_cet_selectedBg)
        a.recycle()

        setup()
    }

    private fun setup() {
        setIconVisible(false, false)
        bgDefaultDrawable?.let {
            background = it
        }
    }

    override fun onTextChanged(
        text: CharSequence,
        start: Int,
        lengthBefore: Int,
        lengthAfter: Int
    ) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter)
        setIconVisible(hasFocus() && text.length > 0, hasFocus())
    }

    override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect)
        setIconVisible(focused && length() > 0, focused)
    }

    private fun setIconVisible(deleteIconVisible: Boolean, focused: Boolean) {
        setCompoundDrawablesRelative(
            if (focused) tipIconSelectedDrawable else tipIconDefaultDrawable,
            null,
            if (deleteIconVisible) deleteIconDrawable else null,
            null
        )
        if (bgDefaultDrawable != null && bgSelectedDrawable != null) {
            background = if (focused) bgSelectedDrawable else bgDefaultDrawable
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.action == MotionEvent.ACTION_UP) {
            val drawable = deleteIconDrawable
            if (drawable != null) {
                if (event.x <= width - paddingRight && event.x >= width - paddingRight - drawable.bounds.width()) {
                    text = null
                }
            }
        }
        return super.onTouchEvent(event)
    }
}

可显示密码的EditText

定义属性:

xml 复制代码
<declare-styleable name="PasswordEditText">
    <attr name="pet_eyeIconSize" format="dimension" />
    <attr name="pet_tipDefaultIcon" format="reference" />
    <attr name="pet_tipSelectedIcon" format="reference" />
    <attr name="pet_tipIconSize" format="dimension" />
    <attr name="pet_defaultBg" format="color|reference" />
    <attr name="pet_selectedBg" format="color|reference" />
</declare-styleable>

定义PasswordEditText:

kotlin 复制代码
class PasswordEditText @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : BaseEditText(context, attrs) {

    private val eyeOpenDrawable: Drawable?
    private val eyeCloseDrawable: Drawable?
    private var currentEyeDrawable: Drawable? = null
    private var tipIconDefaultDrawable: Drawable?
    private var tipIconSelectedDrawable: Drawable?
    private var bgDefaultDrawable: Drawable?
    private var bgSelectedDrawable: Drawable?

    init {
        val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.PasswordEditText)

        val eyeIconSize = a.getDimensionPixelSize(R.styleable.PasswordEditText_pet_eyeIconSize, 0)
        eyeOpenDrawable = ContextCompat.getDrawable(context, R.drawable.eye_open)
        eyeOpenDrawable?.let {
            if (eyeIconSize > 0) {
                it.setBounds(0, 0, eyeIconSize, eyeIconSize)
            } else {
                it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
            }
        }
        eyeCloseDrawable = ContextCompat.getDrawable(context, R.drawable.eye_close)
        eyeCloseDrawable?.let {
            if (eyeIconSize > 0) {
                it.setBounds(0, 0, eyeIconSize, eyeIconSize)
            } else {
                it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
            }
        }
        val tipIconSize = a.getDimensionPixelSize(R.styleable.PasswordEditText_pet_tipIconSize, 0)
        tipIconDefaultDrawable = a.getDrawable(R.styleable.PasswordEditText_pet_tipDefaultIcon)
        tipIconDefaultDrawable?.let { it ->
            if (tipIconSize > 0) {
                it.setBounds(0, 0, tipIconSize, tipIconSize)
            } else {
                it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
            }
        }
        tipIconSelectedDrawable = a.getDrawable(R.styleable.PasswordEditText_pet_tipSelectedIcon)
        tipIconSelectedDrawable?.let { it ->
            if (tipIconSize > 0) {
                it.setBounds(0, 0, tipIconSize, tipIconSize)
            } else {
                it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
            }
        }
        bgDefaultDrawable = a.getDrawable(R.styleable.PasswordEditText_pet_defaultBg)
        bgSelectedDrawable = a.getDrawable(R.styleable.PasswordEditText_pet_selectedBg)
        a.recycle()

        setup()
    }

    private fun setup() {
        setIconVisible(false, false)
        currentEyeDrawable = eyeCloseDrawable
        bgDefaultDrawable?.let {
            background = it
        }
        inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
        transformationMethod = PasswordTransformationMethod.getInstance()
    }

    override fun onTextChanged(
        text: CharSequence,
        start: Int,
        lengthBefore: Int,
        lengthAfter: Int
    ) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter)
        setIconVisible(hasFocus() && text.length > 0, hasFocus())
    }

    override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect)
        setIconVisible(focused && length() > 0, focused)
    }

    private fun setIconVisible(pwdIconVisible: Boolean, focused: Boolean) {
        setCompoundDrawablesRelative(
            if (focused) tipIconSelectedDrawable else tipIconDefaultDrawable,
            null,
            if (pwdIconVisible) currentEyeDrawable else null,
            null
        )
        if (bgDefaultDrawable != null && bgSelectedDrawable != null) {
            background = if (focused) bgSelectedDrawable else bgDefaultDrawable
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.action == MotionEvent.ACTION_UP) {
            val drawable = currentEyeDrawable
            if (drawable != null) {
                if (event.x <= width - paddingRight && event.x >= width - paddingRight - drawable.bounds.width()) {
                    if (drawable == eyeOpenDrawable) {
                        // 密码不可见
                        currentEyeDrawable = eyeCloseDrawable
                        transformationMethod = PasswordTransformationMethod.getInstance()
                        refreshDrawables()
                    } else if (drawable == eyeCloseDrawable) {
                        // 密码可见
                        currentEyeDrawable = eyeOpenDrawable
                        transformationMethod = HideReturnsTransformationMethod.getInstance()
                        refreshDrawables()
                    }
                }
            }
        }
        return super.onTouchEvent(event)
    }

    private fun refreshDrawables() {
        val drawables = compoundDrawablesRelative
        setCompoundDrawablesRelative(drawables[0], drawables[1], currentEyeDrawable, drawables[3])
    }
}

使用

xml 复制代码
<com.example.widgets.custom_edittext.ClearEditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginHorizontal="30dp"
    android:layout_marginTop="60dp"
    android:hint="请输入用户名"
    android:padding="10dp"
    android:singleLine="true"
    android:textSize="20sp"
    app:cet_defaultBg="@drawable/shape_border_gray"
    app:cet_deleteIcon="@drawable/ic_delete_x"
    app:cet_deleteIconSize="30dp"
    app:cet_selectedBg="@drawable/shape_border_blue"
    app:cet_tipDefaultIcon="@drawable/ic_user_gray"
    app:cet_tipIconSize="30dp"
    app:cet_tipSelectedIcon="@drawable/ic_user_blue" />

<com.example.widgets.custom_edittext.ClearEditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginHorizontal="30dp"
    android:layout_marginTop="30dp"
    android:hint="请输入手机号"
    android:inputType="phone"
    android:padding="10dp"
    android:textSize="20sp"
    app:cet_defaultBg="@drawable/shape_border_gray"
    app:cet_deleteIcon="@drawable/ic_delete_x"
    app:cet_deleteIconSize="30dp"
    app:cet_selectedBg="@drawable/shape_border_blue"
    app:cet_tipDefaultIcon="@drawable/ic_user_gray"
    app:cet_tipIconSize="30dp"
    app:cet_tipSelectedIcon="@drawable/ic_user_blue" />

<com.example.widgets.custom_edittext.PasswordEditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginHorizontal="30dp"
    android:layout_marginTop="30dp"
    android:hint="请输入密码"
    android:padding="10dp"
    android:textSize="20sp"
    app:pet_defaultBg="@drawable/shape_border_gray"
    app:pet_selectedBg="@drawable/shape_border_blue"
    app:pet_tipDefaultIcon="@drawable/ic_lock_gray"
    app:pet_tipIconSize="30dp"
    app:pet_tipSelectedIcon="@drawable/ic_lock_blue" />

源码下载

相关推荐
inmK11 小时前
蓝奏云官方版不好用?蓝云最后一版实测:轻量化 + 不限速(避更新坑) 蓝云、蓝奏云第三方安卓版、蓝云最后一版、蓝奏云无广告管理工具、安卓网盘轻量化 APP
android·工具·网盘工具
giaoho1 小时前
Android 热点开发的相关api总结
android
咖啡の猫3 小时前
Android开发-常用布局
android·gitee
程序员老刘3 小时前
Google突然“变脸“,2026年要给全球开发者上“紧箍咒“?
android·flutter·客户端
Tans54 小时前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
雨白4 小时前
实现双向滑动的 ScalableImageView(下)
android
峥嵘life4 小时前
Android Studio新版本编译release版本apk实现
android·ide·android studio
studyForMokey6 小时前
【Android 消息机制】Handler
android
敲代码的鱼哇6 小时前
跳转原生系统设置插件 支持安卓/iOS/鸿蒙UTS组件
android·ios·harmonyos
翻滚丷大头鱼6 小时前
android View详解—动画
android