美文网首页Android RoadAndroid开发收录Android知识
「代码」Android官方Toolbar自定义高度最靠谱的解决办

「代码」Android官方Toolbar自定义高度最靠谱的解决办

作者: Alimin利民 | 来源:发表于2016-08-30 16:19 被阅读7828次

    相信大家在开发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()方法。看效果:

    好了,所有问题都解决了,希望能对大家有所帮助。欢迎大家评论在评论区交流,当然打赏一下我也是不介意的。最后再提一下,转载要写明出处,请尊重作者的劳动成果,谢谢大家支持。

    相关文章

      网友评论

      • 松小白:您好,api4.4.4和5.1.1 的高度不一样,5.1.1无论怎么修改高度也是一样的。
      • BlainPeng:你MyToolbar类里面的构造方法名错了!
      • khunkk:看你的图,状态栏的 字和图标 设置成黑色, 怎么实现的?
        khunkk: @JOAH_际遇做向导 xml
        khunkk: @Ordosbxy height 啊
        Ordosbxy:请问这个自定义MyToolbar中哪一段代码是修改Toolbar高度的? 我想修改成40dp高度。谢谢
      • 会理发的店小二:大兄弟,toolbar的父类是祯布局,继承toobar你在实现个布局管理接口,就完美了
      • Avanline:源码没有共享吗0.0
      • 7f71607d3b9f:多谢楼主。
        多问一句,`mMenuView` 对应的menu布局,如果在 `setNavigationIcon` 方法之后再inflate的话,居中就会失效。而目前我还没有在Toolbar里面找到 inflate menu 回调,所以目前只能在 inflate menu所在的Fragment处单独处理,不知道楼主有没有什么解决办法?
      • Exception_Cui:楼主可以是这在ToolBar
        这在XML里面放一个TextView 然后设置TextView居中
        Alimin利民: @Exception_Cui 这个前文有提到,之前我也一直这样做,但是不太优雅,而且时间长了发现多维护一个view太麻烦,所以选择使用官方的加以修改。谢谢您的评论😃
      • 乘风破浪的程序员:楼主,toolbar 在左边怎么设置文字或图片啊
        Alimin利民:@hante 官方的Toolbar的返回按钮是不支持文字的,不过你可以用文字图片代替
        乘风破浪的程序员:@zippo1992 ??????????????????????????????????????????????????????????????????????????????????
        zippozeng: @hante

      本文标题:「代码」Android官方Toolbar自定义高度最靠谱的解决办

      本文链接:https://www.haomeiwen.com/subject/jjafettx.html