前言

TextView是相当杂乱的UI组件,TextView不仅仅支撑纯文本展现,并且还支撑图片、SpannableString、文本输入、超链接等诸多功用,因而很多View本身也是直接继承自TextView的,如EditText、Button、Chronometer等。可见TextView功用十分强壮,基本上是app中运用率最高的View组件。

Android TextView 文本展现优化

不过 TextView 缺点也不少,主要问题点如下:

  • 跑马灯履行的条件过高,且部分属性有一定的重复问题
  • setText 容易触发requestLayout
  • 换行文本容易呈现犬牙(很多小说类app自行制作文本来处理此问题)

当然,以上是大多数状况中咱们容易遇到的问题。

优化办法

上面咱们列出了3个常见的问题,咱们这边逐一来看。

跑马灯问题

TextView对跑马灯的要求比较高,有必要是单行文本,并且有必要设置MaxLines,并且不支撑Lines设置,别的有必要是focused或许是selected,这明显添加了一些成本,要知道如果父布局focused,那么子View是不或许focused,明显对TV设备不行有好。可是别的一个问题,View能够一起具备Focused和Selected状况,这明显添加了问题的难度,为此咱们需求剥离focused状况。

   private void startMarquee() {
        // Do not ellipsize EditText
        if (getKeyListener() != null) return;
        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
//宽度大于0,或许硬件加速
            return;
        }
        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
                && getLineCount() == 1 && canMarquee()) {
            //获焦或许selected状况,由于focus相对于selected杂乱,主张运用selected
            //TextLayout行数有必要为1
            if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
                final Layout tmp = mLayout;
                mLayout = mSavedMarqueeModeLayout;
                mSavedMarqueeModeLayout = tmp;
                setHorizontalFadingEdgeEnabled(true);
                requestLayout();
                invalidate();
            }
            if (mMarquee == null) mMarquee = new Marquee(this);
            mMarquee.start(mMarqueeRepeatLimit);
        }
    }

那么,这儿咱们通过优化,使其仅在selected状况具备跑马灯,当然,如果你还想用selected状况完成其他用途,明显是无法运用了,不过体系中还有setEnable、setActivated状况供大家运用。

下面是跑马灯兼容逻辑

public class MarqueeTextView extends AppCompatTextView {
    private static final String TAG = "MarqueeTextView";
    private boolean isMarqueeEnable = false;
    public MarqueeTextView(Context context) {
        this(context, null);
    }
    public MarqueeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public MarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        /**
         * TextView.canMarquee() == false 时是不会滚动的
         * 一般原因是行数问题影响,导致宽度不合适,而android:lines是无效的
         *  focus 或许 selected状况才能跑马灯
         */
        setMaxLines(1);
        setSingleLine(true);
        if (isMarqueeEnable) {
            setMarqueeRepeatLimit(-1);
            setEllipsize(TextUtils.TruncateAt.MARQUEE);
        } else {
            setMarqueeRepeatLimit(0);
            setEllipsize(TextUtils.TruncateAt.END);
        }
        super.setSelected(isMarqueeEnable);
    }
    public void setMarqueeEnable(boolean enable) {
        if (isMarqueeEnable != enable) {
            isMarqueeEnable = enable;
            if (enable) {
                super.setSelected(true);
                setMarqueeRepeatLimit(-1);
                setEllipsize(TextUtils.TruncateAt.MARQUEE);
            } else {
                super.setSelected(false);
                setMarqueeRepeatLimit(0);
                setEllipsize(TextUtils.TruncateAt.END);
            }
        }
    }
    public boolean isMarqueeEnable() {
        return isMarqueeEnable;
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!isMarqueeEnable) {
            return;
        }
        if (getLineCount() > 1) {
            Log.e(TAG, "the marquee will not work if  TextLineCount > 1");
        }
        if (getMarqueeRepeatLimit() <= 0) {
            Log.e(TAG, "the marquee may not work if  MarqueeRepeatLimit != -1");
        }
    }
    @Override
    public void setSelected(boolean selected) {
   //复写此办法,制止外部调用,确保只有内部调用
    }
}

频频触发requestLayout

TextView很容易触发requestLayout,除非长宽有必要是固定巨细的,不过固定巨细或许遇到文本展现的不全的问题,别的Google也供给了PrecomputedText异步丈量文本的办法去优化功能,可是requestLayout造成的功能问题实际上比丈量要大,别的PrecomputedText编码办法也不行方便。

那么有没有更好的办法去抑制requestLayout的频频调用呢?

实际上咱们对单行文本的运用远超多行文本,即便是播放器时刻进度也是单行文本,因而咱们能够自行丈量单行文本,比较前后的尺度差异,选择性调用requestLayout。

办法很多,这儿咱们运用BoringLayout优化,当然在android 5.0之前的版本BoringLayout 兼容性并不好,因而这儿还引入StaticLayout进行兜底。

优化setText

构建Layout

    protected Layout buildTextLayout(CharSequence text, int wantWidth) {
        // fixed: Chinese word is not boring in android 4.4,在Android 4.4长度或许小于measureText丈量的
        float measureTextWidth = mTextPaint.measureText(text, 0, text.length());
        BoringLayout.Metrics boring = BoringLayout.isBoring(text, mTextPaint,UNKNOWN_BORING);
        float lineSpaceMult = mLineSpacingMult;
        if (lineSpaceMult < 1F) {
            lineSpaceMult = 1.0f;
        }
        if (boring != null) {
            int outWidth = wantWidth != ANY_WIDTH ? wantWidth : (int) (Math.max(boring.width,measureTextWidth) + mLineSpacingMult + mLineSpacingAdd);
            return BoringLayout.make(text, mTextPaint,
                    outWidth,
                    Layout.Alignment.ALIGN_NORMAL,
                    0,
                    mLineSpacingAdd,
                    boring,
                    mIncludeFontPadding);
        }
        //fix Android  4.4  mLineSpacingMult 有必要大于0
        float desiredWidthForStaticLayout = StaticLayout.getDesiredWidth(text, mTextPaint);
        int desiredWidth = (int) (Math.max(desiredWidthForStaticLayout,measureTextWidth) + mLineSpacingMult + mLineSpacingAdd);
        int outWidth = wantWidth != ANY_WIDTH ? wantWidth : desiredWidth;
        StaticLayout staticLayout = new StaticLayout(text,
                mTextPaint,
                outWidth,
                Layout.Alignment.ALIGN_NORMAL,
                lineSpaceMult,
                mLineSpacingAdd,
                mIncludeFontPadding);
        return staticLayout;
    }

设置文本

    public void setText(final CharSequence text) {
        CharSequence targetText = text == null ? "" : text;
        if (mLayout != null && TextUtils.equals(targetText, this.mText)) {
            return;
        }
        this.mText = targetText;
        if (!isAttachedToWindow()) {
            mLayout = null;
            mHintLayout = null;
            return;
        }
        if (measureWidthMode == -1 || measureHeightMode == -1) {
            mLayout = null;
            mHintLayout = null;
            requestLayout();
            invalidate();
            return;
        }
        int width = measureWidthMode == MeasureSpec.EXACTLY ? getMeasuredWidth() : ANY_WIDTH;
        mHintLayout = buildTextLayout(text, width);
        int desireWidth = mHintLayout.getWidth() + getPaddingLeft() + getPaddingRight();
        int desireHeight = getTextLayoutHeight(mHintLayout)+ getPaddingTop() + getPaddingBottom();
        if (desireWidth != getWidth() || measureHeightMode != MeasureSpec.EXACTLY && desireHeight != getHeight()) {
            mLayout = null;
            requestLayout();
        } else {
            mLayout = mHintLayout;
            mHintLayout = null;
        }
        invalidate();
    }

完好代码

public class BoringTextView extends View {
    private static final int ANY_WIDTH = -1;
    private static final String TAG = "BoringTextView";
    private TextPaint mTextPaint;
    private DisplayMetrics mDisplayMetrics;
    private int mContentHeight = 0;
    private int mContentWidth = 0;
    private Layout mLayout;
    private Layout mHintLayout;
    private int mTextColor;
    private ColorStateList mTextColorStateList;
    private CharSequence mText = "";
    private boolean mIncludeFontPadding = false;
    private int measureWidthMode = -1;
    private int measureHeightMode = -1;
    // fixed: mSpacingMult in android 4.4 must be greater 0
    private float mLineSpacingMult = 1.0f;
    private float mLineSpacingAdd = 0.0f;
    public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
    public BoringTextView(Context context) {
        this(context, null);
    }
    public BoringTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint(context, attrs, 0, 0);
    }
    public BoringTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint(context, attrs, defStyleAttr, 0);
    }
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BoringTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initPaint(context, attrs, defStyleAttr, defStyleRes);
    }
    private void initPaint(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        Resources resources = getResources();
        mDisplayMetrics = resources.getDisplayMetrics();
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setTextSize(sp2px(12));
        mTextPaint.density = mDisplayMetrics.density;
        mTextColorStateList = ColorStateList.valueOf(Color.GRAY);
        if (attrs != null) {
            int[] attrset = {
                    //注意顺序,从大到小,否则无法正常获取
                    android.R.attr.textSize,
                    android.R.attr.textColor,
                    android.R.attr.text,
                    android.R.attr.includeFontPadding
            };
            TypedArray attributes = context.obtainStyledAttributes(attrs, attrset, defStyleAttr, defStyleRes);
            int length = attributes.getIndexCount();
            for (int i = 0; i < length; i++) {
                int attrIndex = attributes.getIndex(i);
                int attrItem = attrset[attrIndex];
                switch (attrItem) {
                    case android.R.attr.text:
                        CharSequence text = attributes.getText(attrIndex);
                        setText(text);
                        break;
                    case android.R.attr.textColor:
                        //涉及到ColorStateList ,暂不做支撑动态切换
                        ColorStateList colorStateList = attributes.getColorStateList(attrIndex);
                        if (colorStateList != null) {
                            mTextColorStateList = colorStateList;
                        }
                        break;
                    case android.R.attr.textSize:
                        int dimensionPixelSize = attributes.getDimensionPixelSize(attrIndex, (int) sp2px(12));
                        mTextPaint.setTextSize(dimensionPixelSize);
                        break;
                    case android.R.attr.includeFontPadding:
                        mIncludeFontPadding = attributes.getBoolean(attrIndex, false);
                        break;
                }
            }
            attributes.recycle();
        }
        setTextColor(mTextColorStateList);
    }
    public void setTypeface(Typeface tf, int style) {
        if (style > 0) {
            if (tf == null) {
                tf = Typeface.defaultFromStyle(style);
            } else {
                tf = Typeface.create(tf, style);
            }
            setTypeface(tf);
            // now compute what (if any) algorithmic styling is needed
            int typefaceStyle = tf != null ? tf.getStyle() : 0;
            int styleFlags = style & ~typefaceStyle;
            mTextPaint.setFakeBoldText((styleFlags & Typeface.BOLD) != 0);
            mTextPaint.setTextSkewX((styleFlags & Typeface.ITALIC) != 0 ? -0.25f : 0);
        } else {
            mTextPaint.setFakeBoldText(false);
            mTextPaint.setTextSkewX(0);
            setTypeface(tf);
        }
    }
    public void setTypeface(Typeface tf) {
        if (mTextPaint.getTypeface() != tf) {
            mTextPaint.setTypeface(tf);
            if (mLayout != null) {
                requestLayout();
                invalidate();
            }
        }
    }
    public Typeface getTypeface() {
        if (mTextPaint != null) {
            return mTextPaint.getTypeface();
        }
        return null;
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int defaultWidth = MeasureSpec.getSize(widthMeasureSpec);
        if (measureWidthMode != -1 && measureWidthMode != widthMode) {
            mHintLayout = null;
        }
        int widthSize = defaultWidth;
        if (widthMode != MeasureSpec.EXACTLY) {
            if (mHintLayout == null) {
                //在setText时现已核算过了,直接复用mHintLayout
                mLayout = buildTextLayout(this.mText, ANY_WIDTH);
            } else {
                mLayout = mHintLayout;
            }
            int requestWidth = (getPaddingRight() + getPaddingLeft()) + (mLayout != null ? mLayout.getWidth() : 0);
            if(widthMode == MeasureSpec.AT_MOST){
                widthSize = Math.min(requestWidth,defaultWidth);
            }else {
                widthSize = requestWidth;
            }
        } else {
            if (mHintLayout == null) {
                int contentWidth = (widthSize - (getPaddingRight() + getPaddingLeft()));
                mLayout = buildTextLayout(this.mText, contentWidth);
            } else {
                mLayout = mHintLayout;
            }
        }
        int defaultHeight = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = 0;
        if (heightMode == MeasureSpec.AT_MOST) {
            heightSize = Math.min(getTextLayoutHeight(mLayout),defaultHeight);
        } else if (heightMode == MeasureSpec.UNSPECIFIED) {
            int desireHeight = getTextLayoutHeight(mLayout);
            heightSize = (getPaddingTop() + getPaddingBottom()) + desireHeight;
        }
        setMeasuredDimension(widthSize, heightSize);
        Log.i(TAG,"widthSize="+widthSize+", heightSize="+heightSize+",paddingTop="+getPaddingTop()+",paddingBottom="+getPaddingBottom());
        measureHeightMode = heightMode;
        measureWidthMode = widthMode;
        mHintLayout = null;
    }
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        measureWidthMode = -1;
        measureHeightMode = -1;
    }
    @Override
    public void setLayoutParams(ViewGroup.LayoutParams params) {
        measureWidthMode = -1;
        measureHeightMode = -1;
        super.setLayoutParams(params);
    }
    private int getTextLayoutHeight(Layout layout) {
        if(layout == null) {
            return 0;
        }
        int desireHeight = 0;
        desireHeight = layout.getHeight();
        if(desireHeight <= 0){
            int minTextLayoutLines = Math.min(layout.getLineCount(), 1);
            desireHeight =  Math.round(mTextPaint.getFontMetricsInt(null)* mLineSpacingMult + mLineSpacingAdd) * minTextLayoutLines;
        }
        return desireHeight;
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mContentHeight = (h - getPaddingTop() - getPaddingBottom());
        mContentWidth = (w - getPaddingLeft() - getPaddingRight());
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float strokeWidth = mTextPaint.getStrokeWidth() * 2;
        if (mContentWidth <= strokeWidth || mContentHeight <= strokeWidth) {
            return;
        }
        int save = canvas.save();
        if (mLayout != null) {
            int verticalHeight = getPaddingTop() + getPaddingBottom() + getTextLayoutHeight(mLayout);
            float offset = (getHeight() - verticalHeight) >> 1;
            if(offset < 0){
                offset = 0;
            }
            canvas.translate(getPaddingLeft(), getPaddingTop() + offset);
            mLayout.draw(canvas);
        }
        canvas.restoreToCount(save);
    }
    public void setText(final CharSequence text) {
        CharSequence targetText = text == null ? "" : text;
        if (mLayout != null && TextUtils.equals(targetText, this.mText)) {
            return;
        }
        this.mText = targetText;
        if (!isAttachedToWindow()) {
            mLayout = null;
            mHintLayout = null;
            return;
        }
        if (measureWidthMode == -1 || measureHeightMode == -1) {
            mLayout = null;
            mHintLayout = null;
            requestLayout();
            invalidate();
            return;
        }
        int width = measureWidthMode == MeasureSpec.EXACTLY ? getMeasuredWidth() : ANY_WIDTH;
        mHintLayout = buildTextLayout(text, width);
        int desireWidth = mHintLayout.getWidth() + getPaddingLeft() + getPaddingRight();
        int desireHeight = getTextLayoutHeight(mHintLayout)+ getPaddingTop() + getPaddingBottom();
        if (desireWidth != getWidth() || measureHeightMode != MeasureSpec.EXACTLY && desireHeight != getHeight()) {
            mLayout = null;
            requestLayout();
        } else {
            mLayout = mHintLayout;
            mHintLayout = null;
        }
        invalidate();
    }
    protected Layout buildTextLayout(CharSequence text, int wantWidth) {
        // fixed: Chinese word is not boring in android 4.4,在Android 4.4长度或许小于measureText丈量的
        float measureTextWidth = mTextPaint.measureText(text, 0, text.length());
        BoringLayout.Metrics boring = BoringLayout.isBoring(text, mTextPaint,UNKNOWN_BORING);
        float lineSpaceMult = mLineSpacingMult;
        if (lineSpaceMult < 1F) {
            lineSpaceMult = 1.0f;
        }
        if (boring != null) {
            int outWidth = wantWidth != ANY_WIDTH ? wantWidth : (int) (Math.max(boring.width,measureTextWidth) + mLineSpacingMult + mLineSpacingAdd);
            return BoringLayout.make(text, mTextPaint,
                    outWidth,
                    Layout.Alignment.ALIGN_NORMAL,
                    0,
                    mLineSpacingAdd,
                    boring,
                    mIncludeFontPadding);
        }
        //fix Android  4.4  mLineSpacingMult 有必要大于0
        float desiredWidthForStaticLayout = StaticLayout.getDesiredWidth(text, mTextPaint);
        int desiredWidth = (int) (Math.max(desiredWidthForStaticLayout,measureTextWidth) + mLineSpacingMult + mLineSpacingAdd);
        int outWidth = wantWidth != ANY_WIDTH ? wantWidth : desiredWidth;
        StaticLayout staticLayout = new StaticLayout(text,
                mTextPaint,
                outWidth,
                Layout.Alignment.ALIGN_NORMAL,
                lineSpaceMult,
                mLineSpacingAdd,
                mIncludeFontPadding);
        return staticLayout;
    }
    public float sp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, dp, mDisplayMetrics);
    }
    public void setIncludeFontPadding(boolean includePad) {
        this.mIncludeFontPadding = includePad;
        mHintLayout = null;
        mLayout = null;
        requestLayout();
        invalidate();
    }
    public void setTextColor(int color) {
        ColorStateList colorStateList = ColorStateList.valueOf(color);
        setTextColor(colorStateList);
    }
    public void setTextColor(ColorStateList colorStateList) {
        if (colorStateList == null) return;
        final int[] drawableState = getDrawableState();
        int forStateColor = colorStateList.getColorForState(drawableState, 0);
        mTextColor = forStateColor;
        mTextColorStateList = colorStateList;
        mTextPaint.setColor(forStateColor);
        postInvalidate();
    }
    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        if(mTextColorStateList!=null && mTextColorStateList.isStateful()) {
            setTextColor(mTextColorStateList);
        }
    }
    public int getCurrentTextColor() {
        return mTextColor;
    }
    public void setTextSize(float textSize) {
        mTextPaint.setTextSize(textSize);
    }
    public TextPaint getPaint() {
        return mTextPaint;
    }
    public CharSequence getText() {
        return mText;
    }
    @Override
    public boolean isAttachedToWindow() {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
            return super.isAttachedToWindow();
        }
        return getWindowToken() != null;
    }
}

犬牙问题:

这种问题的处理办法网上能搜出很多,可是对中文支撑最好的得参考下面文章 《关于TextView中换行后对齐问题

其间完成原理是对TextView重写,可是缺点是对英文支撑的不行好,不过关系不大,对英文分词即可快速完成。

其间心逻辑是:对最终一行的以外的其他文本行添加文字距离(word space),从而使得看起来犬牙的文本显的规整,但其本身并非是两头对齐。

    private void drawScaledText(Canvas canvas, int lineStart, String line,
                                float lineWidth) {
        float x = 0;
        if (isFirstLineOfParagraph(lineStart, line)) {
            String blanks = "  ";
            canvas.drawText(blanks, x, mLineY, getPaint());
            float bw = StaticLayout.getDesiredWidth(blanks, getPaint());
            x += bw;
            line = line.substring(3);
        }
        int gapCount = line.length() - 1;
        int i = 0;
        if (line.length() > 2 && line.charAt(0) == 12288
                && line.charAt(1) == 12288) {
            String substring = line.substring(0, 2);
            float cw = StaticLayout.getDesiredWidth(substring, getPaint());
            canvas.drawText(substring, x, mLineY, getPaint());
            x += cw;
            i += 2;
        }
        float d = (mViewWidth - lineWidth) / gapCount;
        for (; i < line.length(); i++) {
            String c = String.valueOf(line.charAt(i));
            float cw = StaticLayout.getDesiredWidth(c, getPaint());
            canvas.drawText(c, x, mLineY, getPaint());
            x += cw + d;
        }
    }

完好代码

public class TextAlignTextView extends TextView {
    private int mLineY;
    private int mViewWidth;
    public static final String TWO_CHINESE_BLANK = "  ";
    public TextAlignTextView (Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        TextPaint paint = getPaint();
        paint.setColor(getCurrentTextColor());
        paint.drawableState = getDrawableState();
        mViewWidth = getMeasuredWidth();
        String text = getText().toString();
        mLineY = 0;
        mLineY += getTextSize();
        Layout layout = getLayout();
        // layout.getLayout()在4.4.3呈现NullPointerException
        if (layout == null) {
            return;
        }
        Paint.FontMetrics fm = paint.getFontMetrics();
        int textHeight = (int) (Math.ceil(fm.descent - fm.ascent));
        textHeight = (int) (textHeight * layout.getSpacingMultiplier() + layout
                .getSpacingAdd());
        //处理了最终一行文字距离过大的问题
        for (int i = 0; i < layout.getLineCount(); i++) {
            int lineStart = layout.getLineStart(i);
            int lineEnd = layout.getLineEnd(i);
            float width = StaticLayout.getDesiredWidth(text, lineStart,
                    lineEnd, getPaint());
            String line = text.substring(lineStart, lineEnd);
            if(i < layout.getLineCount() - 1) {
                if (needScale(line)) {
                    drawScaledText(canvas, lineStart, line, width);
                } else {
                    canvas.drawText(line, 0, mLineY, paint);
                }
            } else {
                canvas.drawText(line, 0, mLineY, paint);
            }
            mLineY += textHeight;
        }
    }
    private void drawScaledText(Canvas canvas, int lineStart, String line,
                                float lineWidth) {
        float x = 0;
        if (isFirstLineOfParagraph(lineStart, line)) {
            String blanks = "  ";
            canvas.drawText(blanks, x, mLineY, getPaint());
            float bw = StaticLayout.getDesiredWidth(blanks, getPaint());
            x += bw;
            line = line.substring(3);
        }
        int gapCount = line.length() - 1;
        int i = 0;
        if (line.length() > 2 && line.charAt(0) == 12288
                && line.charAt(1) == 12288) {
            String substring = line.substring(0, 2);
            float cw = StaticLayout.getDesiredWidth(substring, getPaint());
            canvas.drawText(substring, x, mLineY, getPaint());
            x += cw;
            i += 2;
        }
        float d = (mViewWidth - lineWidth) / gapCount;
        for (; i < line.length(); i++) {
            String c = String.valueOf(line.charAt(i));
            float cw = StaticLayout.getDesiredWidth(c, getPaint());
            canvas.drawText(c, x, mLineY, getPaint());
            x += cw + d;
        }
    }
    private boolean isFirstLineOfParagraph(int lineStart, String line) {
        return line.length() > 3 && line.charAt(0) == ' '
                && line.charAt(1) == ' ';
    }
    private boolean needScale(String line) {
        if (line == null || line.length() == 0) {
            return false;
        } else {
            return line.charAt(line.length() - 1) != 'n';
        }
    }
}

总结

到这儿本篇就结束了,TextView作为Android中最杂乱的View组件之一,其间有很多办法的调用也是非公开的,别的其间的Editor也是没有公开的,这明显是造成TextView存在功能问题的原因之一。

本篇这儿的优化基本都有线上允许,在播放器中,咱们用到了BoringTextView和有跑马灯,有用降低了焦点问题和requestLayout频频的问题,当然文本的展现并不一定非得用BoringLayout和StaticLayout,也有很多办法能够完成此类优化。文本对齐问题,实际上在一些协议页面运用会获得很好的体验,这儿咱们就不再赘述了。