上一篇,我们实现了一个基本的StrokeTextView,能支持各种Gravity,本篇,我们就要在上一篇的基础上进一步优化,让StrokeTextView可以支持各种Padding,而它的margin我们不需要关心,因为margin是由父容器控制的。
如果有同学不记得上一篇的内容,或者是新同学还没看过我的三部曲序列,可以点击下面的传送门阅读~: 理解TextView三部曲概览 理解TextView三部曲(一):TextView的文本绘制过程
同时奉上源码:项目源码地址
还是先来回顾下,上篇完成效果的不足:
虽然我们的StrokeTextView能够适配不同Gravity,但是有padding的情况下,描边位置还是不准确。
按照惯例,这时候要放上本篇优化后的效果:
本篇的目标就是,让StrokeTextView在不同Gravity的情况下,也支持各种padding
因为本篇依然是绘图位置的调整,所以只需要对onDraw()再进行优化。
这是上一篇的处理逻辑,在不同gravity上我们计算了stroke的落笔点位置,但却忽略了padding所占的空间,所以本篇把这部分的逻辑加上。
不过在开始之前,我们得先搞清楚,TextView绘制文本的时候,如果有padding,TextView是怎么计算文本的落笔位置的。
看看TextView.onDraw()的绘制流程。
protected void onDraw(Canvas canvas) { restartMarqueeIfNeeded(); // Draw the background for this view super.onDraw(canvas); // initialize the padding.. // .. // 画四周的drawable if (dr != null) { // ... } .. // mLayout就是用来填充text的Layout if (mLayout == null) { assumeLayout(); } // .. mTextPaint.setColor(color); mTextPaint.drawableState = getDrawableState(); // 画hint、跑马灯的效果(需要的话) // .. }这里截取了重要的部分代码,其中有个非常重要的属性mLayout,如果mLayout == null,就会执行assumeLayout()对它进行初始化。
/** * Make a new Layout based on the already-measured size of the view, * on the assumption that it was measured correctly at some point. */ private void assumeLayout() { // 计算所需要的宽度,这里减去了左、右的padding int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); if (width < 1) { width = 0; } int physicalWidth = width; if (mHorizontallyScrolling) { width = VERY_WIDE; } // 把计算后的width传入,创建一个新的layout来填充text makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, physicalWidth, false); }可以看看getCompoundPaddingLeft()方法的实现:
mDrawables就是用来存放TextView四周的drawable的数组、
getCompoundPaddingRight()同理,再看看makeNewLayout方法的官方注释:
/** The width passed in is now the desired layout width, not the full view width with padding. 传入的width是text想要的宽度,并不包括左、右两边的padding */ public void makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)也就是说,经过了这一轮的调用后,mLayout就被初始化了,而且mLayout的宽度是不包括左、右padding的。
但是mLayout是做什么用的呢?好像到现在都没介绍过它的用处,它会是用来填充text的layout吗?
TextView中没有明显的使用mLayout来绘制text(后来发现原来是通过BoringLayout来绘制的),但我们可以看看TextView.setText(),我们知道,text就是经过这个方法设置的。看看每一次text改变时,TextView是怎么重绘的。
TextView.setText()中有唯一的一行关于mLayout的代码:
if (mLayout != null) { checkForRelayout(); }checkForRelayout(),看着名字的意思好像是:判断需不需要根据新设置的text重新reLayout(),点进去看看:
/** * Check whether entirely new text requires a new view layout * or merely a new text layout. */ private void checkForRelayout() { .. if (如果是固定宽度) { // Static width, so try making a new text layout. // 获取mLayout原来的宽、高 int oldht = mLayout.getHeight(); int want = mLayout.getWidth(); int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); // 重新创建mLayout(内部判断是否需要) makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); .. // 重绘 requestLayout(); invalidate(); } else { // 如果设置的width是wrap_content, // 则重新requestLayout nullLayouts(); requestLayout(); invalidate(); } }根据官方给出注释我们也可以大致知道mLayout的作用,它就是用来填充text的Layout!
如果有同学对mLayout的作用仍然有疑惑的话,可以在之后去看看部曲三,部曲三会分析TextView的onMeasure()方法,并再次提到mLayout
那么到这里就很明显了,用于填充text的区域mLayout是不包含周围padding所占的区域的。
也就是说TextView的text能够使用的宽度就应该为:
LeftSpace = getWidht() - getCompoundPaddingLeft() - getCompoundPaddingRight(),并不是整个getWidth()!
那么给TextView设置的Gravity,应该也是基于LeftSpace来判断的。
搞清楚了TextView在有padding的情况下的text绘制位置后,那么处理不同的Gravity就变得简单了。无非还是改变落笔点x坐标的位置,y坐标的位置依然是baseline的位置
首先来看Gravity=LEFT的情况,上一篇的逻辑:
canvas.drawText(text, 0f, getBaseline(), mStrokePaint);在局左对齐的情况下要支持padding,同时不能使用padding占用的区域,那不就是让原来的落笔点x坐标加上左边padding的距离就好了嘛!,原来的是stroke落笔点坐标是[0, getBaseline()],现在就是 [getCompoundPaddingLeft(), getBaseline()]
canvas.drawText(text, getCompoundPaddingLeft(), getBaseline(), mStrokePaint);Gravity=RIGHT的情况,上一篇的逻辑:
canvas.drawText(text, getWidth() - strokePaint. measureText(text), getBaseline(), strokePaint);同理,算上右边padding的距离,更新stroke落笔点:
canvas.drawText(text, getWidth() - getCompoundPaddingRight() - strokePaint.measureText(text), getBaseline(), mStrokePaint);Gravity=CENTER的情况就稍微复杂些,不过也就复杂壹点点~
还是来捋一下逻辑,不使用左、右padding占用的位置,在剩下的位置中要居中对齐,那么剩下的区域中落笔点的位置就是:
xInLeftSpace = (getWidth() - getCompoundPaddingRight() - getCompoundPaddingLeft() - getPaint().measureText(text)) / 2;
在绘图时还要跳过左边padding距离,所以我们最终stroke的落笔点x位置就是:newX = getCompoundPaddingLeft() + xInLeftSpace
最后,onDraw()优化后的完整代码:
至此,部曲二的优化部分就结束了,改动虽然不多,涉及的内容点也不是很高深的东西。不过重要的是,部曲二的优化都是我自己看源码后才得出来的。
之前我并不习惯于看源码,有问题就想百度直接看结果,但是百度看到的结果并不能给我留下更深的影响,感觉就像在抄答案一样,抄完就忘。所以这次部曲二的优化,也是我自己独立看源码的一次尝试。
安卓要看的源码这么多,就先从简单点的TextView开始入手吧!
下一篇,理解TextView三部曲(三):倔强的StrokeTextView(我无论如何都要展示出来!) 将会改变TextView的文本默认显示样式,让我们的StrokeTextView美美的显示出来!
拜托拜托,谢谢各位同学!