1. 程式人生 > >Android標題居中的Toolbar

Android標題居中的Toolbar

2017年特意寫第一篇部落格開個頭。之前一直不寫部落格的原因,一個是懶,另一個是因為覺得自己沒那個能力,會誤導別人。但是最近特別想聽聽別人的意見,所以就下定決心開始寫部落格。廢話不說,趕快開波。

前言

用過Toolbar的都知道標題是在左邊的,但通常UI都要求我們把標題居中。沒用過的童鞋可以看看泡網的一篇文章android:ToolBar詳解(手把手教程)。我並不想自己寫一個佈局來充當Toolbar,因為那樣會增加不少工作量。但怎樣才能把Toolbar的標題居中?
有問題先google。如果你還在百度的話,我也沒什麼辦法了。谷歌後找到這樣一種方法。在原來的Toolbar裡面加上一個TextView,標題居中的關鍵是第12行,將layout_gravity設定成center。

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary">

        <TextView
            android:id="@+id/toolbar_title"
            style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"/> </android.support.v7.widget.Toolbar>

接著再來個helper類。很簡單的一個類,看看init方法就可以過了。

public class ToolbarHelper {
    private TextView mTitleTextView;
    private
Toolbar mToolbar; public void init(AppCompatActivity activity, Toolbar toolbar, TextView titleTextView) { if (activity == null) { return; } mTitleTextView = titleTextView; mToolbar = toolbar; activity.setSupportActionBar(toolbar); // titleTextView不為null才設定actionbar不顯示Title if (titleTextView != null) { ActionBar actionBar = activity.getSupportActionBar(); if (actionBar != null) { // 不顯示title和subTitle actionBar.setDisplayShowTitleEnabled(false); } } } public void setTitle(@StringRes int resid) { if (mTitleTextView != null) { mTitleTextView.setText(resid); } } public void setTitle(CharSequence title) { if (mTitleTextView != null) { mTitleTextView.setText(title); } } public void setTitleTextColor(@ColorInt int color) { if (mTitleTextView != null) { mTitleTextView.setTextColor(color); } } public Toolbar getToolbar() { return mToolbar; } public TextView getTitleTextView() { return mTitleTextView; } }

然後在Activity中,最好是BaseActivity,建立一個ToolbarHelper物件。在初始化View的時候,呼叫toolbarHelper的init()方法。

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
TextView titleView = (TextView) findViewById(R.id.toolbar_title);
mToolbarHelper.init(this, toolbar, titleView);

接下來才是重點,重寫onTitleChanged方法

    @Override
    protected void onTitleChanged(CharSequence title, int color) {
        super.onTitleChanged(title, color);
        if (mToolbarHelper != null) {
            mToolbarHelper.setTitle(title);
        }
    }

這樣標題居中的Toolbar就搞定了。但有一點值得注意的是,不要呼叫toolbar自身的setTitle()方法。否則的話Toolbar自身的標題也會顯示。

重點來了,CenterTitleToolbar

這樣就滿足了嗎?不,上面這種方法太麻煩了。又要在xml裡面加東西,又要改activity。能不能只改動Toolbar裡面的程式碼,不需要動Toolbar以外的任何程式碼就能實現標題居中?
基於這個思路,我們要建立一個Toolbar的子類。使Toolbar的title居中有兩種做法。

  • 通過反射獲取其mTitleTextView屬性,並修改其LayoutParams的gravity屬性
  • 重寫toolbar的setTitle方法,自己維護一個titleTextView,並將其居中顯示。

這裡我選擇第二種。因為第一種使用了反射,且mTitleTextView是通過懶載入形式建立的,要考慮好反射獲取的時機。OK,想好了就開工了。
先來看看Toolbar原始碼裡的setTitle()方法

    public void setTitle(CharSequence title) {
        if (!TextUtils.isEmpty(title)) {
            if (mTitleTextView == null) {
                final Context context = getContext();
                mTitleTextView = new TextView(context);
                mTitleTextView.setSingleLine();
                mTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
                if (mTitleTextAppearance != 0) {
                    mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
                }
                if (mTitleTextColor != 0) {
                    mTitleTextView.setTextColor(mTitleTextColor);
                }
            }
            if (!isChildOrHidden(mTitleTextView)) {
                addSystemView(mTitleTextView, true);
            }
        } else if (mTitleTextView != null && isChildOrHidden(mTitleTextView)) {
            removeView(mTitleTextView);
            mHiddenViews.remove(mTitleTextView);
        }
        if (mTitleTextView != null) {
            mTitleTextView.setText(title);
        }
        mTitleText = title;
    }

看到第3-14行就知道我剛剛為什麼說mTitleTextView是通過懶載入來建立了吧。其他都看懂了,isChildOrHidden,addSystemView這兩個方法做了什麼?繼續看原始碼。

    // 判斷該view的父View是否為Toolbar
    private boolean isChildOrHidden(View child) {
        return child.getParent() == this || mHiddenViews.contains(child);
    }

    // 建立LayoutParams物件,並新增到Toolbar
    private void addSystemView(View v, boolean allowHide) {
        final ViewGroup.LayoutParams vlp = v.getLayoutParams();
        final LayoutParams lp;
        if (vlp == null) {
            lp = generateDefaultLayoutParams();
        } else if (!checkLayoutParams(vlp)) {
            lp = generateLayoutParams(vlp);
        } else {
            lp = (LayoutParams) vlp;
        }
        lp.mViewType = LayoutParams.SYSTEM;

        if (allowHide && mExpandedActionView != null) {
            v.setLayoutParams(lp);
            mHiddenViews.add(v);
        } else {
            addView(v, lp);
        }
    }

這兩個方法中都有mHiddenViews,這個對於我們的需求沒什麼用,忽略就好了。不必每句都看懂,我們知道它大概做了什麼即可。
OK,開始寫我們的CenterTitleToolbar。先繼承Toolbar,宣告和Title相關的屬性

    private TextView mTitleTextView;
    private CharSequence mTitleText;
    private int mTitleTextColor;
    private int mTitleTextAppearance;

重寫setTitle()方法。

    @Override
    public void setTitle(CharSequence title) {
        if (!TextUtils.isEmpty(title)) {
            if (mTitleTextView == null) { // 懶載入
                final Context context = getContext();
                mTitleTextView = new TextView(context);
                mTitleTextView.setSingleLine();
                mTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
                if (mTitleTextAppearance != 0) {
                    mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
                }
                if (mTitleTextColor != 0) {
                    mTitleTextView.setTextColor(mTitleTextColor);
                }
            }
            if (mTitleTextView.getParent() != this) {
                // 新增到Toolbar並居中顯示
                addCenterView(mTitleTextView);
            }
        } else if (mTitleTextView != null && mTitleTextView.getParent() == this) {
            // 當title為空時,remove
            removeView(mTitleTextView);
        }
        if (mTitleTextView != null) {
            mTitleTextView.setText(title);
        }
        mTitleText = title;
    }

依照原來的思路稍微的修改。程式碼和原來的雷同,改動的地方就只有16,18,20和22行。關鍵是addCenterView這個方法。標題居中就看這裡了。

    private void addCenterView(View v) {
        final ViewGroup.LayoutParams vlp = v.getLayoutParams();
        final LayoutParams lp;
        if (vlp == null) {
            lp = generateDefaultLayoutParams();
        } else if (!checkLayoutParams(vlp)) {
            lp = generateLayoutParams(vlp);
        } else {
            lp = (LayoutParams) vlp;
        }

        lp.gravity = Gravity.CENTER;
        addView(v, lp);
    }

該方法與Toolbar的addSystemView()方法雷同,我基本都是直接拷過來改要改的地方,去掉了一些無用的程式碼。看到第12行,沒錯,我們前面做了那麼多,就為了將gravity設定為center。

細心的童鞋會發現,setTitle()方法裡還有mTitleTextAppearance 和 mTitleTextColor需要獲取。沒錯,要想獲取這兩個值,我們需要重寫setTitleTextAppearance()和setTitleTextColor()方法,只不過是將原來Toolbar的程式碼拷過來,這裡就不說了。除此之外,還需要在構建方法中自己獲取一次。

        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
                R.styleable.Toolbar, defStyleAttr, 0);
        try {
            final int titleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
            if (titleTextAppearance != 0) {
                setTitleTextAppearance(context, titleTextAppearance);
            }
            if (a.hasValue(R.styleable.Toolbar_titleTextColor)) {
                setTitleTextColor(a.getColor(R.styleable.Toolbar_titleTextColor, 0xffffffff));
            }
        } finally {
            a.recycle();
        }

這裡解釋下為什麼已經重寫了setTitleTextAppearance()和setTitleTextColor()方法還需要在構造方法中獲取這兩個值。因為Toolbar的原始碼獲取TextAppearance並沒有呼叫setTitleTextAppearance()方法,導致我們自己的mTitleTextAppearance和Toolbar的不一致。還要注意下呼叫那兩個方法的順序。

最後

有什麼錯漏的麻煩指出。感謝大家。