扩展EditText-支持IconFont图标

扩展EditText-支持IconFont图标


扩展EditText
自带清空按钮 显示/隐藏密码按钮(右图标)
支持IconFont图标

源码
/**
 * 创建者:  TomCat0916
 * 创建时间:  2020/10/25
 * 功能描述:  可配置显示/隐藏按钮(设置密码的显示隐藏) 或 配置清除按钮(清空输入框)  三者同时配置优先清空模式
 * 支持IconFont
 *
 * 示例一:图片模式
 * ```
 *  <com.*.widget.ClearEditTextView2
 *      ...
 *     app:DrawableInvisible="@mipmap/icon_on"
 *     app:DrawableVisible="@mipmap/icon_off"/>
 * ```
 *示例二:IconFont模式
 * ```
 *  <com.*.ClearEditTextView2
 *      ...
 *      app:IconFontLeft="image"
 *      app:IconFontVisible="off"
 *      app:IconFontInvisible="on"/>
 * ```
 *  属性设置参考[package.R.styleable.ClearEditTextView2]
 */
class ClearEditTextView2 @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.editTextStyle
) : androidx.appcompat.widget.AppCompatEditText(context, attrs, defStyleAttr),
    OnFocusChangeListener, TextWatcher, LifecycleObserver {

    private var mClearDrawable: Drawable? = null
    private var visibleDrawable: Drawable? = null
    private var invisibleDrawable: Drawable? = null
    private var onTouchListener: OnTouchListener? = null
    private var translateAnimation: Animation? = null

    private var invisibleText: CharSequence? = null
    private var clearText: CharSequence? = null
    private var leftIconText: CharSequence? = null
    private var visibleText: CharSequence? = null

    private var iconFontTextSize = 0
    private var iconFontTextColor = Color.BLACK
    private var iconFontBackgroundColor = Color.TRANSPARENT
    private var iconFontPath: String? = null
    private var leftIconFontTextLayout: Layout? = null
    private var rightIconFontTextLayout: Layout? = null
    private var hasData = false
    private var oldPaddingLeft = 0
    private var oldPaddingTop = 0
    private var oldPaddingRight = 0
    private var oldPaddingBottom = 0
    private var isLeftIconDraw = false
    private var isRightIconDraw = false
    private var iconLeftPadding = 0f
    private var iconRightPadding = 0f
    private var leftIconW = 0
    private var rightIconW = 0
    private var leftRecFBg: RectF? = null
    private var rightRecFBg: RectF? = null
    private var iconFontH = 0f
    private var iconFontDrawPadding = 0f
    private var lastVisibleState = true
    private var lastClearShowState = false
    private var maxRightTextW = 0
    private val textMap by lazy {
        HashMap<String, TextBean>()
    }

    private val iconFontPaint: TextPaint by lazy {
        TextPaint().apply {
            isAntiAlias = false
            color = iconFontTextColor
            textSize = iconFontTextSize.toFloat()
            style = Paint.Style.FILL
        }
    }
    private val bgPaint: Paint by lazy {
        Paint().apply {
            isAntiAlias = false
            color = iconFontBackgroundColor
            style = Paint.Style.FILL
        }
    }

    private companion object {
        private const val CLEAR_HIDE_MODEL = 0
        private const val CLEAR_SHOW_MODEL = 1
        private const val INVISIBLE_MODEL = 2
        private const val VISIBLE_MODEL = 3
    }

    private var rightIconModel = CLEAR_HIDE_MODEL//0清理按钮隐藏 1清理按钮显示 2 明文显示按钮 3明文关闭按钮

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        onFocusChangeListener = null
        removeTextChangedListener(this)
        mClearDrawable = null
        visibleDrawable = null
        invisibleDrawable = null
        onTouchListener = null
        if (translateAnimation != null) {
            translateAnimation!!.cancel()
            translateAnimation = null
        }
        if (context is LifecycleOwner) {
            (context as LifecycleOwner).lifecycle.removeObserver(this)
        }

        leftIconFontTextLayout = null
        rightIconFontTextLayout = null
        leftRecFBg = null
        rightRecFBg = null
        textMap.clear()
    }

    init {
        if (context is LifecycleOwner) {
            (context as LifecycleOwner).lifecycle.addObserver(this)
        }
        val array =
            context.obtainStyledAttributes(attrs, R.styleable.ClearEditTextView2)
        if (array != null) {
            mClearDrawable = array.getDrawable(R.styleable.ClearEditTextView2_DrawableClear)
            visibleDrawable = array.getDrawable(R.styleable.ClearEditTextView2_DrawableVisible)
            invisibleDrawable = array.getDrawable(R.styleable.ClearEditTextView2_DrawableInvisible)
            leftIconText = array.getText(R.styleable.ClearEditTextView2_IconFontLeft)
            clearText = array.getText(R.styleable.ClearEditTextView2_IconFontClear)
            visibleText = array.getText(R.styleable.ClearEditTextView2_IconFontVisible)
            invisibleText = array.getText(R.styleable.ClearEditTextView2_IconFontInvisible)
            iconFontPath = array.getString(R.styleable.ClearEditTextView2_IconFontPath)
            iconFontTextColor =
                array.getColor(R.styleable.ClearEditTextView2_IconFontTextColor, paint.color)

            iconFontBackgroundColor =
                array.getColor(
                    R.styleable.ClearEditTextView2_IconFontBackground,
                    iconFontBackgroundColor
                )

            iconFontTextSize = array.getDimensionPixelSize(
                R.styleable.ClearEditTextView2_IconFontTextSize, paint.textSize.toInt()
            )
            array.recycle()
        }
        setSingleLine()
        init()
    }

    private fun init() {
        if (isIconFontModel()) {//优先IconFont模式
            //设置IconFont文本字体样式
            setIconFontPath(iconFontPath ?: BaseApplication.getInstance().globalIconFontPath)
            if (clearText?.isNotEmpty() == true) {
                lastClearShowState = false
                //默认隐藏清除按钮状态 (优先)
                rightIconModel = CLEAR_HIDE_MODEL
                //初始化清除按钮IconFont
                addIconFontText(clearText?.toString(), true)
            } else if (visibleText?.isNotEmpty() == true
                && invisibleText?.isNotEmpty() == true
            ) {
                //初始化显示按钮IconFont
                addIconFontText(visibleText?.toString(), true)
                //初始化隐藏按钮IconFont
                addIconFontText(invisibleText?.toString(), true)
                lastVisibleState = true
                //默认设置隐藏状态
                inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
                rightIconModel = INVISIBLE_MODEL
                setVisibleIcon()
            }
            //记录原本padding距离
            oldPaddingLeft = paddingLeft
            oldPaddingTop = paddingTop
            oldPaddingRight = paddingRight
            oldPaddingBottom = paddingBottom
            if (leftIconText?.isNotEmpty() == true) {
                //初始化左边IconFont
                addIconFontText(leftIconText?.toString(), false)
            }
        } else if (mClearDrawable != null) {
            //设置删除按钮的边界
            mClearDrawable!!.setBounds(
                0,
                0,
                mClearDrawable!!.intrinsicWidth,
                mClearDrawable!!.intrinsicHeight
            )
            rightIconModel = CLEAR_HIDE_MODEL
            //默认隐藏删除按钮
            setClearIcon(false)
            //监听EditText焦点变化,以根据text长度控制删除按钮的显示、隐藏
            onFocusChangeListener = this
            //监听文本内容变化
            addTextChangedListener(this)
        } else if (visibleDrawable != null && invisibleDrawable != null) {
            inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
            //设置显示按钮的边界
            visibleDrawable!!.setBounds(
                0,
                0,
                visibleDrawable!!.intrinsicWidth,
                visibleDrawable!!.intrinsicHeight
            )
            //设置隐藏按钮的边界
            invisibleDrawable!!.setBounds(
                0,
                0,
                invisibleDrawable!!.intrinsicWidth,
                invisibleDrawable!!.intrinsicHeight
            )
            rightIconModel = INVISIBLE_MODEL
            lastVisibleState = true
            setVisibleIcon()
        }
    }

    /**
     * 根据状态获取右边IconFont内容文字
     */
    private fun getRightIcon(): CharSequence? {
        return when (rightIconModel) {
            VISIBLE_MODEL -> visibleText
            INVISIBLE_MODEL -> invisibleText
            CLEAR_SHOW_MODEL -> clearText
            else -> ""
        }
    }

    private fun isIconFontModel(): Boolean {
        return (leftIconText?.isNotEmpty() == true
                || clearText?.isNotEmpty() == true
                || visibleText?.isNotEmpty() == true
                || invisibleText?.isNotEmpty() == true
                || iconFontPath?.isNotEmpty() == true)
    }

    /**
     * 设置IconFont字体样式文件(优先assets下字体文件)
     *
     * @param path 文件路径
     */
    @Suppress("UNCHECKED")
    fun setIconFontPath(path: String?) {
        Intrinsics.checkNotNull(path)
        if (!TextUtils.isEmpty(path)) {
            val assets = context.assets
            if (assets != null) {
                try {
                    assets.open(path!!).use {
                        iconFontPaint.typeface = Typeface.createFromAsset(assets, path)
                    }
                } catch (e: IOException) {
                    setIconFontPathFromFile(path)
                }
            } else {
                setIconFontPathFromFile(path)
            }
        }
    }

    /**
     * 设置IconFont字体样式文件
     *
     * @param path 文件路径
     */
    @Suppress("UNCHECKED")
    fun setIconFontPathFromFile(path: String?) {
        Intrinsics.checkNotNull(path)
        if (!TextUtils.isEmpty(path)) {
            setIconFontPathFromFile(File(path))
        }
    }

    /**
     * 设置IconFont字体样式文件
     *
     * @param fontFile 字体样式文件
     */
    @Suppress("UNCHECKED")
    fun setIconFontPathFromFile(fontFile: File) {
        Intrinsics.checkNotNull(fontFile)
        if (fontFile.exists()) {
            iconFontPaint.typeface = Typeface.createFromFile(fontFile)
        }
    }

    /**
     * 控制EditText右边制眼睛按钮的显示、隐藏
     */
    private fun setVisibleIcon() {
        val isVisible = rightIconModel == VISIBLE_MODEL
        if (lastVisibleState != isVisible) {
            lastVisibleState = isVisible
            if (isIconFontModel()) {
                this.transformationMethod =
                    if (isVisible) HideReturnsTransformationMethod.getInstance() else PasswordTransformationMethod.getInstance()
                setRightText()
                invalidate()
                requestLayout()
            } else {
                val visibleIcon = if (isVisible) visibleDrawable else invisibleDrawable
                setCompoundDrawables(
                    compoundDrawables[0], compoundDrawables[1],
                    visibleIcon, compoundDrawables[3]
                )
                setCompoundDrawablesWithIntrinsicBounds(
                    compoundDrawables[0], compoundDrawables[1],
                    visibleIcon, compoundDrawables[3]
                )
                this.transformationMethod =
                    if (isVisible) HideReturnsTransformationMethod.getInstance() else PasswordTransformationMethod.getInstance()
                setSelection(length())
            }
            setSelection(text?.length ?: 0)
        }
    }

    /**
     * 控制EditText右边制删除按钮的显示、隐藏
     */
    private fun setClearIcon(isShow: Boolean) {
        if (isShow != lastClearShowState) {
            lastClearShowState = isShow
            rightIconModel = if (isShow) CLEAR_SHOW_MODEL else CLEAR_HIDE_MODEL
            if (isIconFontModel()) {
                setRightText()
                invalidate()
                requestLayout()
            } else if (mClearDrawable != null) {
                val rightDrawable = if (isShow) mClearDrawable else null
                setCompoundDrawables(
                    compoundDrawables[0], compoundDrawables[1],
                    rightDrawable, compoundDrawables[3]
                )
            }
        }
    }

    private fun isClearModel() =
        rightIconModel == CLEAR_SHOW_MODEL || rightIconModel == CLEAR_HIDE_MODEL

    /**
     * 有焦点,并文本长度大于0则显示删除按钮
     */
    override fun onFocusChange(v: View, hasFocus: Boolean) {
        if (isClearModel()) {
            if (hasFocus) {
                setClearIcon(!text.isNullOrEmpty())
            } else {
                setClearIcon(false)
            }
        }
    }

    /**
     * 文本内容变化时回调
     * 当文本长度大于0时显示删除按钮, 否则隐藏
     */
    override fun onTextChanged(
        text: CharSequence,
        start: Int,
        lengthBefore: Int,
        lengthAfter: Int
    ) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter)
        if (hasData && text.isEmpty() && lengthAfter == 0) {
            startShake(3)
        }
        hasData = text.isNotEmpty()
        if (isClearModel()) {
            setClearIcon(hasData)
        }
    }

    override fun beforeTextChanged(
        s: CharSequence,
        start: Int,
        count: Int,
        after: Int
    ) {
    }

    override fun afterTextChanged(s: Editable) {}
    /**
     * 通过手指的触摸位置模式删除按钮的点击事件
     */
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (compoundDrawables[2] != null || isRightIconDraw) {
            if (event.action == MotionEvent.ACTION_UP) {
                if (isClearModel()) {
                    if ((isClickDrawable(event, mClearDrawable)
                                || isClickRecF(event, rightRecFBg))
                    ) {
                        setText("")
                        return true
                    }
                } else if (isClickDrawable(event, visibleDrawable)
                    || isClickDrawable(event, invisibleDrawable)
                    || isClickRecF(event, rightRecFBg)
                ) {
                    rightIconModel =
                        if (rightIconModel == VISIBLE_MODEL) INVISIBLE_MODEL else VISIBLE_MODEL
                    setVisibleIcon()
                    return true
                }
            }
        }
        return if (onTouchListener != null && onTouchListener!!.onTouch(this, event)) {
            true
        } else super.onTouchEvent(event)
    }

    override fun setOnTouchListener(l: OnTouchListener) {
        onTouchListener = l
    }

    /**
     * 是否在右边图标范围内点击
     */
    private fun isClickDrawable(event: MotionEvent, drawable: Drawable?): Boolean {
        if (drawable == null) return false
        val xTouchable =
            (event.x > width - paddingRight - drawable.intrinsicWidth
                    && event.x < width - paddingRight)
        val yTouchable =
            (event.y > (height - drawable.intrinsicHeight) shr 1
                    && event.y < (height + drawable.intrinsicHeight) shr 1)
        return xTouchable && yTouchable
    }

    /**
     * 是否在右边iconFont范围内点击
     */
    private fun isClickRecF(event: MotionEvent, rectF: RectF?): Boolean {
        if (rectF == null) return false
        val xTouchable =
            (event.x > right - paddingRight
                    && event.x < right)
        val yTouchable =
            (event.y > rectF.top
                    && event.y < rectF.bottom)
        return xTouchable && yTouchable
    }

    /**
     * EditText抖动
     */
    @Suppress("UNCHECKED")
    fun startShake(counts: Int) {
        if (translateAnimation == null) {
            synchronized(this) {
                if (translateAnimation == null) {
                    translateAnimation = TranslateAnimation(0f, 10f, 0f, 0f)
                    translateAnimation?.interpolator = CycleInterpolator(counts.toFloat())
                    translateAnimation?.duration = 500
                }
            }
        }
        startAnimation(translateAnimation)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        if (isIconFontModel()) {

            iconFontDrawPadding = compoundDrawablePadding.toFloat()
            val fontMetrics = iconFontPaint.fontMetrics
            leftRecFBg = RectF()
            rightRecFBg = RectF()
            iconFontH = fontMetrics.top.absoluteValue + fontMetrics.bottom.absoluteValue//字体高度

            val textBean = textMap[leftIconText]
            leftIconFontTextLayout = textBean?.layout
            leftIconW = textBean?.with ?: 0
            isLeftIconDraw = leftIconW > 0
            iconLeftPadding = 2 * iconFontDrawPadding + leftIconW//内容文本左偏移距离

            setRightText()
            iconRightPadding = 2 * iconFontDrawPadding + maxRightTextW//内容文本右偏移距离
            setPadding(oldPaddingLeft, oldPaddingTop, oldPaddingRight, oldPaddingBottom)
        }
    }

    private fun setRightText() {
        val textBean = textMap[getRightIcon()?.toString()]
        rightIconFontTextLayout = textBean?.layout
        rightIconW = textBean?.with ?: 0
        isRightIconDraw = rightIconW > 0
    }

    override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
        oldPaddingLeft = left
        oldPaddingTop = top
        oldPaddingRight = right
        oldPaddingBottom = bottom
        super.setPadding(
            left + (iconLeftPadding).toInt(),
            top,
            right + (iconRightPadding).toInt(),
            bottom
        )
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        if (isLeftIconDraw || isRightIconDraw) {
            val compoundPaddingTop = compoundPaddingTop
            val compoundPaddingBottom = compoundPaddingBottom
            val mScrollX: Int = scrollX//横向滚动距离  防止IconFont跟随内容滚动
            val mScrollY: Int = scrollY//纵向滚动距离  防止IconFont跟随内容滚动

            //iconFont文字顶部位置
            val mTop =
                mScrollY + compoundPaddingTop + ((bottom - top - compoundPaddingBottom - compoundPaddingTop - iconFontH) / 2f)
            //iconFont文字底部位置
            val mBottom = mTop + iconFontH
            if (isLeftIconDraw) {
                setLeftRecF(mScrollX, mTop, mBottom)
                //绘制左边IconFont背景
                drawIconFontBg(canvas, leftRecFBg)
                //绘制左边IconFont
                drawIconFont(canvas, leftRecFBg, leftIconFontTextLayout)
            }

            if (isRightIconDraw) {
                setRightRecF(mScrollX, mTop, mBottom)
                //绘制右边IconFont背景
                drawIconFontBg(canvas, rightRecFBg)
                //绘制右边IconFont
                drawIconFont(canvas, rightRecFBg, rightIconFontTextLayout)
            }
        }
    }

    private fun setLeftRecF(mScrollX: Int, mTop: Float, mBottom: Float) {
        val mLeft = (mScrollX + iconFontDrawPadding)
        leftRecFBg?.left = mLeft
        leftRecFBg?.right = mLeft + leftIconW
        leftRecFBg?.top = mTop
        leftRecFBg?.bottom = mBottom
    }

    private fun setRightRecF(mScrollX: Int, mTop: Float, mBottom: Float) {
        val left = mScrollX + right - left - rightIconW - iconFontDrawPadding
        rightRecFBg?.left = left
        rightRecFBg?.right = left + rightIconW
        rightRecFBg?.top = mTop
        rightRecFBg?.bottom = mBottom
    }

    private fun drawIconFont(canvas: Canvas?, rectF: RectF?, layout: Layout?) {
        canvas?.save()
        canvas?.translate(rectF?.left ?: 0f, rectF?.top ?: 0f)
        layout?.draw(canvas)
        canvas?.restore()
    }

    @Suppress("DEPRECATION")
    private fun addIconFontText(source: String?, isRight: Boolean) {
        if (!source.isNullOrEmpty()) {
            val rect = Rect()
            iconFontPaint.getTextBounds(source, 0, source.length, rect)
            val width = (rect.width() + iconFontPaint.textSize / 2).toInt()
//        1.需要分行的字符串
//        4.画笔对象
//        5.layout的宽度,字符串超出宽度时自动换行。
//        6.layout的对其方式,有ALIGN_CENTER, ALIGN_NORMAL, ALIGN_OPPOSITE 三种。
//        7.相对行间距,相对字体大小,1.5f表示行间距为1.5倍的字体高度。
//        8.在基础行距上添加多少 实际行间距等于这两者的和。
//        9.设置是否在字体的上升和下降之外包括额外的空间(避免在某些语言(例如阿拉伯语和卡纳达语)中进行剪切是必需的*)。 *默认值为{@code true}
            val textLayout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                StaticLayout.Builder
                    .obtain(source, 0, source.length, iconFontPaint, width)
                    .setAlignment(Layout.Alignment.ALIGN_CENTER)
                    .setLineSpacing(0f, 1f)
                    .build()
            } else {
                StaticLayout(
                    source,
                    iconFontPaint,
                    width,
                    Layout.Alignment.ALIGN_CENTER,
                    1f,
                    0f,
                    true
                )
            }
            if (textLayout != null) {
                textMap[source] = TextBean(textLayout, width)
            }
            if (isRight) {
                maxRightTextW = max(maxRightTextW, width)
            }
        }
    }

    private fun drawIconFontBg(canvas: Canvas?, bgRectF: RectF?) {
        if (bgRectF != null) {
            canvas?.save()
            canvas?.drawRoundRect(
                bgRectF,
                0f,
                0f,
                bgPaint
            )
            canvas?.restore()
        }
    }

    private data class TextBean(
        val layout: Layout? = null,
        val with: Int? = null
    )
}
attr资源
 <declare-styleable name="ClearEditTextView2">
        <!--清空按钮图标 图片模式下高优先级-->
        <attr name="DrawableClear" format="reference" />
        <!--显示按钮图标-->
        <attr name="DrawableVisible" format="reference" />
        <!--隐藏按钮图标-->
        <attr name="DrawableInvisible" format="reference" />
        <!--清空按钮IconFont 最高优先级-->
        <attr name="IconFontClear" format="string" />
        <!--显示按钮IconFont-->
        <attr name="IconFontVisible" format="string" />
        <!--隐藏按钮IconFont-->
        <attr name="IconFontInvisible" format="string" />
        <!--左边IconFont 资源为空不显示-->
        <attr name="IconFontLeft" format="string" />
        <!--IconFont字体文件路径-->
        <attr name="IconFontPath" format="string" />
        <!--IconFont背景颜色 默认透明色-->
        <attr name="IconFontBackground" format="color" />
        <!--IconFont字体颜色 默认内容文本颜色-->
        <attr name="IconFontTextColor" format="color" />
        <!--IconFont字体尺寸 默认内容文本尺寸-->
        <attr name="IconFontTextSize" format="dimension" />
    </declare-styleable>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值