Android文本後 追加可點擊的文字點擊按鈕

效果圖:


直接上代碼如下:

val tip = “點擊View獲取到點擊事件View”
                            val detail = " View"
                            val checkBuilder = SpannableStringUtils.getBuilder(tip + detail).create()
                            val start = tip.length

                            // 設置點擊事件
                            checkBuilder.setSpan(
                                object : ClickableSpan() {
                                    override fun onClick(widget: View) {
                                        val tips = NotifyMsgUtils.getMsgTipsBean(msg.tipsJSON)
                                        EventBusUtils.postActionEvent(ActionEvent.EventCode.CODE_CHAT_SKIP_TARGET_MSG,tips?.msgId)
                                    }

                                    override fun updateDrawState(ds: TextPaint) {
                                        // 字體色
                                        ds.color = ContextCompat.getColor(context, R.color.base_color)
                                        ds.isUnderlineText = true
                                    }
                                },
                                start + 1,
                                start + detail.length,
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
                            )
                            // 要想讓點擊動作生效,此行代碼必不可少。
                            holder.getView<TextView>(R.id.tv_hint).movementMethod = CustomMovementMethod.getInstance()
                            checkBuilder



工具類如下:

class SpannableStringUtils private constructor() {

    init {
        throw UnsupportedOperationException("u can't instantiate me...")
    }

    class Builder internal constructor(private var text: CharSequence?) {

        private val defaultValue = 0x12000000

        private var flag: Int = 0

        @ColorInt
        private var foregroundColor: Int = 0

        @ColorInt
        private var backgroundColor: Int = 0

        @ColorInt
        private var quoteColor: Int = 0

        private var isLeadingMargin: Boolean = false
        private var first: Int = 0
        private var rest: Int = 0

        private var isBullet: Boolean = false
        private var gapWidth: Int = 0
        private var bulletColor: Int = 0

        private var proportion: Float = 0.toFloat()
        private var xProportion: Float = 0.toFloat()
        private var isStrikethrough: Boolean = false
        private var isUnderline: Boolean = false
        private var isSuperscript: Boolean = false
        private var isSubscript: Boolean = false
        private var isBold: Boolean = false
        private var isItalic: Boolean = false
        private var isBoldItalic: Boolean = false
        private var fontFamily: String? = null
        private var typeface: Typeface? = null
        private var align: Alignment? = null

        private var imageIsBitmap: Boolean = false
        private var bitmap: Bitmap? = null
        private var imageIsDrawable: Boolean = false
        private var drawable: Drawable? = null
        private var imageIsUri: Boolean = false
        private var uri: Uri? = null
        private var imageIsResourceId: Boolean = false

        @DrawableRes
        private var resourceId: Int = 0

        private var clickSpan: ClickableSpan? = null
        private var url: String? = null

        private var isBlur: Boolean = false
        private var radius: Float = 0.toFloat()
        private var style: Blur? = null

        private val mBuilder: SpannableStringBuilder


        init {
            flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            foregroundColor = defaultValue
            backgroundColor = defaultValue
            quoteColor = defaultValue
            proportion = -1f
            xProportion = -1f
            mBuilder = SpannableStringBuilder()
        }

        /**
         * 設置標識
         *
         * @param flag
         *  * [Spanned.SPAN_INCLUSIVE_EXCLUSIVE]
         *  * [Spanned.SPAN_INCLUSIVE_INCLUSIVE]
         *  * [Spanned.SPAN_EXCLUSIVE_EXCLUSIVE]
         *  * [Spanned.SPAN_EXCLUSIVE_INCLUSIVE]
         *
         * @return [Builder]
         */
        fun setFlag(flag: Int): Builder {
            this.flag = flag
            return this
        }

        /**
         * 設置前景色
         *
         * @param color 前景色
         * @return [Builder]
         */
        fun setForegroundColor(@ColorInt color: Int): Builder {
            this.foregroundColor = color
            return this
        }

        /**
         * 設置背景色
         *
         * @param color 背景色
         * @return [Builder]
         */
        fun setBackgroundColor(@ColorInt color: Int): Builder {
            this.backgroundColor = color
            return this
        }

        /**
         * 設置引用線的顏色
         *
         * @param color 引用線的顏色
         * @return [Builder]
         */
        fun setQuoteColor(@ColorInt color: Int): Builder {
            this.quoteColor = color
            return this
        }

        /**
         * 設置縮進
         *
         * @param first 首行縮進
         * @param rest  剩餘行縮進
         * @return [Builder]
         */
        fun setLeadingMargin(first: Int, rest: Int): Builder {
            this.first = first
            this.rest = rest
            isLeadingMargin = true
            return this
        }

        /**
         * 設置列表標記
         *
         * @param gapWidth 列表標記和文字間距離
         * @param color    列表標記的顏色
         * @return [Builder]
         */
        fun setBullet(gapWidth: Int, color: Int): Builder {
            this.gapWidth = gapWidth
            bulletColor = color
            isBullet = true
            return this
        }

        /**
         * 設置字體比例
         *
         * @param proportion 比例
         * @return [Builder]
         */
        fun setProportion(proportion: Float): Builder {
            this.proportion = proportion
            return this
        }

        /**
         * 設置字體橫向比例
         *
         * @param proportion 比例
         * @return [Builder]
         */
        fun setXProportion(proportion: Float): Builder {
            this.xProportion = proportion
            return this
        }

        /**
         * 設置刪除線
         *
         * @return [Builder]
         */
        fun setStrikethrough(): Builder {
            this.isStrikethrough = true
            return this
        }

        /**
         * 設置下劃線
         *
         * @return [Builder]
         */
        fun setUnderline(): Builder {
            this.isUnderline = true
            return this
        }

        /**
         * 設置上標
         *
         * @return [Builder]
         */
        fun setSuperscript(): Builder {
            this.isSuperscript = true
            return this
        }

        /**
         * 設置下標
         *
         * @return [Builder]
         */
        fun setSubscript(): Builder {
            this.isSubscript = true
            return this
        }

        /**
         * 設置粗體
         *
         * @return [Builder]
         */
        fun setBold(): Builder {
            isBold = true
            return this
        }

        /**
         * 設置斜體
         *
         * @return [Builder]
         */
        fun setItalic(): Builder {
            isItalic = true
            return this
        }

        /**
         * 設置粗斜體
         *
         * @return [Builder]
         */
        fun setBoldItalic(): Builder {
            isBoldItalic = true
            return this
        }

        /**
         * 設置字體
         *
         * @param fontFamily 字體
         *
         *  * monospace
         *  * serif
         *  * sans-serif
         *
         * @return [Builder]
         */
        fun setFontFamily(fontFamily: String?): Builder {
            this.fontFamily = fontFamily
            return this
        }

        fun setFontTypeFace(typeface: Typeface): Builder {
            this.typeface = typeface
            return this
        }

        /**
         * 設置對齊
         *
         * @param align 對其方式
         *
         *  * [Alignment.ALIGN_NORMAL]正常
         *  * [Alignment.ALIGN_OPPOSITE]相反
         *  * [Alignment.ALIGN_CENTER]居中
         *
         * @return [Builder]
         */
        fun setAlign(align: Alignment?): Builder {
            this.align = align
            return this
        }

        /**
         * 設置圖片
         *
         * @param bitmap 圖片位圖
         * @return [Builder]
         */
        fun setBitmap(bitmap: Bitmap): Builder {
            this.bitmap = bitmap
            imageIsBitmap = true
            return this
        }

        /**
         * 設置圖片
         *
         * @param drawable 圖片資源
         * @return [Builder]
         */
        fun setDrawable(drawable: Drawable): Builder {
            this.drawable = drawable
            imageIsDrawable = true
            return this
        }

        /**
         * 設置圖片
         *
         * @param uri 圖片uri
         * @return [Builder]
         */
        fun setUri(uri: Uri): Builder {
            this.uri = uri
            imageIsUri = true
            return this
        }

        /**
         * 設置圖片
         *
         * @param resourceId 圖片資源id
         * @return [Builder]
         */
        fun setResourceId(@DrawableRes resourceId: Int): Builder {
            this.resourceId = resourceId
            imageIsResourceId = true
            return this
        }

        /**
         * 設置點擊事件
         *
         * 需添加view.setMovementMethod(LinkMovementMethod.getInstance())
         *
         * @param clickSpan 點擊事件
         * @return [Builder]
         */
        fun setClickSpan(clickSpan: ClickableSpan): Builder {
            this.clickSpan = clickSpan
            return this
        }

        /**
         * 設置超鏈接
         *
         * 需添加view.setMovementMethod(LinkMovementMethod.getInstance())
         *
         * @param url 超鏈接
         * @return [Builder]
         */
        fun setUrl(url: String): Builder {
            this.url = url
            return this
        }

        /**
         * 設置模糊
         *
         * 尚存bug,其他地方存在相同的字體的話,相同字體出現在之前的話那麼就不會模糊,出現在之後的話那會一起模糊
         *
         * 推薦還是把所有字體都模糊這樣使用
         *
         * @param radius 模糊半徑(需大於0)
         * @param style  模糊樣式
         *  * [Blur.NORMAL]
         *  * [Blur.SOLID]
         *  * [Blur.OUTER]
         *  * [Blur.INNER]
         *
         * @return [Builder]
         */
        fun setBlur(radius: Float, style: Blur): Builder {
            this.radius = radius
            this.style = style
            this.isBlur = true
            return this
        }

        /**
         * 追加樣式字符串
         *
         * @param text 樣式字符串文本
         * @return [Builder]
         */
        fun append(text: CharSequence): Builder {
            setSpan()
            this.text = text
            return this
        }

        /**
         * 創建樣式字符串
         *
         * @return 樣式字符串
         */
        fun create(): SpannableStringBuilder {
            setSpan()
            return mBuilder
        }

        /**
         * 設置樣式
         */
        private fun setSpan() {
            val start = mBuilder.length
            mBuilder.append(this.text)
            val end = mBuilder.length
            if (foregroundColor != defaultValue) {
                mBuilder.setSpan(ForegroundColorSpan(foregroundColor), start, end, flag)
                foregroundColor = defaultValue
            }
            if (backgroundColor != defaultValue) {
                mBuilder.setSpan(BackgroundColorSpan(backgroundColor), start, end, flag)
                backgroundColor = defaultValue
            }
            if (isLeadingMargin) {
                mBuilder.setSpan(LeadingMarginSpan.Standard(first, rest), start, end, flag)
                isLeadingMargin = false
            }
            if (quoteColor != defaultValue) {
                mBuilder.setSpan(QuoteSpan(quoteColor), start, end, 0)
                quoteColor = defaultValue
            }
            if (isBullet) {
                mBuilder.setSpan(BulletSpan(gapWidth, bulletColor), start, end, 0)
                isBullet = false
            }
            if (proportion != -1f) {
                mBuilder.setSpan(RelativeSizeSpan(proportion), start, end, flag)
                proportion = -1f
            }
            if (xProportion != -1f) {
                mBuilder.setSpan(ScaleXSpan(xProportion), start, end, flag)
                xProportion = -1f
            }
            if (isStrikethrough) {
                mBuilder.setSpan(StrikethroughSpan(), start, end, flag)
                isStrikethrough = false
            }
            if (isUnderline) {
                mBuilder.setSpan(UnderlineSpan(), start, end, flag)
                isUnderline = false
            }
            if (isSuperscript) {
                mBuilder.setSpan(SuperscriptSpan(), start, end, flag)
                isSuperscript = false
            }
            if (isSubscript) {
                mBuilder.setSpan(SubscriptSpan(), start, end, flag)
                isSubscript = false
            }
            if (isBold) {
                mBuilder.setSpan(StyleSpan(Typeface.BOLD), start, end, flag)
                isBold = false
            }
            if (isItalic) {
                mBuilder.setSpan(StyleSpan(Typeface.ITALIC), start, end, flag)
                isItalic = false
            }
            if (isBoldItalic) {
                mBuilder.setSpan(StyleSpan(Typeface.BOLD_ITALIC), start, end, flag)
                isBoldItalic = false
            }
            if (fontFamily != null) {
                mBuilder.setSpan(TypefaceSpan(fontFamily), start, end, flag)
                fontFamily = null
            }
            if (typeface != null) {
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
                    mBuilder.setSpan(TypefaceSpan(typeface!!), start, end, flag)
                }
                typeface = null
            }
            if (align != null) {
                mBuilder.setSpan(AlignmentSpan.Standard(align!!), start, end, flag)
                align = null
            }
            if (imageIsBitmap || imageIsDrawable || imageIsUri || imageIsResourceId) {
                if (imageIsBitmap) {
                    mBuilder.setSpan(CenterImageSpan(App.mContext, bitmap!!), start, end, flag)
                    bitmap = null
                    imageIsBitmap = false
                } else if (imageIsDrawable) {
                    mBuilder.setSpan(CenterImageSpan(drawable!!), start, end, flag)
                    drawable = null
                    imageIsDrawable = false
                } else if (imageIsUri) {
                    mBuilder.setSpan(CenterImageSpan(App.mContext, uri!!), start, end, flag)
                    uri = null
                    imageIsUri = false
                } else {
                    mBuilder.setSpan(CenterImageSpan(App.mContext, resourceId), start, end, flag)
                    resourceId = 0
                    imageIsResourceId = false
                }
            }
            if (clickSpan != null) {
                mBuilder.setSpan(clickSpan, start, end, flag)
                clickSpan = null
            }
            if (url != null) {
                mBuilder.setSpan(URLSpan(url), start, end, flag)
                url = null
            }
            if (isBlur) {
                mBuilder.setSpan(MaskFilterSpan(BlurMaskFilter(radius, style)), start, end, flag)
                isBlur = false
            }
            flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        }
    }

    companion object {

        /**
         * 獲取建造者
         *
         * @param text 樣式字符串文本
         * @return [Builder]
         */
        fun getBuilder(text: CharSequence): Builder {
            return Builder(text)
        }
    }

    class CenterImageSpan: ImageSpan {
        constructor(drawable: Drawable) : super(drawable)
        constructor(context: Context, resourceId: Int) : super(context, resourceId)
        constructor(context: Context, uri: Uri) : super(context, uri)
        constructor(context: Context, bitmap: Bitmap) : super( context, bitmap)

        override fun draw(
            canvas: Canvas,
            text: CharSequence?,
            start: Int,
            end: Int,
            x: Float,
            top: Int,
            y: Int,
            bottom: Int,
            paint: Paint
        ) {
            var b = drawable
            canvas.save()
            var transY = (((bottom - top) - b.bounds.bottom) / 2 + top).toFloat()
            canvas.translate(x, transY)
            b.draw(canvas)
            canvas.restore()
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章