TextView源码
记录一些问题,以后来看,现在看得有点晕
- 自适应,设置可点击文字,点击之后字号变小;添加
includeFontPadding=false
之后可以暂时解决,但是如果文本很长,原来的框显示不下的话,会截断前面的。猜测是setMovementMethod(LinkMovementMethod.getInstance())
的原因,但具体原因未知 - 自适应,
maxLines=2
在两行还未占满之前通过append()
添加文字字号会变小,原因未知;append()之后调用requestlayout()
可以解决 - 行距,1.5倍实际看起来是两倍不止,看许多说法是字体的问题,打印了
FontMetrics
的参数,top
和ascent
之间bottom
和descent
之间还有比较长的距离,leading
好像一直是0,设不设置行间距没差,另外就是includeFontPadding
为看不出来有什么效果;据说,FontMetrics是系统决定的,所以,要么重写onDraw
方法,否则无法解决行间距和实际预期不符的问题
onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
BoringLayout.Metrics boring = UNKNOWN_BORING;
BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
if (mTextDir == null) {
mTextDir = getTextDirectionHeuristic();
}
int des = -1;
boolean fromexisting = false;
final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
? (float) widthSize : Float.MAX_VALUE;
//宽度是精确模式,父容器给的宽度当作当前TextView的宽度;
if (widthMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
width = widthSize;
} else {
if (mLayout != null && mEllipsize == null) {
des = desired(mLayout);
}
if (des < 0) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
if (boring != null) {
mBoring = boring;
}
} else {
fromexisting = true;
}
if (boring == null || boring == UNKNOWN_BORING) {
if (des < 0) {
des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
mTransformed.length(), mTextPaint, mTextDir, widthLimit));
}
width = des;
} else {
width = boring.width;
}
final Drawables dr = mDrawables;
if (dr != null) {
width = Math.max(width, dr.mDrawableWidthTop);
width = Math.max(width, dr.mDrawableWidthBottom);
}
if (mHint != null) {
int hintDes = -1;
int hintWidth;
if (mHintLayout != null && mEllipsize == null) {
hintDes = desired(mHintLayout);
}
if (hintDes < 0) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
}
if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
if (hintDes < 0) {
hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
mHint.length(), mTextPaint, mTextDir, widthLimit));
}
hintWidth = hintDes;
} else {
hintWidth = hintBoring.width;
}
if (hintWidth > width) {
width = hintWidth;
}
}
width += getCompoundPaddingLeft() + getCompoundPaddingRight();
if (mMaxWidthMode == EMS) {
width = Math.min(width, mMaxWidth * getLineHeight());
} else {
width = Math.min(width, mMaxWidth);
}
if (mMinWidthMode == EMS) {
width = Math.max(width, mMinWidth * getLineHeight());
} else {
width = Math.max(width, mMinWidth);
}
// Check against our minimum width
width = Math.max(width, getSuggestedMinimumWidth());
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
}
int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
int unpaddedWidth = want;
if (mHorizontallyScrolling) want = VERY_WIDE;
int hintWant = want;
int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
if (mLayout == null) {
makeNewLayout(want, hintWant, boring, hintBoring,
width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
} else {
final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
|| (mLayout.getEllipsizedWidth()
!= width - getCompoundPaddingLeft() - getCompoundPaddingRight());
final boolean widthChanged = (mHint == null) && (mEllipsize == null)
&& (want > mLayout.getWidth())
&& (mLayout instanceof BoringLayout
|| (fromexisting && des >= 0 && des <= want));
final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
if (layoutChanged || maximumChanged) {
if (!maximumChanged && widthChanged) {
mLayout.increaseWidthTo(want);
} else {
makeNewLayout(want, hintWant, boring, hintBoring,
width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
}
} else {
// Nothing has changed
}
}
if (heightMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
height = heightSize;
mDesiredHeightAtMeasure = -1;
} else {
int desired = getDesiredHeight();
height = desired;
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desired, heightSize);
}
}
int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
}
/*
* We didn't let makeNewLayout() register to bring the cursor into view,
* so do it here if there is any possibility that it is needed.
*/
if (mMovement != null
|| mLayout.getWidth() > unpaddedWidth
|| mLayout.getHeight() > unpaddedHeight) {
registerForPreDraw();
} else {
scrollTo(0, 0);
}
setMeasuredDimension(width, height);
}
该方法测量控件的宽高。
计算宽度的逻辑:
宽度是精确模式,父容器给的宽度当作当前TextView的宽度;
宽度不是精确模式:
计算desired,如果文字不超过一行,宽度为desired;
如果文字超过一行:
判断boring,这里我的理解是text的内容是不是普通text,因为可能有换行,而且是1个字就换行,
如果boring为null,计算DesiredWidthWithLimit
如果boring不为null,width=boring.width
到这里也就基本计算出文字的宽度了,接下来还要考虑drawable、hit、padding、是否可滑动等,一步步计算控件宽度
setLineSpacing()
写标准文字控件的时候,发现设置1.5倍的行距视觉上看起来像是两倍以上,所以比较好奇调用了这个方法之后会发生什么
代码只有几行
public void setLineSpacing(float add, float mult) {
if (mSpacingAdd != add || mSpacingMult != mult) {
mSpacingAdd = add;
mSpacingMult = mult;
if (mLayout != null) {
nullLayouts(); //将一些参数都置null
requestLayout(); //遍历视图树重新布局
invalidate(); //请求重绘View树
}
}
}