OverView
继承关系
onMeasure
调用流程
时序图
背景知识
关于源码
onMeasure
1 |
|
makeNewLayout
1 | /** |
makeSingleLayout
1 | private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, |
关键路径
如何实现
行间距和字间距实现
com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
com.android.internal.R.styleable.TextView_lineSpacingExtra:
com.android.internal.R.styleable.TextView_letterSpacing: api_level 21 5.0
TextPaint开放接口提供功能实现
如何判断换行
1 |
|
如何实现文字省略
setBreakStrategy api_level 23 android 6.0
values below: https://developer.android.com/reference/android/text/Layout.html#BREAK_STRATEGY_SIMPLE
如何实现emoji的统一替换
在settext之前把text替换成spannabelstring,并且插入imageSpan
1 | public void setText(CharSequence text, BufferType type) { |
onLayout
调用流程
背景知识
关于源码
onLayout
1 |
|
如何实现
onLayout的事情主要还是交给 BoringLayout,DynamicLayout和StaticLayout
- [ ] 重点分析这三类Layout
onDraw
调用流程
背景知识
View的draw做了些什么
1 | /* |
onDraw()
- [ ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
protected void onDraw(Canvas canvas) {
restartMarqueeIfNeeded();
// Draw the background for this view
super.onDraw(canvas);
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingRight = getCompoundPaddingRight();
final int compoundPaddingBottom = getCompoundPaddingBottom();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
final int right = mRight;
final int left = mLeft;
final int bottom = mBottom;
final int top = mTop;
final boolean isLayoutRtl = isLayoutRtl();
final int offset = getHorizontalOffsetForDrawables();
final int leftOffset = isLayoutRtl ? 0 : offset;
final int rightOffset = isLayoutRtl ? offset : 0 ;
//----------step1 ---------drawableLeft....------------------------------
final Drawables dr = mDrawables;
if (dr != null) {
/*
* Compound, not extended, because the icon is not clipped
* if the text height is smaller.
*/
int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
//理解下面的代码,可以知道drawableLeft或者其他,比如2行会怎么显示,比如有自带滚动条会怎么显示
if (dr.mShowing[Drawables.LEFT] != null) {
canvas.save();
//最左边的中点开始画leftDrawable,paddingLef和scrollX在drawable的左边
//翻阅资料 scrollX是滚动的偏移量(起点-终点坐标),+scrollX保证了图片不会随着跑马灯滚动
canvas.translate(scrollX + mPaddingLeft + leftOffset,
scrollY + compoundPaddingTop +
(vspace - dr.mDrawableHeightLeft) / 2);
dr.mShowing[Drawables.LEFT].draw(canvas);
canvas.restore();
}
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
if (dr.mShowing[Drawables.RIGHT] != null) {
canvas.save();
//减的意思就是这个值在它的右边其作用,加代表这个值在他左边起作用
canvas.translate(scrollX + right - left - mPaddingRight
- dr.mDrawableSizeRight - rightOffset,
scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
dr.mShowing[Drawables.RIGHT].draw(canvas);
canvas.restore();
}
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
if (dr.mShowing[Drawables.TOP] != null) {
canvas.save();
//最上面的中点开始画drawableTop,scrollX和paddingLeft始终在其左边
canvas.translate(scrollX + compoundPaddingLeft +
(hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
dr.mShowing[Drawables.TOP].draw(canvas);
canvas.restore();
}
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
//x = scrollx+compaddingLeft+(hspace-drawableWidth)/2 -------bingo
//y = scrollY+(bottom-top-compaddingButton-drawableHeight) -------bingo
if (dr.mShowing[Drawables.BOTTOM] != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft +
(hspace - dr.mDrawableWidthBottom) / 2,
scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
dr.mShowing[Drawables.BOTTOM].draw(canvas);
canvas.restore();
}
}
int color = mCurTextColor;
if (mLayout == null) {
assumeLayout();
}
Layout layout = mLayout;
if (mHint != null && mText.length() == 0) {
if (mHintTextColor != null) {
color = mCurHintTextColor;
}
layout = mHintLayout;
}
mTextPaint.setColor(color);
mTextPaint.drawableState = getDrawableState();
//-------------step 2 ---------------文字阴影----------------------
canvas.save();
/* Would be faster if we didn't have to do this. Can we chop the
(displayable) text so that we don't need to do this ever?
*/
int extendedPaddingTop = getExtendedPaddingTop();
int extendedPaddingBottom = getExtendedPaddingBottom();
final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
//TODO why getHeight() equals getLineTop(getLineCount)?//api注释应该在layout基类里实现了
final int maxScrollY = mLayout.getHeight() - vspace;
float clipLeft = compoundPaddingLeft + scrollX;
float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
float clipRight = right - left - getFudgedPaddingRight() + scrollX;
float clipBottom = bottom - top + scrollY -
((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
//TODO how to make mShadowRadius successfully
//这里只是切范围,阴影应该在textPaint里面处理的
if (mShadowRadius != 0) {
clipLeft += Math.min(0, mShadowDx - mShadowRadius);
clipRight += Math.max(0, mShadowDx + mShadowRadius);
clipTop += Math.min(0, mShadowDy - mShadowRadius);
clipBottom += Math.max(0, mShadowDy + mShadowRadius);
}
canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
int voffsetText = 0;
int voffsetCursor = 0;
// translate in by our padding
/* shortcircuit calling getVerticaOffset() */
if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
voffsetText = getVerticalOffset(false);
voffsetCursor = getVerticalOffset(true);
}
canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
//---------step 3 ----------初始化跑马灯的初始化状态
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
(absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
final int width = mRight - mLeft;
final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
final float dx = mLayout.getLineRight(0) - (width - padding);
canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
}
if (mMarquee != null && mMarquee.isRunning()) {
final float dx = -mMarquee.getScroll();
canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
}
}
final int cursorOffsetVertical = voffsetCursor - voffsetText;
//-----------step 4 ------------------------画文字----------------------
Path highlight = getUpdatedHighlightPath();
if (mEditor != null) {
mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
} else {
layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
}
//-------------step 5 -----------------开始跑马灯的效果--------
if (mMarquee != null && mMarquee.shouldDrawGhost()) {
final float dx = mMarquee.getGhostOffset();
canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
}
canvas.restore();
}
Layout:draw
1 | /** |
Layout:drawText
1 | /** |
Layout:elipsize+StaticLayout: getEllipsisCount+StaticLayout:getEllipsisStart
省略是怎么配合发生的
1 | private void ellipsize(int start, int end, int line, |
TextLine:draw
1 | /** |
如何实现
跑马灯
效果实现
1 |
|
数值更新
1 | private static final class Marquee { |
其他
文字缓存
https://github.com/facebookincubator/TextLayoutBuilder[Todo 0812]
Layout的方法论
TODO
缺少两张图 (onMeasure 时序图, onDraw 时序图)
- [ ] imageSpan的继承关系和实现形式
- [ ] 重点分析这三类Layout