1. 程式人生 > >Android官方Toolbar自定義高度最靠譜的解決辦法

Android官方Toolbar自定義高度最靠譜的解決辦法

相信大家在開發Android App的時候都需要用到Toolbar這個空間來實現頁面導航。Android官方的Toolbar很強大,而且相信在效能和程式碼方面都是最好的,能夠更好的與Activity相協調,包括title的顯示、Menu的佈局...
但是這個強大的Toolbar也有讓人心痛的地方,相信各位看官也都跟我一樣碰到過這樣的問題,自定義Toolbar的高度,也就是改成一個比官方規定的actionBarSize小的高度之後,Toolbar最讓人蛋疼的問題問題出現了,包括NavButton、Title以及Menu在內的所有子控制元件都不能垂直居中了,而且Title也不能調成居中佈局。這就有點微妙了,貌似是Google為了讓開發者統一使用自己的UI規範而特意做的限制,不對開發者開放這些介面。


這怎麼辦呢?預設的高度對一些看官來說有點高,而且Title佈局中讓很多強迫症患者不能容忍。網上有相關的解決辦法,但我個人搜到的無一例外是給Toolbar新增子TextView解決Title不能居中的問題,顯然這樣做就沒辦法使用Toolbar.setTitle的方法了,而且還要額外持有一個TextView,不僅很不優雅,更重要的是像我這樣的強迫症患者完全不能忍啊!在這裡不得不吐槽一下Baidu等國內一些網站和一些人,Baidu搜到的一些資源連結基本上都是抄同一個作者的,而且一抹一樣,錯誤的地方改都不改直接抄,還有一些人轉載不寫出處,說了那麼多還是希望大家尊重一下作者分享資源的辛苦吧。
言歸正傳,對於這樣的問題,我在stackoverflow也搜尋了一番,都是跟前面說的那樣,不優雅,而且沒辦法解決居中問題(如果大家找到了更好的辦法歡迎在評論區告訴我,一起交流交流,謝謝)。那麼久只能自己動手了。下面進入正題。
1、解決Title居中問題
首先檢視Toolbar原始碼發現Title是其實是由一個mTitleTextView負責顯示的,那麼我們可以順著這個點找,看能不能找到一個解決辦法。mTitleTextView有一個比較特殊的地方,也是解決問題的關鍵。

/**
* Set the title of this toolbar.
*@paramtitleTitle to set
*/
public voidsetTitle(CharSequence title) {
  if(!TextUtils.isEmpty(title)) {
    if(mTitleTextView==null) {
      finalContext context = getContext();
      mTitleTextView=newTextView(context);
      ...
    }
    if(!isChildOrHidden(mTitleTextView)) {
      addSystemView(mTitleTextView, true
); } } ... mTitleText= title; }

檢視上面的原始碼發現,mTitleTextView是在setTitle中初始化的,而且setTitle這個方法正是我們用來設定Title的方法,它是開放的,這就是前面說的特殊之處。那麼我們可以從這裡下手。
首先我們新建一個MyToolbar,並繼承android.support.v7.widget.Toolbar,如下:

public class MyToolbar extends android.support.v7.widget.Toolbar {
}

然後定義一個TextView mTitleTextView欄位,這裡特意讓其與父類的Title控制元件同名。由於要使用自己定義的mTitleTextView,所以跟mTitleTextView有關的程式碼都要複寫,因為程式碼跟官方一樣,支援稍加修改,所以不會很突兀。下面直接上程式碼

public class MyToolbar extends android.support.v7.widget.Toolbar {
  privateTextView mTitleTextView;
  privateCharSequence mTitleText;
  private int mTitleTextColor;
  private int mTitleTextAppearance;
  publicToolbar(Context context) {
    super(context);
    resolveAttribute(context, null,R.attr.toolbarStyle);
  }
publicToolbar(Context context,@NullableAttributeSet attrs) {
super(context,attrs);
resolveAttribute(context,attrs,R.attr.toolbarStyle);
}
publicToolbar(Context context,@NullableAttributeSet attrs, intdefStyleAttr) {
super(context,attrs,defStyleAttr);
resolveAttribute(context,attrs,defStyleAttr);
}
private voidresolveAttribute(Context context,@NullableAttributeSet attrs, intdefStyleAttr) {
// Need to use getContext() here so that we use the themed context
context = getContext();
finalTintTypedArray a = TintTypedArray.obtainStyledAttributes(context,attrs,
R.styleable.Toolbar,defStyleAttr,0);
final inttitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance,0);
if(titleTextAppearance !=0) {
setTitleTextAppearance(context,titleTextAppearance);
}
if(mTitleTextColor!=0) {
setTitleTextColor(mTitleTextColor);
}
a.recycle();
post(newRunnable() {
@Override
public voidrun() {
if(getLayoutParams()instanceofLayoutParams) {
Log.v(Toolbar.class,"is Toolbar.LayoutParams");
((LayoutParams) getLayoutParams()).gravity= Gravity.CENTER;
}
}
});
}
@Override
publicCharSequencegetTitle() {
returnmTitleText;
}
@Override
public voidsetTitle(CharSequence title) {
if(!TextUtils.isEmpty(title)) {
if(mTitleTextView==null) {
finalContext context = getContext();
mTitleTextView=newTextView(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) {
addCenterView(mTitleTextView);
}
}else if(mTitleTextView!=null&&mTitleTextView.getParent() ==this) {// 當title為空時,remove
removeView(mTitleTextView);
}
if(mTitleTextView!=null) {
mTitleTextView.setText(title);
}
mTitleText= title;
}
private voidaddCenterView(View v) {
finalViewGroup.LayoutParams vlp = v.getLayoutParams();
finalLayoutParams lp;
if(vlp ==null) {
lp = generateDefaultLayoutParams();
}else if(!checkLayoutParams(vlp)) {
lp = generateLayoutParams(vlp);
}else{
lp = (LayoutParams) vlp;
}
addView(v,lp);
}
@Override
publicLayoutParamsgenerateLayoutParams(AttributeSet attrs) {
LayoutParams lp =newLayoutParams(getContext(),attrs);
lp.gravity= Gravity.CENTER;
returnlp;
}
@Override
protectedLayoutParamsgenerateLayoutParams(ViewGroup.LayoutParams p) {
LayoutParams lp;
if(pinstanceofLayoutParams) {
lp =newLayoutParams((LayoutParams) p);
}else if(pinstanceofActionBar.LayoutParams) {
lp =newLayoutParams((ActionBar.LayoutParams) p);
}else if(pinstanceofMarginLayoutParams) {
lp =newLayoutParams((MarginLayoutParams) p);
}else{
lp =newLayoutParams(p);
}
lp.gravity= Gravity.CENTER;
returnlp;
}
@Override
protectedLayoutParamsgenerateDefaultLayoutParams() {
LayoutParams lp =newLayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
lp.gravity= Gravity.CENTER;
returnlp;
}
@Override
public voidsetTitleTextAppearance(Context context,@StyleResintresId) {
mTitleTextAppearance= resId;
if(mTitleTextView!=null) {
mTitleTextView.setTextAppearance(context,resId);
}
}
@Override
public voidsetTitleTextColor(@ColorIntintcolor) {
mTitleTextColor= color;
if(mTitleTextView!=null) {
mTitleTextView.setTextColor(color);
}
}
}

至此解決了Title居中的問題,看效果


2、解決修改高度後所有子控制元件垂直居中問題
這個問題就沒有上面的那麼簡單了,因為像mNavButtonView和mMenuView這些都不像上面那樣暴露在一些開放方法裡面。而且與之相關的方法多是私有或者受保護不能複寫的,這就蛋疼了。
到了這裡就不得不祭出java的法寶了,反射!通過反射拿到對應的View,然後修改LayoutParams.gravity= Gravity.CENTER即可。當然上面的mTitleTextView也要這麼處理,只是不需要用到反射而已。下面直接上程式碼,相信有了解過反射機制的都能看懂,不懂的可以在評論區提問,我會盡量回復大家的。

@Override
public void setNavigationIcon(@NullableDrawable icon) {
super.setNavigationIcon(icon);
setGravityCenter();
}

public void setGravityCenter() {
post(newRunnable() {
@Override
public voidrun() {
setCenter("mNavButtonView");
setCenter("mMenuView");
}

});
}
private voidsetCenter(String fieldName) {
try{
Field field = getClass().getSuperclass().getDeclaredField(fieldName);//反射得到父類Field
field.setAccessible(true);
Object obj = field.get(this);//拿到對應的Object
if(obj ==null)return;
if(objinstanceofView) {
View view = (View) obj;
ViewGroup.LayoutParams lp = view.getLayoutParams();//拿到LayoutParams
if(lpinstanceofActionBar.LayoutParams) {
ActionBar.LayoutParams params = (ActionBar.LayoutParams) lp;
params.gravity= Gravity.CENTER;//設定居中
view.setLayoutParams(lp);
}
}
}catch(NoSuchFieldException e) {
e.printStackTrace();
}catch(IllegalAccessException e) {
e.printStackTrace();
}
}

到這裡就決解了上面所有問題,之所以在setNavigationIcon呼叫setGravityCenter方法是因為大多數用到Toolbar的都會設定NavigationIcon,當然也可以在設定Toolbar的時候主動呼叫setGravityCenter()方法。看效果:



好了,所有問題都解決了,希望能對大家有所幫助。歡迎大家評論在評論區交流,當然打賞一下我也是不介意的。最後再提一下,轉載要寫明出處,請尊重作者的勞動成果,謝謝大家支援。



文/mByte(簡書作者)
原文連結:http://www.jianshu.com/p/621225a55561
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。