写文字不一定是TextView
第一次看到上图以为是用TextView写的。所以我试了一下透明的android:textColor="@android:color/transparent"
结果是没办法做到的,因为一旦笔色是透明,那么就会显示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;
}
}