[Plaid源码分析]透明的TextView

[Plaid源码分析]透明的TextView_第1张图片
“Plaid”是透明

写文字不一定是TextView

第一次看到上图以为是用TextView写的。所以我试了一下透明的android:textColor="@android:color/transparent"结果是没办法做到的,因为一旦笔色是透明,那么就会显示TextView的背景色。






    

[Plaid源码分析]透明的TextView_第2张图片
透明笔色是会显示TextView的底色的

Plaid是怎么做的?

CutoutTextView

Plaid是通过自定义View实现的,具体代码为CutoutTextView

自定义属性

attrs_cutout_text_view.xml



    

        
        
        
        
        

        
        

    

具体实现

CutoutTextView.java

class CutoutTextView(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
    private val maxTextSize: Float
    private val text: String

    private var cutout: Bitmap? = null
    private var foregroundColor = Color.MAGENTA
    private var textY = 0f
    private var textX = 0f


    //读取xml配置参数,有就设置
    init {
        val a = getContext().obtainStyledAttributes(attrs, R.styleable.CutoutTextView, 0, 0)

        if (a.hasValue(R.styleable.CutoutTextView_android_fontFamily)) {

            try {
                val font =
                    ResourcesCompat.getFont(
                        getContext(),
                        a.getResourceId(R.styleable.CutoutTextView_android_fontFamily, 0)
                    )
                if (font != null) {
                    textPaint.typeface = font
                }
            } catch (nfe: Resources.NotFoundException) {

            }
        }

        if (a.hasValue(R.styleable.CutoutTextView_foregroundColor)) {
            foregroundColor = a.getColor(R.styleable.CutoutTextView_foregroundColor, foregroundColor)
        }

        text = if (a.hasValue(R.styleable.CutoutTextView_android_text)) {
            a.getString(R.styleable.CutoutTextView_android_text)
        } else {
            ""
        }


        maxTextSize = context.resources.getDimensionPixelSize(R.dimen.display_4_text_size).toFloat()

        a.recycle()
    }


    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        calculateTextPosition()
        createBitmap()
    }


    /**
     * 计算字体位置,字体的大小
     */
    private fun calculateTextPosition() {
        //实际的字体宽度*PHI=控件的宽度 (这里PHI黄金分隔值)
        val targetWidth = width / PHI
        //根据传入字体宽度 动态计算合适的字体大小
        val textSize = ViewUtils.getSingleLineTextSize(
            text, textPaint, targetWidth, 0f, maxTextSize, 0.5f,
            resources.displayMetrics
        )
        textPaint.textSize = textSize
        //https://chris.banes.me/2014/03/27/measuring-text/
        //计算字体的开始X坐标
        textX = (width - textPaint.measureText(text)) / 2
        val textBounds = Rect()
        textPaint.getTextBounds(text, 0, text.length, textBounds)
        //计算字体的开始Y坐标
        val textHeight = textBounds.height().toFloat()
        textY = (height + textHeight) / 2

    }

    private fun createBitmap() {
        // cutout资源
        cutout?.run {
            if (!isRecycled) {
                recycle()
            }
        }

        //https://blog.csdn.net/iispring/article/details/50472485#commentBox
        //PorterDuff.Mode.CLEAR相当于透明色的效果,具体功能见上面的分析
        textPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
        
        //创建bitmap
        cutout = createBitmap(width, height).applyCanvas {
            drawColor(foregroundColor)
            drawText(text, textX, textY, textPaint)
        }
    }


    override fun onDraw(canvas: Canvas) {
        //画图
        canvas.drawBitmap(cutout, 0f, 0f, null)
    }


    companion object {

        private const val PHI = 1.6182f
    }

}

关键点是什么?

PorterDuffXfermode是实现透明TextView的关键,具体可以看该博客分析PorterDuffXfermode,另外还有一个很有趣的地方就是动态计算合理字体大小的方法。

ViewUtils.java

  /**
     * 通过递归查找返回最佳的单行文本字体大小
     *
     * @param text        待绘制的文本
     * @param paint       画笔
     * @param targetWidth 目标宽度
     * @param low         最小字体
     * @param high        最大字体
     * @param precision   精度(最大字体和最小字体相差大小)
     * @param metrics     屏幕相关信息
     * @return 最佳的单行文本字体大小
     */
    public static float getSingleLineTextSize(String text,
                                              TextPaint paint,
                                              float targetWidth,
                                              float low,
                                              float high,
                                              float precision,
                                              DisplayMetrics metrics) {
        final float mid = (low + high) / 2.0f;
        //给画笔设置字体大小
        paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mid, metrics));
        //计算在该字体下,画这些字需要多宽
        final float maxLineWidth = paint.measureText(text);

        if ((high - low) < precision) {
            //high为最大允许的字体,low为最小允许的字体,我们的目标是计算字体大小最合适当前给定的宽度targetWidth
            return low;
        } else if (maxLineWidth > targetWidth) {//计算出的字体太大了,应该再小一点
            //如果计算出当前的宽度超出目标的宽度,那么重新计算合适字体的大小,下一次计算的最大字体值为mid
            return getSingleLineTextSize(text, paint, targetWidth, low, mid, precision, metrics);
        } else if (maxLineWidth < targetWidth) {//计算出的字体大小了,应该再大一点
            //如果计算出当前的宽度小于目标的宽度,那么重新计算合适字体的大小,下一次计算的最小字体值为mid
            return getSingleLineTextSize(text, paint, targetWidth, mid, high, precision, metrics);
        } else {
            //计算出来的字体刚刚好
            return mid;
        }
    }

你可能感兴趣的:([Plaid源码分析]透明的TextView)