joy keeps flowin'

TextView setText发生了什么?

xx
目次

前置知识 #

Span #

既然是setText,想必你一定听过Span的大名,借助Span和SpannableString就可以让文字不光是文字,文字颜色、背景颜色、文字点击、链接还可以自定义更复杂的效果,

MovementMethod #

文字上的点击、光标选中都是由它实现的,详细的之后单独写。

Editor #

和编辑文字相关,之后也单独写。

mTransformation #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    /**
     * Sets the transformation that is applied to the text that this
     * TextView is displaying.
     *
     * @attr ref android.R.styleable#TextView_password
     * @attr ref android.R.styleable#TextView_singleLine
     */
    public final void setTransformationMethod(TransformationMethod method) {
        if (mEditor != null) {
            mEditor.setTransformationMethod(method);
        } else {
            setTransformationMethodInternal(method);
        }
    }

BufferType #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// definition
@UnsupportedAppUsage
private BufferType mBufferType = BufferType.NORMAL;
    
    
/**
 * Type of the text buffer that defines the characteristics of the text such as static,
 * styleable, or editable.
 */
public enum BufferType {
    NORMAL, SPANNABLE, EDITABLE
}

正文 #

作为Android中最基本的控件,TextView简直是无处不在,最最常用的用法像是textView.setText("test"),无论是传入String,还是SpannableString都能显示,为什么能起作用?

setText方法找到方法实现:

1
2
3
public final void setText(CharSequence text) {
        setText(text, mBufferType);
    }

嗯?CharSequence?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class String : Comparable<String>, CharSequence{
    ...
}

expect class StringBuilder : Appendable, CharSequence {
    ...
}

public class SpannableString
extends SpannableStringInternal
implements CharSequence, GetChars, Spannable
{
    ...
}

Android中String和StringBuilder都是CharSequence的子类。

mBufferType又是什么东西?先不管它,接着往下找。

 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
/**
 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
 * <p/>
 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
 * intermediate {@link Spannable Spannables}. Likewise it will use
 * {@link android.text.Editable.Factory} to create final or intermediate
 * {@link Editable Editables}.
 *
 * Subclasses overriding this method should ensure that the following post condition holds,
 * in order to guarantee the safety of the view's measurement and layout operations:
 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
 * will be different from {@code null}.
 *
 * @param text text to be displayed
 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
 *              stored as a static text, styleable/spannable text, or editable text
 *
 * @see #setText(CharSequence)
 * @see android.widget.TextView.BufferType
 * @see #setSpannableFactory(Spannable.Factory)
 * @see #setEditableFactory(Editable.Factory)
 *
 * @attr ref android.R.styleable#TextView_text
 * @attr ref android.R.styleable#TextView_bufferType
 */
public void setText(CharSequence text, BufferType type) {
    setText(text, type, true, 0);

    // drop any potential mCharWrappper leaks
    mCharWrapper = null;
}

又是嵌套,从上面的代码注释中知道了两点:

  1. text是传入的文字内容;
  2. type定义展示文字类型。

private void setText(CharSequence text, BufferType type,boolean notifyBefore, int oldlen) 聪明的你从名字上一定看出了一些什么,notifyBefore大概率是要不要调用callback了,oldlen是长度。 在看代码实现前,你没有疑问吗?即使setText传入SpannableString,type我们可没改,也就是说用的是默认的,里面一定有判断text类型的逻辑。 去代码里找答案。(大量代码预警)

  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
179
180
181
182
183
184
185
186
187
private void setText(CharSequence text, BufferType type,
                     boolean notifyBefore, int oldlen) {
    mTextSetFromXmlOrResourceId = false;
    if (text == null) {
        text = "";
    }
    // 移除SuggestionSpan,类似Word中拼写检查
    // If suggestions are not enabled, remove the suggestion spans from the text
    if (!isSuggestionsEnabled()) {
        text = removeSuggestionSpans(text);
    }
    if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
    // 跑马灯效果
    if (text instanceof Spanned
            && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
        if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
            setHorizontalFadingEdgeEnabled(true);
            mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
        } else {
            setHorizontalFadingEdgeEnabled(false);
            mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
        }
        setEllipsize(TextUtils.TruncateAt.MARQUEE);
    }
    // 按照mFilters过滤文字
    int n = mFilters.length;
    for (int i = 0; i < n; i++) {
        CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
        if (out != null) {
            text = out;
        }
    }
    // 方法中第三个参数起作用
    if (notifyBefore) {
        if (mText != null) {
            oldlen = mText.length();
            sendBeforeTextChanged(mText, 0, oldlen, text.length());
        } else {
            sendBeforeTextChanged("", 0, 0, text.length());
        }
    }
    boolean needEditableForNotification = false;
    if (mListeners != null && mListeners.size() != 0) {
        needEditableForNotification = true;
    }
    // TextView文字排版相关
    PrecomputedText precomputed =
            (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
    // 可以编辑的内容重置状态,deep copy,最重要的是初始化Editor,后面用到。
    if (type == BufferType.EDITABLE || getKeyListener() != null
            || needEditableForNotification) {
        createEditorIfNeeded();
        mEditor.forgetUndoRedo();
        mEditor.scheduleRestartInputForSetText();
        Editable t = mEditableFactory.newEditable(text);
        text = t;
        setFilters(t, mFilters);
    } else if (precomputed != null) {
        if (mTextDir == null) {
            mTextDir = getTextDirectionHeuristic();
        }
        final boolean autoPhraseBreaking =
                !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
                        FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
        final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
                precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
                        mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
                                mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
        switch (checkResult) {
            case PrecomputedText.Params.UNUSABLE:
                throw new IllegalArgumentException(
                    "PrecomputedText's Parameters don't match the parameters of this TextView."
                    + "Consider using setTextMetricsParams(precomputedText.getParams()) "
                    + "to override the settings of this TextView: "
                    + "PrecomputedText: " + precomputed.getParams()
                    + "TextView: " + getTextMetricsParams());
            case PrecomputedText.Params.NEED_RECOMPUTE:
                precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
                break;
            case PrecomputedText.Params.USABLE:
                // pass through
        }
    } else if (type == BufferType.SPANNABLE || mMovement != null) {
        // SPANNABLE deep copy
        text = mSpannableFactory.newSpannable(text);
    } else if (!(text instanceof CharWrapper)) {
        text = TextUtils.stringOrSpannedString(text);
    }
    @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE;
    if (AccessibilityManager.getInstance(mContext).isEnabled()) {
        a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText);
    }
    // 设置autolink更新type
    if (mAutoLinkMask != 0) {
        Spannable s2;
        if (type == BufferType.EDITABLE || text instanceof Spannable) {
            s2 = (Spannable) text;
        } else {
            s2 = mSpannableFactory.newSpannable(text);
        }
        if (Linkify.addLinks(s2, mAutoLinkMask)) {
            text = s2;
            type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
            /*
             * We must go ahead and set the text before changing the
             * movement method, because setMovementMethod() may call
             * setText() again to try to upgrade the buffer type.
             */
            setTextInternal(text);
            if (a11yTextChangeType == AccessibilityUtils.NONE) {
                a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN;
            }
            // Do not change the movement method for text that support text selection as it
            // would prevent an arbitrary cursor displacement.
            if (mLinksClickable && !textCanBeSelected()) {
                setMovementMethod(LinkMovementMethod.getInstance());
            }
        }
    }
    // 更新BufferType
    mBufferType = type;
    // 更新文字
    setTextInternal(text);
    // 像是把密码替换成*的文字处理类
    if (mTransformation == null) {
        mTransformed = text;
    } else {
        mTransformed = mTransformation.getTransformation(text, this);
    }
    if (mTransformed == null) {
        // Should not happen if the transformation method follows the non-null postcondition.
        mTransformed = "";
    }
    final int textLength = text.length();
    final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
    // !!!更新Span
    if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) {
        Spannable sp = (Spannable) text;
        // Remove any ChangeWatchers that might have come from other TextViews.
        final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
        final int count = watchers.length;
        for (int i = 0; i < count; i++) {
            sp.removeSpan(watchers[i]);
        }
        if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
        sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
                | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
        if (mEditor != null) mEditor.addSpanWatchers(sp);
        if (mTransformation != null) {
            final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
            sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
                    | (priority << Spanned.SPAN_PRIORITY_SHIFT));
        }
        if (mMovement != null) {
            mMovement.initialize(this, (Spannable) text);
            /*
             * Initializing the movement method will have set the
             * selection, so reset mSelectionMoved to keep that from
             * interfering with the normal on-focus selection-setting.
             */
            if (mEditor != null) mEditor.mSelectionMoved = false;
        }
    }
    if (mLayout != null) {
        checkForRelayout();
    }
    sendOnTextChanged(text, 0, oldlen, textLength);
    onTextChanged(text, 0, oldlen, textLength);
    mHideHint = false;
    if (a11yTextChangeType == AccessibilityUtils.TEXT) {
        notifyViewAccessibilityStateChangedIfNeeded(
                AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
    } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) {
        notifyViewAccessibilityStateChangedIfNeeded(
                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
    }
    if (needEditableForNotification) {
        sendAfterTextChanged((Editable) text);
    } else {
        notifyListeningManagersAfterTextChanged();
    }
    if (mEditor != null) {
        // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
        mEditor.prepareCursorControllers();
        mEditor.maybeFireScheduledRestartInputForSetText();
    }
}

乍一看setTextInternal像是更新文字的,点进去才发现是更新变量的。 又一看mMovement.initialize(this, (Spannable) text)也很像,点进去发现又是重置状态的。 大失所望,又一想文字改变的callback还没看呢! sendOnTextChanged还是onTextChanged呢,一看sendOnTextChanged有内容而onTextChanged是空实现,那就是sendOnTextChanged。很遗憾虽然有callback,但也不是。

从Span入手 #

ForegroundColorSpan起作用一定要调用什么方法,ForegroundColorSpan和BackgroundColorSpan同时是抽象类CharacterStyle的子类,updateDrawState方法是需要重写的,依我看十有八九是它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
    // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
    // empty by construction. This special case in getSpans() explains the >= & <= tests
    if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit)
            || (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continu
    boolean insideEllipsis =
            mStart + mEllipsisStart <= mMetricAffectingSpanSpanSet.spanStarts[j]
            && mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + mEllipsisEnd;
    final MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
    if (span instanceof ReplacementSpan) {
        replacement = !insideEllipsis ? (ReplacementSpan) span : null;
    } else {
        // We might have a replacement that uses the draw
        // state, otherwise measure state would suffice.
        span.updateDrawState(wp);
    }
}

验证了上面猜测。但是,Span可以点击呢,当然没有忘记在LinkMovementMethod中实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
case CLICK:
    if (selStart == selEnd) {
        return false;

    ClickableSpan[] links = buffer.getSpans(selStart, selEnd, ClickableSpan.class
    if (links.length != 1) {
        return false;

    ClickableSpan link = links[0];
    if (link instanceof TextLinkSpan) {
        ((TextLinkSpan) link).onClick(widget, TextLinkSpan.INVOCATION_METHOD_KEYBOARD);
    } else {
        link.onClick(widget);
    }
    break;
标签:
Categories: