自定义Toolbar,解决你所有的适配苦恼!

About Toolbar

Toolbar是一个官方ToolBar的扩展工具类,省去了对不同版本适配的复杂方案,它可以帮助你轻松实现NavigationBarStatusBar的样式管理,最最重要的是它的使用方式及其接近原生,大家快来试用吧!

起因

按照官方的方式去管理NavigationBarStatusBar显示对各个系统版本的兼容性是很麻烦的。尤其在一些应用中可能不同的页面对应了不同显示状况,比如A页面NavigationBar需要显示成蓝色StatusBar需要显示成深蓝,而到了B页面NavigationBarStatusBar却需要显示成白色。这时候你需要在不同的页面通过Code调过来调过去麻烦得很。那么我们为什么不做一个款仅需要在xml文件中设置几个属性就能完成各种样式适配的Toolbar呢?OK,那我们来定下目标吧!
1. 适配各API版本但不需要那么麻烦的去编写个API对应的styles文件。
2. 使用方式简单,接近原生。
3. 仅需要布局文件,不需要在个页面维护代码逻辑。
针对这写要求,下面我们来实现一款自定义Toolbar.

原理

Toolbar的原理很简单,既然个系统版本需要兼容Statusbar等才能做到效果一致,那我们就不要Statusbar好了!
首先,将StatusBar设置成透明,并且让页面布局可以延伸到StatusBar下。这可以通过全局style实现:

<style name="AppTheme" parent="Theme.MaterialComponents.NoActionBar">
        <item name="android:windowTranslucentStatus">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
</style>

现在我们的布局已经延伸到(0,0)的起点了,接下来怎么办呢?我们肯定不能在每个页面布局里写一个适配StatusBar的布局吧,那我们可以考虑把它放到自定义的view控件中。接下来我们可以自定义一个Toolbar的控件继承自androidx.appcompat.widget.Toolbar。那么接下来的问题就转化成了:
1. 保持Toolbar的原有特性和使用方法,因为我们view是集成来的,所以这点肯定是满足的;
2. 该自定义Toolbar如何适配StatusBar部分;
3. 系统Toolbar中各个内置控件的布局是通过私有方法计算之后显示出来的,我们如何调整到跟原来的显示一模一样。
下面我们来将这些问题一一解决掉(第一条略)。
Toolbar适配系统栏部分,我们可以考虑重写onDraw()方法,绘制一个可自定义颜色的矩形区域,并且保证该区域的高度等于系统栏高度就可以了。请看代码实现:

//在构造函数中使用
private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typedArray = 
                context.obtainStyledAttributes(attrs, R.styleable.Toolbar);
            color = typedArray.getColor(
                R.styleable.Toolbar_statusBarColor,
                getResources().getColor(R.color.colorPrimaryDark));
        }
        int id = context.getResources()
            .getIdentifier("status_bar_height","dimen","android");
        statusBarHeight = context.getResources()
            .getDimensionPixelOffset(id);
        paint = new Paint();
        paint.setColor(color);
}
@Override
protected void onDraw(Canvas canvas) {
    Rect rect = 
      new Rect(0, 0, getMeasuredWidth(), (int) statusBarHeight);
    canvas.drawRect(rect, paint);
    super.onDraw(canvas);
}

这个时候系统栏的背景就搞定了,运行下看下什么效果吧。
运行完毕后,你会发现虽然系统栏背景颜色变了,但是我们的Toolbar就这么高,整个title都移到上面去了,怎么办呢?重写onMeasure()方法,让我们的Toolbar的高度变成:原高度+系统栏高度。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //为了兼容6.0及以前版本多次measure、layout问题
    measuredHeight = 
      measuredHeight == 0? getMinimumHeight() : measuredHeight;
    setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 
      measuredHeight +statusBarHeight);
}

再次运行,不错高度变了,但是为什么Title、icon之类的控件都快顶到系统栏了!看来我们还需要修改下onLayout()方法了。为什么是onLayout方法呢?是因为我们这些操作对控件的大小不会产生影响,只会对这些控件在Toolbar上的布局位置产生影响,所以我们需要重写下这个方法来调整内部控件的位置。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    setPadding(getPaddingLeft(), 
               statusBarHeight, 
               getPaddingRight(), 
               getPaddingBottom());
    super.onLayout(changed, l, t, r, b);
}

通过查看源码可以了解到,Toolbar是由ViewGroup实现的,其中各个控件的位置是通过私有方法计算得到的,而在这个方法中影响垂直位置计算的就是padding值,所以设置padding值将状态栏的那块高度空出来就OK了。
至此我们这个控件就搞定了,赶快去试用吧!源码链接

…………………………………………
有个bug,当使用windowIsTranslucent属性时键盘弹出适配会失效adjust,采用下面的代码可以fix掉bug。

public class KeyboardUtil {
    private View decorView;
    private View contentView;

    public KeyboardUtil(Activity act, View contentView) {
        this.decorView = act.getWindow().getDecorView();
        this.contentView = contentView;

        //only required on newer android versions. it was working on API level 19
        if (Build.VERSION.SDK_INT >= 19) {
            decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
        }
    }

    public void enable() {
        if (Build.VERSION.SDK_INT >= 19) {
            decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
        }
    }

    public void disable() {
        if (Build.VERSION.SDK_INT >= 19) {
            decorView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
        }
    }


    //a small helper to allow showing the editText focus
    ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            Rect r = new Rect();
            //r will be populated with the coordinates of your view that area still visible.
            decorView.getWindowVisibleDisplayFrame(r);

            //get screen height and calculate the difference with the useable area from the r
            int height = decorView.getContext().getResources().getDisplayMetrics().heightPixels;
            int diff = height - r.bottom;

            //if it could be a keyboard add the padding to the view
            if (diff != 0) {
                // if the use-able screen height differs from the total screen height we assume that it shows a keyboard now
                //check if the padding is 0 (if yes set the padding for the keyboard)
                if (contentView.getPaddingBottom() != diff) {
                    //set the padding of the contentView for the keyboard
                    contentView.setPadding(0, 0, 0, diff);
                }
            } else {
                //check if the padding is != 0 (if yes reset the padding)
                if (contentView.getPaddingBottom() != 0) {
                    //reset the padding of the contentView
                    contentView.setPadding(0, 0, 0, 0);
                }
            }
        }
    };


    /**
     * Helper to hide the keyboard
     *
     * @param act
     */
    public static void hideKeyboard(Activity act) {
        if (act != null && act.getCurrentFocus() != null) {
            InputMethodManager inputMethodManager = (InputMethodManager) act.getSystemService(Activity.INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(act.getCurrentFocus().getWindowToken(), 0);
        }
    }
}


new KeyboardUtil(this, contentView);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350