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" />

源码下载

相关推荐
服装学院的IT男18 分钟前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android
Arms20621 分钟前
android 全面屏最底部栏沉浸式
android
服装学院的IT男23 分钟前
【Android 源码分析】Activity生命周期之onStop-1
android
ChinaDragonDreamer3 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
网络研究院5 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下5 小时前
android navigation 用法详细使用
android
小比卡丘8 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭9 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss10 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.11 小时前
数据库语句优化
android·数据库·adb