- CompoundButton 源码分析
- LinearLayout 源码分析
- SearchView 源码解析
- LruCache 源码解析
- ViewDragHelper 源码解析
- BottomSheets 源码解析
- Media Player 源码分析
- NavigationView 源码解析
- Service 源码解析
- Binder 源码分析
- Android 应用 Preference 相关及源码浅析 SharePreferences 篇
- ScrollView 源码解析
- Handler 源码解析
- NestedScrollView 源码解析
- SQLiteOpenHelper/SQLiteDatabase/Cursor 源码解析
- Bundle 源码解析
- LocalBroadcastManager 源码解析
- Toast 源码解析
- TextInputLayout
- LayoutInflater 和 LayoutInflaterCompat 源码解析
- TextView 源码解析
- NestedScrolling 事件机制源码解析
- ViewGroup 源码解析
- StaticLayout 源码分析
- AtomicFile 源码解析
- AtomicFile 源码解析
- Spannable 源码分析
- Notification 之 Android 5.0 实现原理
- CoordinatorLayout 源码分析
- Scroller 源码解析
- SwipeRefreshLayout 源码分析
- FloatingActionButton 源码解析
- AsyncTask 源码分析
- TabLayout 源码解析
generate 方法分析
StaticLayout 中的 generate()
方法近 300 行,其完成了文本的段落、折行的处理,建议自行对照源码来阅读下面的分析,本文不贴太多代码。
接受的参数:
StaticLayout.Builder b
StaticLayout.Builder 对象boolean includepad
是否上下保留空白boolean trackpad
细节分析:
- 在方法的开始,创建了很多的局部变量,并将 Builder 对象对应的值赋值给这些变量。
- 其中有个
Paint.FontMetricsInt fm
变量,FontMetricsInt 是 Paint 的内部类,主要用来完成字体测量,其和FontMetrics
非常类似,只是在文字测量时,对应的数值均是 int 类型,FontMetrics 是 float 类型。FontMetricsInt 类主要包含保存了字体测量相关的数据,源码如下:public static class FontMetricsInt { public int top; public int ascent; public int descent; public int bottom; public int leading; }
每个值的含义如下图所示,在 baseline 之上为负值,baseline 之下为正值,leading 表示两行文本 baseline 之间的距离,这个值可以由行间距倍数和行间距增加值来调整:
在接下来的字体测量中,会使用 fmCache 数组来缓存字体测量的信息,缓存 top, bottom, ascent, 和 descen 四个值,因此 fmCache 数组的大小始终是 4 的倍数。
- 其中有个
- 接下来就是按照一个个段落来处理文本:
for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) { paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd); if (paraEnd < 0) paraEnd = bufEnd; else paraEnd++; ... }
通过查找换行符,确定每个段落的起止位置,接下来的处理,均是对该段落文本的处理。
- span 文本的处理
- 处理段落文本 :
measured.setPara(source, paraStart, paraEnd, textDir, b); char[] chs = measured.mChars; float[] widths = measured.mWidths; byte[] chdirs = measured.mLevels; int dir = measured.mDir; boolean easy = measured.mEasy;
- 处理制表位,这里的制表位是使用
TabStopSpan
方式插入到文本中的,通过 Spanned 接口提供的getSpans(int start, int end, Class<T> type)
方法来获取到 TabStopSpan,排序后将所有的制表位的位置存在 variableTabStops 数组中。int[] variableTabStops = null; if (spanned != null) { TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, paraEnd, TabStopSpan.class); if (spans.length > 0) { int[] stops = new int[spans.length]; for (int i = 0; i < spans.length; i++) { stops[i] = spans[i].getTabStop(); } Arrays.sort(stops, 0, stops.length); variableTabStops = stops; }}
- 完成以上处理后,就是交给 JNI 层来处理段落文本,主要处理了段落的制表行缩进、折行等;需要再分析。
nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, firstWidth, firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency);
- 处理缩进的源码如下:
if (mLeftIndents != null || mRightIndents != null) { int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length; int rightLen = mRightIndents == null ? 0 : mRightIndents.length; int indentsLen = Math.max(1, Math.min(leftLen, rightLen) - mLineCount); int[] indents = new int[indentsLen]; for (int i = 0; i < indentsLen; i++) { int leftMargin = mLeftIndents == null ? 0 : mLeftIndents[Math.min(i + mLineCount, leftLen - 1)]; int rightMargin = mRightIndents == null ? 0 : mRightIndents[Math.min(i + mLineCount, rightLen - 1)]; indents[i] = leftMargin + rightMargin; } nSetIndents(b.mNativePtr, indents); }
开始的条件判断使用的 mLeftIndents 和 mRightIndents 变量是通过 Builder 对象来赋值的:
mLeftIndents = b.mLeftIndents; mRightIndents = b.mRightIndents;
但是比较困惑的是,源码中并没有对 Builder 对象这两个字段赋值的地方,因此这里的条件判断结果都是 false,实际 debug 测试了下,这个地方的判断确实始终是 false,所以具体的逻辑还需要再分析下。可以看见的是,在方法的最后,同样是调用 JNI 层的方法设置缩进。
- 缓存字体测量信息,源码如下:
for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { if (fmCacheCount * 4 >= fmCache.length) { int[] grow = new int[fmCacheCount * 4 * 2]; System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4); fmCache = grow; } if (spanEndCacheCount >= spanEndCache.length) { int[] grow = new int[spanEndCacheCount * 2]; System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); spanEndCache = grow; } if (spanned == null) { spanEnd = paraEnd; int spanLen = spanEnd - spanStart; measured.addStyleRun(paint, spanLen, fm); } else { spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, MetricAffectingSpan.class); int spanLen = spanEnd - spanStart; MetricAffectingSpan[] spans = spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class); measured.addStyleRun(paint, spans, spanLen, fm); } // the order of storage here (top, bottom, ascent, descent) has to match the code below // where these values are retrieved fmCache[fmCacheCount * 4 + 0] = fm.top; fmCache[fmCacheCount * 4 + 1] = fm.bottom; fmCache[fmCacheCount * 4 + 2] = fm.ascent; fmCache[fmCacheCount * 4 + 3] = fm.descent; fmCacheCount++; spanEndCache[spanEndCacheCount] = spanEnd; spanEndCacheCount++; }
fmCache 的初始化时的大小是 16,因此在每次循环开始时,需要判断下是否需要对 fmCache 扩容,这里的扩容同样保证了 fmCache 的大小是 4 的倍数,同时每次扩容时都是双倍扩容。
这里也会对文本中的 Span 的结束位置使用 spanEndCache 缓存记录下来,这里处理的 span 具体类型是 MetricAffectingSpan,顾名思义就是对字体会有影响的 Span,需要单独拿出来处理,缓存字体测量信息。
具体的测量则是交给 MeasuredText 类的
addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm)
和addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, Paint.FontMetricsInt fm)
方法来处理,具体的处理涉及到文字的排版,感兴趣的可以自己查看源码,这里不再详细分析了。测量完成后,字体测量信息的值 4 个一组地存储在 fmCache 数组中,spanEnd 值存储在 spanEndCache 数组中。
- 计算每行宽度和折行处理,宽度的计算和折行的处理分别借助 JNI 层的
nGetWidths()
和nComputeLineBreaks()
方法来处理。nGetWidths(b.mNativePtr, widths); // 得到当前行内包含的折行数目 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length); int[] breaks = lineBreaks.breaks; float[] lineWidths = lineBreaks.widths; int[] flags = lineBreaks.flags; // 得到剩下的行数 = 最大允许行数 - 当前行数 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount; final boolean ellipsisMayBeApplied = ellipsize != null && (ellipsize == TextUtils.TruncateAt.END || (mMaximumVisibleLineCount == 1 && ellipsize != TextUtils.TruncateAt.MARQUEE)); // 如果剩下的行数小于当前行包含的折行数目,则需要将最后一行和超出的行处理成单行 if (remainingLineCount > 0 && remainingLineCount < breakCount && ellipsisMayBeApplied) { // Treat the last line and overflowed lines as a single line. breaks[remainingLineCount - 1] = breaks[breakCount - 1]; // 计算 width 和 flag 值 float width = 0; int flag = 0; for (int i = remainingLineCount - 1; i < breakCount; i++) { width += lineWidths[i]; flag |= flags[i] & TAB_MASK; } lineWidths[remainingLineCount - 1] = width; flags[remainingLineCount - 1] = flag; // 设置当前行中的折行数为可用的行数 breakCount = remainingLineCount; }
处理完折行后,会判断下是否需要省略处理,如果需要,则根据允许的最大行数和当前行包含的折行数目来确定需要处理成省略的那一行,并设置相关的 width 和 flag 信息。
- 处理文本中 Span 和折行:
int here = paraStart; int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; int fmCacheIndex = 0; int spanEndCacheIndex = 0; int breakIndex = 0; for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { // 从之前存储的数据中获取 span 结束位置 spanEnd = spanEndCache[spanEndCacheIndex++]; // 恢复之前存储的字体测量信息 // retrieve cached metrics, order matches above fm.top = fmCache[fmCacheIndex * 4 + 0]; fm.bottom = fmCache[fmCacheIndex * 4 + 1]; fm.ascent = fmCache[fmCacheIndex * 4 + 2]; fm.descent = fmCache[fmCacheIndex * 4 + 3]; fmCacheIndex++; // 参照前面提到的字体测量的几个值的说明,这里的 top 和 ascent 取值小的,bottom 和 descent 取值大的,保证文本均可以正常显示 if (fm.top < fmTop) { fmTop = fm.top; } if (fm.ascent < fmAscent) { fmAscent = fm.ascent; } if (fm.descent > fmDescent) { fmDescent = fm.descent; } if (fm.bottom > fmBottom) { fmBottom = fm.bottom; } // 跳过 span 之前的折行 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { breakIndex++; } // 处理 span 中的折行 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { int endPos = paraStart + breaks[breakIndex]; boolean moreChars = (endPos < bufEnd); v = out(source, here, endPos, fmAscent, fmDescent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, chs, widths, paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, moreChars); if (endPos < spanEnd) { // 如果 Span 文本还未处理完成,则恢复当前的 fontMetrics 信息 // 否则归零处理,处理下一段 Span // preserve metrics for current span fmTop = fm.top; fmBottom = fm.bottom; fmAscent = fm.ascent; fmDescent = fm.descent; } else { fmTop = fmBottom = fmAscent = fmDescent = 0; } here = endPos; breakIndex++; // 如果处理该段落时行数已经超过最大可见行数,则直接终止后面的处理 if (mLineCount >= mMaximumVisibleLineCount) { return; } } } // 如果段落结束就是整个文本的结束,则跳出处理段落的循环,否则处理下一段。 if (paraEnd == bufEnd) break;
至此,以段落为单位的文本就处理完毕,包括文本的折行、Span 的处理都已完成。
- 当需要处理的文本起止位置相同时(即需要处理的文本为空),且前面是换行符时,此时也需要将该空白处理成一个段落。代码如下:
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { // Log.e("text", "output last " + bufEnd); measured.setPara(source, bufEnd, bufEnd, textDir, b); paint.getFontMetricsInt(fm); v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, fm.top, fm.bottom, v, spacingmult, spacingadd, null, null, fm, 0, needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, includepad, trackpad, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false); }
第 10 点和第 11 点分析中均出现了
out()
方法,前面提到,该方法也是 StaticLayout 源码中的一个重要的方法,接下来会分析下out
方法中做了什么处理。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论