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的不一致。還要注意下呼叫那兩個方法的順序。
最後
有什麼錯漏的麻煩指出。感謝大家。