上一篇,我们实现了一个基本的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美美的显示出来!
兄dei,如果觉得我写的还不错,麻烦帮个忙呗 😃
- 给俺点个赞被,激励激励我,同时也能让这篇文章让更多人看见,(#.#)
- 不用点收藏,诶别点啊,你怎么点了?这多不好意思!
拜托拜托,谢谢各位同学!