美文网首页
[Plaid源码分析]透明的TextView

[Plaid源码分析]透明的TextView

作者: Rc在努力 | 来源:发表于2019-03-28 14:33 被阅读0次
“Plaid”是透明

写文字不一定是TextView

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

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/black"
        tools:context=".CutoutActivity">



    <TextView
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:text="@string/app_name"
            android:background="@color/colorPrimary"
            android:gravity="center"
            android:textColor="@android:color/transparent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_width="300dp" android:layout_height="300dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
透明笔色是会显示TextView的底色的

Plaid是怎么做的?

CutoutTextView

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

自定义属性

attrs_cutout_text_view.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CutoutTextView">

        <!--前景色, 这里就是背景色-->
        <attr name="foregroundColor" format="color"/>
        
        <!--文字-->
        <attr name="android:text"/>

        <!--字体-->
        <attr name="android:fontFamily"/>

    </declare-styleable>
</resources>

具体实现

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

      本文链接:https://www.haomeiwen.com/subject/mxrcmqtx.html