返回介绍

3. CharacterStyle

发布于 2024-12-23 22:16:00 字数 4067 浏览 0 评论 0 收藏 0

Span 有很多种,这里拿最简单的 CharacterStyle 来举例说明我们设置的 Span 最终是如何影响到文字绘制的。前面的代码中出现的 UnderlineSpan 就是 CharacterStyle 的子类之一,可以在 官网 上看到其他的子类。CharacterStyle 有一个抽象方法是 updateDrawState,下面是 UnderlineSpan 的简易版:

public class UnderlineSpan extends CharacterStyle 
  implements UpdateAppearance, ParcelableSpan {
  ...

  @Override
  public void updateDrawState(TextPaint ds) {
    ds.setUnderlineText(true);
  }
}

从这个实现来看,在文字绘制的时候会将绘制文字的 TextPaint 传进来,在这里更新其设置后就能实现对应的文字效果。从 TextView 源码分析 中可以看出在对 Span 的处理上分为两步:

TextLine tl = TextLine.obtain();

tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
tl.draw(canvas, x, ltop, lbaseline, lbottom);

通过 TextLine.obtain() 从 shared pool 中获取一个 TextLine(这么做的原因是这个对象本身比较大而且使用次数多,为了避免多次创建导致的频繁 GC),然后通过 TextLine.set 对 TextLine 进行初始化,最后 draw 方法绘制出文字。从源码可以看出在 set 的时候保存了 Span:

void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
      Directions directions, boolean hasTabs, TabStops tabStops) {
  mPaint = paint;
  mText = text;
  mStart = start;
  mLen = limit - start;
  mDir = dir;
  mDirections = directions;
  if (mDirections == null) {
    throw new IllegalArgumentException("Directions cannot be null");
  }
  mHasTabs = hasTabs;
  mSpanned = null;
  boolean hasReplacement = false;
  if (text instanceof Spanned) {
    mSpanned = (Spanned) text;   // 保存 span
    mReplacementSpanSpanSet.init(mSpanned, start, limit);
    hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
  }
  ...
}

然后是 draw 方法:

void draw(Canvas c, float x, int top, int y, int bottom) {
  if (!mHasTabs) {
    if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
      drawRun(c, 0, mLen, false, x, top, y, bottom, false);
      return;
    }
    if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
      drawRun(c, 0, mLen, true, x, top, y, bottom, false);
      return;
    }
  }
  ...
}

 private float drawRun(Canvas c, int start,
    int limit, boolean runIsRtl, float x, int top, int y, int bottom,
    boolean needWidth) {
  if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
    float w = -measureRun(start, limit, limit, runIsRtl, null);
    handleRun(start, limit, limit, runIsRtl, c, x + w, top,
        y, bottom, null, false);
    return w;
  }
  return handleRun(start, limit, limit, runIsRtl, c, x, top,
      y, bottom, null, needWidth);
}

private float handleRun(int start, int measureLimit,
      int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
      int bottom, FontMetricsInt fmi, boolean needWidth) {
  ...
  // 解析 Span
  mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
  ...
  for (int j = i, jnext; j < mlimit; j = jnext) {
    jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) -
        mStart;
    int offset = Math.min(jnext, mlimit);
    wp.set(mPaint);
    // 遍历 Span
    for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
      // Intentionally using >= and <= as explained above
      if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) ||
          (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
      CharacterStyle span = mCharacterStyleSpanSet.spans[k];
      span.updateDrawState(wp);  // updateDrawState!!!
    }
    // Only draw hyphen on last run in line
    if (jnext < mLen) {
      wp.setHyphenEdit(0);
    }
    x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
        top, y, bottom, fmi, needWidth || jnext < measureLimit, offset);
  }
  ...
}

几经周折到了 handleRun 这里,首先是 CharacterStyleSpanSet 的初始化,初始化的时候会将 mSpanned 中所带的 Span 都解析出来具体的过程可以看 SpanSet 的源码,然后使用 updateDrawState 更新 TextPaint 加入想要的效果,最后通过 handleText 绘制。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文