天天看點

強大的提示控件TextInputLayout使用以及源碼分析

本篇部落格帶來一個具有強大提示的Material Design風格的控件TextInputLayout,使用這個控件可以非常友善的做出使用者登入界面帳号密碼輸入框的效果,文章将會從以下TextInputLayout使用和TextInputLayout源碼分析兩個方面對這個強大的控件進行分析。

TextInputLayout的使用

這裡使用TextInputLayout簡單寫一個登入的界面

布局代碼

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context="com.example.administrator.textinputdemo2.LoginActivity">



    <ScrollView
        android:id="@+id/login_form"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/email_login_form"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:hintAnimationEnabled="true"
                app:errorEnabled="true"
                android:id="@+id/textInputLayout">

                <AutoCompleteTextView
                    android:id="@+id/email"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/prompt_email"
                    android:inputType="textEmailAddress"
                    android:maxLines="1"
                    android:singleLine="true" />

            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <EditText
                    android:id="@+id/password"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/prompt_password"
                    android:imeActionId="@+id/login"
                    android:imeActionLabel="@string/action_sign_in_short"
                    android:imeOptions="actionUnspecified"
                    android:inputType="textPassword"
                    android:maxLines="1"
                    android:singleLine="true" />

            </android.support.design.widget.TextInputLayout>

            <Button
                android:id="@+id/email_sign_in_button"
                style="?android:textAppearanceSmall"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="@string/action_sign_in"
                android:textStyle="bold" />

        </LinearLayout>
    </ScrollView>
</LinearLayout>
           

LoginActivity代碼

public class LoginActivity extends AppCompatActivity {

    private TextInputLayout textInputLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        textInputLayout = (TextInputLayout)findViewById(R.id.textInputLayout);
        //檢測長度應該低于6位數
        textInputLayout.getEditText().addTextChangedListener(new MinLengthTextWatcher(textInputLayout, "長度應低于6位數!"));

        //開啟計數
        textInputLayout.setCounterEnabled(true);
        textInputLayout.setCounterMaxLength(10);//最大輸入限制數

    }
    class MinLengthTextWatcher implements TextWatcher{
        private String errorStr;
        private TextInputLayout textInputLayout;
        public  MinLengthTextWatcher(TextInputLayout textInputLayout, String errorStr){
            this.textInputLayout = textInputLayout;
            this.errorStr = errorStr;
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }

        @Override
        public void afterTextChanged(Editable s) {
            // 文字改變後回調
            if(textInputLayout.getEditText().getText().toString().length()<=6){
                textInputLayout.setErrorEnabled(false);
            }else{
                textInputLayout.setErrorEnabled(true);
                textInputLayout.setError(errorStr);
            }
        }
    }
}
           

使用TextInputLayout非常簡單,它是一個ViewGroup,裡面可以包裹EditText或者AutoCompleteTextView,以下幾個屬性和方法需要聲明一下:

app:hintAnimationEnabled="true"可以開啟動畫,這個為true時,獲得焦點的時候hint提示問題會動畫地移動上去。

app:errorEnabled="true"時,開啟錯誤提示

textInputLayout.setCounterEnabled(true);用于 開啟計數

textInputLayout.setCounterMaxLength(10);設定最大輸入限制數

textInputLayout.setError(errorStr);設定錯誤提示的資訊

textInputLayout.getEditText().addTextChangedListener()用于給textInputLayout包裹的EditText設定内容變化監聽,我們可以自己重寫一個監聽實作裡面的方法進行相關邏輯的處理

效果如下:

強大的提示控件TextInputLayout使用以及源碼分析

TextInputLayout源碼分析

TextInputLayout繼承自LinearLayout,說明它是一個ViewGroup

public class TextInputLayout extendsLinearLayout
           

先從構造函數開始看起

public TextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        // Can't call through to super(Context, AttributeSet, int) since it doesn't exist on API 10
        super(context, attrs);

        setOrientation(VERTICAL);
        setWillNotDraw(false);
        setAddStatesFromChildren(true);
           

這裡它自動設定了VERTICAL的Orientation,說明這個TextInputLayout是一個豎直的排列,那字數超過部分的提示,在哪裡添加的呢?說明在源碼中必定有添加這個提示的邏輯,這裡我們後面在讨論,先繼續往下看

mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
        mCollapsingTextHelper.setPositionInterpolator(new AccelerateInterpolator());
        mCollapsingTextHelper.setCollapsedTextGravity(Gravity.TOP | GravityCompat.START);

        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.TextInputLayout, defStyleAttr, R.style.Widget_Design_TextInputLayout);
        mHint = a.getText(R.styleable.TextInputLayout_android_hint);
        mHintAnimationEnabled = a.getBoolean(
                R.styleable.TextInputLayout_hintAnimationEnabled, true);
           

這裡出現了一個mCollapsingTextHelper,通過它可以設定文字大小的加速動畫,FAST_OUT_SLOW_IN_INTERPOLATOR,快出慢進的效果,還有設定位置的加速器setPositionInterpolator,setCollapsedTextGravity設定折疊文字的Gravity,看來這個mCollapsingTextHelper的作用還是很強大的,我們後面再看它的源碼,先繼續往下看

final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.TextInputLayout, defStyleAttr, R.style.Widget_Design_TextInputLayout);
        mHint = a.getText(R.styleable.TextInputLayout_android_hint);
        mHintAnimationEnabled = a.getBoolean(
                R.styleable.TextInputLayout_hintAnimationEnabled, true);

        if (a.hasValue(R.styleable.TextInputLayout_android_textColorHint)) {
            mDefaultTextColor = mFocusedTextColor =
                    a.getColorStateList(R.styleable.TextInputLayout_android_textColorHint);
        }

        final int hintAppearance = a.getResourceId(
                R.styleable.TextInputLayout_hintTextAppearance, -1);
        if (hintAppearance != -1) {
            setHintTextAppearance(
                    a.getResourceId(R.styleable.TextInputLayout_hintTextAppearance, 0));
        }

        mErrorTextAppearance = a.getResourceId(R.styleable.TextInputLayout_errorTextAppearance, 0);
        final boolean errorEnabled = a.getBoolean(R.styleable.TextInputLayout_errorEnabled, false);
a.recycle();
           

從TypedArray中取出一些使用者給TextInputLayout設定的屬性,比如給hint設定的文字,mHintAnimationEnabled,hint内文字的動畫是否可用,還有hintAppearance的值,mErrorTextAppearance是錯誤提示文字的樣式,errorEnabled是否開啟錯誤提示

setErrorEnabled(errorEnabled);
           

并通過setErrorEnabled把errorEnabled的值設定給TextInputLayout,TextInputLayout是一個ViewGroup,是以addView方法是必須的

public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (child instanceof EditText) {
            setEditText((EditText) child);
            super.addView(child, 0, updateEditTextMargin(params));
        } else {
            // Carry on adding the View...
            super.addView(child, index, params);
        }
}
           

隻有當child 是 EditText的時候,會調用自身的setEditText方法,然後調用父類LinearLayout的addView方法,如果不是EditText,也調用父類的addView方法,檢視setEditText方法 内部

private void setEditText(EditText editText) {
        // If we already have an EditText, throw an exception
        if (mEditText != null) {
            throw new IllegalArgumentException("We already have an EditText, can only have one");
        }
        mEditText = editText;

        // Use the EditText's typeface, and it's text size for our expanded text
        mCollapsingTextHelper.setTypeface(mEditText.getTypeface());
        mCollapsingTextHelper.setExpandedTextSize(mEditText.getTextSize());
        mCollapsingTextHelper.setExpandedTextGravity(mEditText.getGravity());
           

如果TextInputLayout内已經有了一個EditText,再添加就會報錯,使用CollapsingTextHelper把傳進來的editText的相關屬性取出進行設定

mEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(Editable s) {
                updateLabelVisibility(true)<pre name="code" class="java">
           

然後給EditText設定文本變化的監聽,在文本改變之前,正在改變的時候都可以做相應的邏輯處理,往下看有更改EditText的Margin的方法

private LayoutParams updateEditTextMargin(ViewGroup.LayoutParams lp) {
        // Create/update the LayoutParams so that we can add enough top margin
        // to the EditText so make room for the label
        LayoutParams llp = lp instanceof LayoutParams ? (LayoutParams) lp : new LayoutParams(lp);

        if (mTmpPaint == null) {
            mTmpPaint = new Paint();
        }
        mTmpPaint.setTypeface(mCollapsingTextHelper.getTypeface());
        mTmpPaint.setTextSize(mCollapsingTextHelper.getCollapsedTextSize());
        llp.topMargin = (int) -mTmpPaint.ascent();

        return llp;
}
           

設定提示文字的樣式

public void setHintTextAppearance(@StyleRes int resId) {
        mCollapsingTextHelper.setCollapsedTextAppearance(resId);
        mFocusedTextColor = ColorStateList.valueOf(mCollapsingTextHelper.getCollapsedTextColor());

        if (mEditText != null) {
            updateLabelVisibility(false);

            // Text size might have changed so update the top margin
            LayoutParams lp = updateEditTextMargin(mEditText.getLayoutParams());
            mEditText.setLayoutParams(lp);
            mEditText.requestLayout();
        }
}
           

設定錯誤提示開啟和關閉的方法

public void setErrorEnabled(boolean enabled) {
        if (mErrorEnabled != enabled) {
            if (mErrorView != null) {
                ViewCompat.animate(mErrorView).cancel();
            }

            if (enabled) {
                mErrorView = new TextView(getContext());
                mErrorView.setTextAppearance(getContext(), mErrorTextAppearance);
                mErrorView.setVisibility(INVISIBLE);
                addView(mErrorView);

                if (mEditText != null) {
                    // Add some start/end padding to the error so that it matches the EditText
                    ViewCompat.setPaddingRelative(mErrorView, ViewCompat.getPaddingStart(mEditText),
                            0, ViewCompat.getPaddingEnd(mEditText), mEditText.getPaddingBottom());
                }
            } else {
                removeView(mErrorView);
                mErrorView = null;
            }
            mErrorEnabled = enabled;
        }
}
           

如果enabled為true的時候,這裡會new一個TextView,給TextView設定文本資訊和設為可見,然後使用addView(mErrorView)方法,将其添加到TextInputLayout之中,還記得前面我們提過TextInputLayout之中肯定應該會有一個添加錯誤提示資訊的方法,在這裡我們找到了,同時這裡的代碼也是值得我們進行學習的,隻有當使用者設定錯誤提示為真的時候,才會new一個TextView,這樣是比較省性能的,接下來是setError方法,設定錯誤提示的文本資訊,裡面是一些判斷和動畫的設定

public void setError(@Nullable CharSequence error) {
        if (!mErrorEnabled) {
            if (TextUtils.isEmpty(error)) {
                // If error isn't enabled, and the error is empty, just return
                return;
            }
            // Else, we'll assume that they want to enable the error functionality
            setErrorEnabled(true);
        }

        if (!TextUtils.isEmpty(error)) {
            ViewCompat.setAlpha(mErrorView, 0f);
            mErrorView.setText(error);
            ViewCompat.animate(mErrorView)
                    .alpha(1f)
                    .setDuration(ANIMATION_DURATION)
                    .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
                        @Override
                        public void onAnimationStart(View view) {
                            view.setVisibility(VISIBLE);
                        }
                    })
                    .start();

            // Set the EditText's background tint to the error color
            ViewCompat.setBackgroundTintList(mEditText,
                    ColorStateList.valueOf(mErrorView.getCurrentTextColor()));
        } else {
            if (mErrorView.getVisibility() == VISIBLE) {
                ViewCompat.animate(mErrorView)
                        .alpha(0f)
                        .setDuration(ANIMATION_DURATION)
                        .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
                        .setListener(new ViewPropertyAnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(View view) {
                                view.setVisibility(INVISIBLE);
                            }
                        }).start();

                // Restore the 'original' tint, using colorControlNormal and colorControlActivated
                final TintManager tintManager = TintManager.get(getContext());
                ViewCompat.setBackgroundTintList(mEditText,
                        tintManager.getTintList(R.drawable.abc_edit_text_material));
            }
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
           

接下來是draw方法,調用了mCollapsingTextHelper的draw方法,說明這個TextInputLayout是畫出來

<pre name="code" class="java">public void draw(Canvas canvas) {
        super.draw(canvas);
        mCollapsingTextHelper.draw(canvas);
}
           

onLayout方法,會拿到EditText的Left,Right等屬性,然後使用mCollapsingTextHelper 來setExpandedBounds,設定一個Bound區域

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (mEditText != null) {
            final int l = mEditText.getLeft() + mEditText.getCompoundPaddingLeft();
            final int r = mEditText.getRight() - mEditText.getCompoundPaddingRight();

            mCollapsingTextHelper.setExpandedBounds(l,
                    mEditText.getTop() + mEditText.getCompoundPaddingTop(),
                    r, mEditText.getBottom() - mEditText.getCompoundPaddingBottom());

            // Set the collapsed bounds to be the the full height (minus padding) to match the
            // EditText's editable area
            mCollapsingTextHelper.setCollapsedBounds(l, getPaddingTop(),
                    r, bottom - top - getPaddingBottom());

            mCollapsingTextHelper.recalculate();
        }
}
           

上面的有一句注釋還是很重要的:設定折疊的bounds去比對EditText可編輯區域的高,接下來我們檢視CollapsingTextHelper這個非常重要的類的代碼

public CollapsingTextHelper(View view) {
        mView = view;

        mTextPaint = new TextPaint();
        mTextPaint.setAntiAlias(true);

        mCollapsedBounds = new Rect();
        mExpandedBounds = new Rect();
        mCurrentBounds = new RectF();
}
           

構造函數中會把view傳進來,而這個view就是TextInputLayout,同時new了一個TextPaint來進行文本的繪制,然後是new出來3個矩形區域,mCollapsedBounds:輸入框處于折疊狀态下的矩形區域,mExpandedBounds:提示框獲得焦點,提示文字向上展開的矩形區域,mCurrentBounds:目前狀态下的矩形區域;往下是一大堆set方法,然後有一個setExpandedBounds方法

void setExpandedBounds(int left, int top, int right, int bottom) {
        if (!rectEquals(mExpandedBounds, left, top, right, bottom)) {
            mExpandedBounds.set(left, top, right, bottom);
            mBoundsChanged = true;
            onBoundsChanged();
        }
}
           

setCollapsedBounds方法

void setCollapsedBounds(int left, int top, int right, int bottom) {
        if (!rectEquals(mCollapsedBounds, left, top, right, bottom)) {
            mCollapsedBounds.set(left, top, right, bottom);
            mBoundsChanged = true;
            onBoundsChanged();
        }
    }
           

其實也沒有什麼,就是設定left,top, right, bottom,然後調用onBoundsChanged方法進行更新,接下來有setCollapsedTextAppearance方法,設定折疊時候文字的樣式

void setCollapsedTextAppearance(int resId) {
        TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
            mCollapsedTextColor = a.getColor(
                    R.styleable.TextAppearance_android_textColor, mCollapsedTextColor);
        }
        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
            mCollapsedTextSize = a.getDimensionPixelSize(
                    R.styleable.TextAppearance_android_textSize, (int) mCollapsedTextSize);
        }
        a.recycle();

        recalculate();
}
           

setExpandedTextAppearance:設定展開狀态時文字的樣式

void setExpandedTextAppearance(int resId) {
        TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
            mExpandedTextColor = a.getColor(
                    R.styleable.TextAppearance_android_textColor, mExpandedTextColor);
        }
        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
            mExpandedTextSize = a.getDimensionPixelSize(
                    R.styleable.TextAppearance_android_textSize, (int) mExpandedTextSize);
        }
        a.recycle();

        recalculate();
}
           

都是取出mView(傳進來的TextInputLayout)的屬性取出來進行設定,我們會發現各個方法裡都調用了recalculate()方法,也就是重新計算,我們檢視一下這個方法

public void recalculate() {
        if (mView.getHeight() > 0 && mView.getWidth() > 0) {
            // If we've already been laid out, calculate everything now otherwise we'll wait
            // until a layout
            calculateBaseOffsets();
            calculateCurrentOffsets();
        }
    }
           

判斷條件是當TextInputLayout的Height與Width都大于0的時候會調用calculateBaseOffsets()與calculateCurrentOffsets()方法,注釋的内容:如果TextInputLayout已經被laid out的時候,會去重新計算現在布局的一切,否則就等待。calculateBaseOffsets()方法,用于計算基本的偏移量,注意注釋的内容:在計算折疊狀态下的文字大小,也使用同樣的邏輯

final int collapsedAbsGravity = GravityCompat.getAbsoluteGravity(mCollapsedTextGravity,
                mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR);
        switch (collapsedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
            case Gravity.BOTTOM:
                mCollapsedDrawY = mCollapsedBounds.bottom;
                break;
            case Gravity.TOP:
                mCollapsedDrawY = mCollapsedBounds.top - mTextPaint.ascent();
                break;
            case Gravity.CENTER_VERTICAL:
            default:
                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
                float textOffset = (textHeight / 2) - mTextPaint.descent();
                mCollapsedDrawY = mCollapsedBounds.centerY() + textOffset;
                break;
        }
        switch (collapsedAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.CENTER_HORIZONTAL:
                mCollapsedDrawX = mCollapsedBounds.centerX() - (width / 2);
                break;
            case Gravity.RIGHT:
                mCollapsedDrawX = mCollapsedBounds.right - width;
                break;
            case Gravity.LEFT:
            default:
                mCollapsedDrawX = mCollapsedBounds.left;
                break;
        }
           

取出collapsedAbsGravity的值,然後和各種Gravity進行比較,然後确定mCollapsedDrawY 和mCollapsedDrawX的值

mTextPaint.setTextSize(mExpandedTextSize);
        width = mTextToDraw != null
                ? mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()) : 0;
        final int expandedAbsGravity = GravityCompat.getAbsoluteGravity(mExpandedTextGravity,
                mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR);
        switch (expandedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
            case Gravity.BOTTOM:
                mExpandedDrawY = mExpandedBounds.bottom;
                break;
            case Gravity.TOP:
                mExpandedDrawY = mExpandedBounds.top - mTextPaint.ascent();
                break;
            case Gravity.CENTER_VERTICAL:
            default:
                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
                float textOffset = (textHeight / 2) - mTextPaint.descent();
                mExpandedDrawY = mExpandedBounds.centerY() + textOffset;
                break;
        }
        switch (expandedAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.CENTER_HORIZONTAL:
                mExpandedDrawX = mExpandedBounds.centerX() - (width / 2);
                break;
            case Gravity.RIGHT:
                mExpandedDrawX = mExpandedBounds.right - width;
                break;
            case Gravity.LEFT:
            default:
                mExpandedDrawX = mExpandedBounds.left;
                break;
        }
           

取出expandedAbsGravity的值然後和各種Gravity進行比較,然後确定mCollapsedDrawY 和mCollapsedDrawX的值,最後調用clearTexture()方法清空texture(紋理的意思)

clearTexture();
           

calculateCurrentOffsets方法,通過lerp方法擷取mCurrentDrawX與mCurrentDrawY的值,如果mCollapsedTextColor != mExpandedTextColor,給mTextPaint設定顔色,而這個顔色會通過blendColors方法将mCollapsedTextColor與mExpandedTextColor進行混合,然後采用ViewCompat.postInvalidateOnAnimation方法進行重新整理

private void calculateCurrentOffsets() {
        final float fraction = mExpandedFraction;

        interpolateBounds(fraction);
        mCurrentDrawX = lerp(mExpandedDrawX, mCollapsedDrawX, fraction,
                mPositionInterpolator);
        mCurrentDrawY = lerp(mExpandedDrawY, mCollapsedDrawY, fraction,
                mPositionInterpolator);

        setInterpolatedTextSize(lerp(mExpandedTextSize, mCollapsedTextSize,
                fraction, mTextSizeInterpolator));

        if (mCollapsedTextColor != mExpandedTextColor) {
            // If the collapsed and expanded text colors are different, blend them based on the
            // fraction
            mTextPaint.setColor(blendColors(mExpandedTextColor, mCollapsedTextColor, fraction));
        } else {
            mTextPaint.setColor(mCollapsedTextColor);
        }

        ViewCompat.postInvalidateOnAnimation(mView);
}
           

再看一下blendColors内部,也就是通過一個ratio對顔色進行計算,分别計算出ARGB

private static int blendColors(int color1, int color2, float ratio) {
        final float inverseRatio = 1f - ratio;
        float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio);
        float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio);
        float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio);
        float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio);
        return Color.argb((int) a, (int) r, (int) g, (int) b);
    }
           

再看一下draw方法

public void draw(Canvas canvas) {
        final int saveCount = canvas.save();

        if (mTextToDraw != null && mDrawTitle) {
            float x = mCurrentDrawX;
            float y = mCurrentDrawY;

            final boolean drawTexture = mUseTexture && mExpandedTitleTexture != null;

            final float ascent;
            final float descent;

            // Update the TextPaint to the current text size
            mTextPaint.setTextSize(mCurrentTextSize);

            if (drawTexture) {
                ascent = mTextureAscent * mScale;
                descent = mTextureDescent * mScale;
            } else {
                ascent = mTextPaint.ascent() * mScale;
                descent = mTextPaint.descent() * mScale;
            }

            if (DEBUG_DRAW) {
                // Just a debug tool, which drawn a Magneta rect in the text bounds
                canvas.drawRect(mCurrentBounds.left, y + ascent, mCurrentBounds.right, y + descent,
                        DEBUG_DRAW_PAINT);
            }

            if (drawTexture) {
                y += ascent;
            }

            if (mScale != 1f) {
                canvas.scale(mScale, mScale, x, y);
            }

            if (drawTexture) {
                // If we should use a texture, draw it instead of text
                canvas.drawBitmap(mExpandedTitleTexture, x, y, mTexturePaint);
            } else {
                canvas.drawText(mTextToDraw, 0, mTextToDraw.length(), x, y, mTextPaint);
            }
        }

        canvas.restoreToCount(saveCount);
}
           

給TextPaint設定目前文字的大小,并給X,Y指派

float x = mCurrentDrawX;
            float y = mCurrentDrawY;

            final boolean drawTexture = mUseTexture && mExpandedTitleTexture != null;

            final float ascent;
            final float descent;

            // Update the TextPaint to the current text size
            mTextPaint.setTextSize(mCurrentTextSize);
           

如果需要繪制紋理,則調用canvas的drawBitmap方法,否則canvas 的drawText方法,繪制文字

if (drawTexture) {
                // If we should use a texture, draw it instead of text
                canvas.drawBitmap(mExpandedTitleTexture, x, y, mTexturePaint);
            } else {
                canvas.drawText(mTextToDraw, 0, mTextToDraw.length(), x, y, mTextPaint);
            }
           

還有一個calculateIsRtl方法,從右向左計算,是專門給左撇子設計的

private boolean calculateIsRtl(CharSequence text) {
        final boolean defaultIsRtl = ViewCompat.getLayoutDirection(mView)
                == ViewCompat.LAYOUT_DIRECTION_RTL;
        return (defaultIsRtl
                ? TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL
                : TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR).isRtl(text, 0, text.length());
}
           

到此,源碼就基本分析完畢了。

繼續閱讀