返回介绍

4. TextView 的创建 Layout 的过程

发布于 2024-12-23 22:05:50 字数 8684 浏览 0 评论 0 收藏 0

TextView 内部并不仅仅只有一个用来显示文本内容的 Layout,在设置了 hint 的时候,还需要有一个 mHintLayout 来处理 hint 的内容。如果设置了 Ellipsize 类型为 Marquee 时,还会有一个 mSavedMarqueeModeLayout 专门用来显示 marquee 效果。这些 Layout 都是通过内部的 makeNewLayout 方法来创建的:

protected void makeNewLayout(int wantWidth, int hintWidth
                 BoringLayout.Metrics boring,
                 BoringLayout.Metrics hintBoring,
                 int ellipsisWidth, boolean bringIntoView) {
    //如果当前有 marquee 动画,则先停止动画
    stopMarquee();

    mOldMaximum = mMaximum;
    mOldMaxMode = mMaxMode;

    mHighlightPathBogus = true;

    if (wantWidth < 0) {
      wantWidth = 0;
    }
    if (hintWidth < 0) {
      hintWidth = 0;
    }

    //文本对齐方式
    Layout.Alignment alignment = getLayoutAlignment();
    final boolean testDirChange = mSingleLine && mLayout != null &&
      (alignment == Layout.Alignment.ALIGN_NORMAL ||
       alignment == Layout.Alignment.ALIGN_OPPOSITE);
    int oldDir = 0;
    if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
  
    //检测是否设置了 ellipsize
    boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
    final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
        mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
    TruncateAt effectiveEllipsize = mEllipsize;
    if (mEllipsize == TruncateAt.MARQUEE &&
        mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
      effectiveEllipsize = TruncateAt.END_SMALL;
    }
  
    //文本方向
    if (mTextDir == null) {
      mTextDir = getTextDirectionHeuristic();
    }

    //创建主 Layout
    mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
        effectiveEllipsize, effectiveEllipsize == mEllipsize);
  
    //非常规的 Marquee 模式下,需要创建 mSavedMarqueeModeLayout 来保存 marquee 动画时所用的 Layout,并且在动画期间把它和 TextView 的主 Layout 对换
    if (switchEllipsize) {
      TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
          TruncateAt.END : TruncateAt.MARQUEE;
      mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
          shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
    }

    shouldEllipsize = mEllipsize != null;
    mHintLayout = null;

    //判断是否需要创建 hintLayout
    if (mHint != null) {
      if (shouldEllipsize) hintWidth = wantWidth;

      if (hintBoring == UNKNOWN_BORING) {
        hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
                           mHintBoring);
        if (hintBoring != null) {
          mHintBoring = hintBoring;
        }
      }

      //判断是否为 boring,如果是则创建 BoringLayout
      if (hintBoring != null) {
        if (hintBoring.width <= hintWidth &&
          (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
          if (mSavedHintLayout != null) {
            mHintLayout = mSavedHintLayout.
                replaceOrMake(mHint, mTextPaint,
                hintWidth, alignment, mSpacingMult, mSpacingAdd,
                hintBoring, mIncludePad);
          } else {
            mHintLayout = BoringLayout.make(mHint, mTextPaint,
                hintWidth, alignment, mSpacingMult, mSpacingAdd,
                hintBoring, mIncludePad);
          }

          mSavedHintLayout = (BoringLayout) mHintLayout;
        } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
          if (mSavedHintLayout != null) {
            mHintLayout = mSavedHintLayout.
                replaceOrMake(mHint, mTextPaint,
                hintWidth, alignment, mSpacingMult, mSpacingAdd,
                hintBoring, mIncludePad, mEllipsize,
                ellipsisWidth);
          } else {
            mHintLayout = BoringLayout.make(mHint, mTextPaint,
                hintWidth, alignment, mSpacingMult, mSpacingAdd,
                hintBoring, mIncludePad, mEllipsize,
                ellipsisWidth);
          }
        }
      }
      
      //不是 boring 的状态下,用 StaticLayout 来创建
      if (mHintLayout == null) {
        StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
            mHint.length(), mTextPaint, hintWidth)
            .setAlignment(alignment)
            .setTextDirection(mTextDir)
            .setLineSpacing(mSpacingAdd, mSpacingMult)
            .setIncludePad(mIncludePad)
            .setBreakStrategy(mBreakStrategy)
            .setHyphenationFrequency(mHyphenationFrequency);
        if (shouldEllipsize) {
          builder.setEllipsize(mEllipsize)
              .setEllipsizedWidth(ellipsisWidth)
              .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
        }
        mHintLayout = builder.build();
      }
    }

    if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
      registerForPreDraw();
    }

    //判断是否需要开始 Marquee 动画
    if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
      if (!compressText(ellipsisWidth)) {
        final int height = mLayoutParams.height;
        if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
          startMarquee();
        } else {
          mRestartMarquee = true;
        }
      }
    }

    if (mEditor != null) mEditor.prepareCursorControllers();
  }

TextView 的布局创建过程涉及到一个 boring 的概念,boring 是指布局所用的文本里面不包含任何 Span,所有的文本方向都是从左到右的布局,并且仅需一行就能显示完全的布局。这种情况下,TextView 会使用 BoringLayout 类来创建相关的布局,以节省不必要的文本测量以及文本折行、Span 宽度、文本方向等的计算。下面我们来看一下 makeNewLayout 中使用频率比较高的 makeSingleLayout 的代码:

private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
      Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
      boolean useSaved) {
    Layout result = null;
    //判断是否 Spannable,如果是则用 DynamicLayout 类来创建布局,DynamicLayout 内部实际也是使用 StaticLayout 来做文本的测量绘制,并在 StaticLayout 的基础上增加了文本或者 Span 改变时的监听,及时对文本或者 Span 的变化做出反应。
    if (mText instanceof Spannable) {
      result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
          alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
          mBreakStrategy, mHyphenationFrequency,
          getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
    } else {
      //如果 boring 是未知状态,则重新判断一次是否 boring
      if (boring == UNKNOWN_BORING) {
        boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
        if (boring != null) {
          mBoring = boring;
        }
      }

      //根据 boring 的属性来创建对应的布局,如果有 mSavedLayout 则从 mSavedLayout 创建
      if (boring != null) {
        if (boring.width <= wantWidth &&
            (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
          if (useSaved && mSavedLayout != null) {
            //从之前保存的 Layout 中创建
            result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                boring, mIncludePad);
          } else {
            //创建新的 Layout
            result = BoringLayout.make(mTransformed, mTextPaint,
                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                boring, mIncludePad);
          }

          if (useSaved) {
            mSavedLayout = (BoringLayout) result;
          }
        } else if (shouldEllipsize && boring.width <= wantWidth) {
          if (useSaved && mSavedLayout != null) {
            result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                boring, mIncludePad, effectiveEllipsize,
                ellipsisWidth);
          } else {
            result = BoringLayout.make(mTransformed, mTextPaint,
                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                boring, mIncludePad, effectiveEllipsize,
                ellipsisWidth);
          }
        }
      }
    }
  
    //如果没有创建 BoringLayout, 则使用 StaticLayout 类来创建布局
    if (result == null) {
      StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
          0, mTransformed.length(), mTextPaint, wantWidth)
          .setAlignment(alignment)
          .setTextDirection(mTextDir)
          .setLineSpacing(mSpacingAdd, mSpacingMult)
          .setIncludePad(mIncludePad)
          .setBreakStrategy(mBreakStrategy)
          .setHyphenationFrequency(mHyphenationFrequency);
      if (shouldEllipsize) {
        builder.setEllipsize(effectiveEllipsize)
            .setEllipsizedWidth(ellipsisWidth)
            .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
      }
      result = builder.build();
    }
    return result;
  }

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

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

发布评论

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