Androd setText性能優化 - BoringTextView

Android BoringTextView

public class BoringTextView extends View {

    private static final int ANY_WIDTH = -1;
    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;

    public BoringTextView(Context context) {
        this(context, null);
    }

    public BoringTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 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();
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        if (measureWidthMode != -1 && measureWidthMode != widthMode) {
            mHintLayout = null;
        }
        if (widthMode != MeasureSpec.EXACTLY) {
            if (mHintLayout == null) {
                //在setText時已經計算過了,直接複用mHintLayout
                mLayout = buildTextLayout(this.mText, ANY_WIDTH);
            } else {
                mLayout = mHintLayout;
            }
            widthSize = (getPaddingRight() + getPaddingLeft()) + (mLayout != null ? mLayout.getWidth() : 0);
        } else {
            if (mHintLayout == null) {
                int contentWidth = (widthSize - (getPaddingRight() + getPaddingLeft()));
                mLayout = buildTextLayout(this.mText, contentWidth);
            } else {
                mLayout = mHintLayout;
            }
        }

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (heightMode != MeasureSpec.EXACTLY) {
            heightSize = (getPaddingTop() + getPaddingBottom()) + (mLayout != null ? mLayout.getHeight() : 0);
        }
        setMeasuredDimension(widthSize, heightSize);

        measureWidthMode = widthMode;
        mHintLayout = null;
    }

    @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();
        canvas.translate(getPaddingLeft(), getPaddingTop());
        if (mLayout != null) {
            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) {
            mLayout = null;
            mHintLayout = null;
            requestLayout();
            invalidate();
            return;
        }
        int width = measureWidthMode == MeasureSpec.EXACTLY ? getMeasuredWidth() : ANY_WIDTH;
        mHintLayout = buildTextLayout(text, width);

        int disireWidth = mHintLayout.getWidth() + getPaddingLeft() + getPaddingRight();
        int disireHeight = mHintLayout.getHeight() + getPaddingTop() + getPaddingBottom();

        if (disireWidth != getWidth() || disireHeight != getHeight()) {
            mLayout = null;
            requestLayout();
        } else {
            mLayout = mHintLayout;
            mHintLayout = null;
        }
        invalidate();
    }

    protected Layout buildTextLayout(CharSequence text, int wantWidth) {
        BoringLayout.Metrics boring = BoringLayout.isBoring(text, mTextPaint);
        if (boring != null) {
            int outWidth = wantWidth != ANY_WIDTH ? wantWidth : boring.width;

            return BoringLayout.make(text, mTextPaint,
                    outWidth, Layout.Alignment.ALIGN_NORMAL,
                    0, 0.0f,
                    boring, mIncludeFontPadding);
        }
        //下面是兜底邏輯
        int desiredWidth = (int) StaticLayout.getDesiredWidth(text, mTextPaint);
        int outWidth = wantWidth != ANY_WIDTH ? wantWidth : desiredWidth;
        StaticLayout staticLayout = new StaticLayout(text,
                mTextPaint,
                outWidth,
                Layout.Alignment.ALIGN_NORMAL,
                0.0f,
                0.0f,
                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);
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        setTextColor(mTextColor);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章