Osheep

时光不回头,当下最重要。

Android自带沉浸状态栏效果标题栏控件-TitleBarView


废话不多说,先上效果图

《Android自带沉浸状态栏效果标题栏控件-TitleBarView》

状态栏白色文字
《Android自带沉浸状态栏效果标题栏控件-TitleBarView》

状态栏黑色文字

相信大家在项目中一定使用过标题栏控件(不知道大家怎么称呼它,姑且那么叫吧),可能使用的最多的应该是Android自带的ToolBar控件,那么为什么我要去自己自定义这样一个控件?TitleBarView控件相比ToolBar有什么优势?且听我慢慢道来

TitleBarView是基于ViewGroup的扩展,主要具有以下特性

  • 支持Android 4.4以上版本沉浸式(关于这个叫法大家不要去纠结,意会即可)及半透明状态栏效果

  • 实现MIUI V6、Flyme 4.0、Android 6.0以上状态栏文字颜色切换(当然只能黑或白色)

  • 支持设置主/副标题跑马灯效果

  • 支持Java代码及XML设置众多自定义属性

  • 可设置左边文字/图片、中间主、副标题、右边文字/图片

  • 支持Java代码添加左边、中间、右边 View

说明:此处沉浸式状态栏为状态栏透明化且布局延伸至状态栏下效果,非状态栏着色模式

GitHub地址

Demo下载

《Android自带沉浸状态栏效果标题栏控件-TitleBarView》

扫描二维码下载demo

Gradle集成

allprojects {
    repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}
dependencies {
    // compile 'com.github.AriesHoo:TitleBarView:1.8.0'
     compile 'com.github.AriesHoo:TitleBarView:${LATEST_VERSION}'
}

TitleBarView XML可设置属性-相应属性均可Java代码设置

     <declare-styleable name="TitleBarView">
        <!--是否沉浸式状态栏,默认true-->
        <attr name="title_immersible" format="boolean"/>
        <!--内边距(距左右),默认12dp-->
        <attr name="title_outPadding" format="dimension"/>
        <!--添加子View 内边距,默认1dp-->
        <attr name="title_actionPadding" format="dimension"/>
        <!--标题文字是否左对齐,默认false即:标题居中-->
        <attr name="title_centerGravityLeft" format="boolean"/>
        <!--是否浅色状态栏(黑色文字及图标)-->
        <attr name="title_statusBarLightMode" format="boolean"/>

        <!--状态栏背景色,默认-1-->
        <attr name="title_statusColor" format="color"/>
        <!--状态栏背景资源,默认-1-->
        <attr name="title_statusResource" format="reference"/>
        <!--下划线背景色,默认Color.TRANSPARENT-->
        <attr name="title_dividerColor" format="color"/>
        <!--下划线背景资源,默认-1-->
        <attr name="title_dividerResource" format="reference"/>
        <!--下划线高度,默认0.5dp-->
        <attr name="title_dividerHeight" format="dimension"/>
        <!--下划线是否可见,默认true-->
        <attr name="title_dividerVisible" format="boolean"/>

        <!--左边文字,支持CharSequence及String资源-->
        <attr name="title_leftText" format="string"/>
        <!--左边文字大小,默认14dp-->
        <attr name="title_leftTextSize" format="dimension"/>
        <!--左边文字颜色,默认Color.WHITE-->
        <attr name="title_leftTextColor" format="color"/>
        <!--左边文字背景颜色,默认Color.TRANSPARENT-->
        <attr name="title_leftTextBackgroundColor" format="color"/>
        <!--左边文字背景资源,默认-1-->
        <attr name="title_leftTextBackgroundResource" format="reference"/>
        <!--左边文字drawable资源-->
        <attr name="title_leftTextDrawable" format="reference"/>
        <!--左边文字与drawable资源边距,默认1dp-->
        <attr name="title_leftTextDrawablePadding" format="dimension"/>

        <!--主标题文字,支持CharSequence及String资源-->
        <attr name="title_titleMainText" format="string"/>
        <!--主标题文字大小,默认18dp-->
        <attr name="title_titleMainTextSize" format="dimension"/>
        <!--主标题文字颜色,默认Color.WHITE-->
        <attr name="title_titleMainTextColor" format="color"/>
        <!--主标题文字背景颜色,默认Color.TRANSPARENT-->
        <attr name="title_titleMainTextBackgroundColor" format="color"/>
        <!--主标题文字背景资源,默认-1-->
        <attr name="title_titleMainTextBackgroundResource" format="reference"/>
        <!--主标题文字是否粗体,默认false-->
        <attr name="title_titleMainTextFakeBold" format="boolean"/>
        <!--主标题文字是否跑马灯,默认false-->
        <attr name="title_titleMainTextMarquee" format="boolean"/>

        <!--副标题文字,支持CharSequence及String资源-->
        <attr name="title_titleSubText" format="string"/>
        <!--副标题文字大小,默认12dp-->
        <attr name="title_titleSubTextSize" format="dimension"/>
        <!--副标题文字颜色,默认Color.WHITE-->
        <attr name="title_titleSubTextColor" format="color"/>
        <!--副标题文字背景颜色,默认Color.TRANSPARENT-->
        <attr name="title_titleSubTextBackgroundColor" format="color"/>
        <!--副标题文字背景资源,默认-1-->
        <attr name="title_titleSubTextBackgroundResource" format="reference"/>
        <!--副标题文字是否粗体,默认false-->
        <attr name="title_titleSubTextFakeBold" format="boolean"/>
        <!--副标题文字是否跑马灯,默认false-->
        <attr name="title_titleSubTextMarquee" format="boolean"/>

        <!--右边边文字,支持CharSequence及String资源-->
        <attr name="title_rightText" format="string"/>
        <!--右边文字大小,默认14dp-->
        <attr name="title_rightTextSize" format="dimension"/>
        <!--右边文字颜色,默认Color.WHITE-->
        <attr name="title_rightTextColor" format="color"/>
        <!--右边文字背景颜色,默认Color.TRANSPARENT-->
        <attr name="title_rightTextBackgroundColor" format="color"/>
        <!--右边文字背景资源,默认-1-->
        <attr name="title_rightTextBackgroundResource" format="reference"/>
        <!--右边文字drawable资源-->
        <attr name="title_rightTextDrawable" format="reference"/>
        <!--右边文字与drawable资源边距,默认1dp-->
        <attr name="title_rightTextDrawablePadding" format="dimension"/>

        <!--添加TextView文字大小,默认14dp-->
        <attr name="title_actionTextSize" format="dimension"/>
        <!--添加TextView文字颜色,默认Color.WHITE-->
        <attr name="title_actionTextColor" format="color"/>
        <!--添加TextView文字背景颜色,默认Color.TRANSPARENT-->
        <attr name="title_actionTextBackgroundColor" format="color"/>
        <!--添加TextView文字背景资源,默认-1-->
        <attr name="title_actionTextBackgroundResource" format="reference"/>
    </declare-styleable>

XML示例

<?xml version="1.0" encoding="utf-8"?>
<com.aries.ui.view.title.TitleBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/titleBar"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    app:title_statusBarLightMode="true"
    android:background="@color/colorTitle"
    app:title_actionTextColor="@color/colorTextBlack"
    app:title_dividerColor="@color/colorLineGray"
    app:title_leftTextColor="@color/colorTextBlack"
    app:title_leftTextDrawable="@drawable/ic_arrow_left"
    app:title_rightTextColor="@color/colorTextBlack"
    app:title_titleMainTextColor="@color/colorTextBlack"
    app:title_titleSubTextColor="@color/colorTextBlack"/>

Java代码示例

  • 设置文本-支持CharSequence和String资源
titleBar.setLeftText("左边文字");
titleBar.setRightText("右边文字");
titleBar.setTitleMainText("主标题");
titleBar.setTitleSubText("副标题");
  • 设置左右TextView图片资源-只支持drawable
titleBar.setLeftTextDrawable(R.drawable.ic_share);
titleBar.setRightTextDrawable(R.drawable.ic_close);
  • 设置点击事件
titleBar.setOnLeftTextClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
titleBar.setOnCenterClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
 titleBar.setOnRightTextClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
  • addAction–可支持给左、中、右容器新增TextView、ImageView、View
titleBar.addLeftAction(titleBar.new ImageAction(R.drawable.ic_close, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showDialog();
            }
        }));
 View view = View.inflate(mContext, R.layout.layout_news_sliding, null);
            mSlidingTab = (SlidingTabLayout) view.findViewById(R.id.tabLayout_slidingNews);
 titleBar.addCenterAction(titleBar.new ViewAction(view));
 titleBar.addRightAction(titleBar.new ImageAction(R.drawable.fast_ic_close, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showDialog();
            }
        }));
《Android自带沉浸状态栏效果标题栏控件-TitleBarView》

addCenterAction
  • 设置状态栏透明度
titleBar.setStatusAlpha(102);//5.0 半透明模式alpha-102

说明:alpha可设置0-255:0-全透明;255-类似非沉浸效果;5.0自带半透明效果设置102即可

特殊场景处理

方案一:推荐方案

titleBar.setBottomEditTextControl();

原理:通过监听Activity 中DecorView的 ViewTreeObserver添加addOnGlobalLayoutListener(当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,所要调用的回调函数的接口类)设置DecorView的paddingBottom为屏幕高度-软键盘高度.

方案二:

//底部有输入框时使用--最后一个参数false
titleBar.setImmersible(getActivity(), true, true, false);
//设置根布局setFitsSystemWindows(true)
mContentView.setFitsSystemWindows(true);
//根布局背景色保持和titleBar背景一致
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    mContentView.setBackground(titleBar.getBackground());
} else {
    mContentView.setBackgroundResource(android.R.color.holo_purple);
}
《Android自带沉浸状态栏效果标题栏控件-TitleBarView》

底部输入框处理

TitleBarView实现主要流程

  • 自定义子View
    /**
     * 自定义View
     */
    private View mStatusView;//状态栏View-用于单独设置颜色
    private LinearLayout mLeftLayout;//左边容器
    private LinearLayout mCenterLayout;//中间容器
    private LinearLayout mRightLayout;//右边容器
    private TextView mLeftTv;//左边TextView
    private TextView mTitleTv;//主标题
    private TextView mSubTitleText;//副标题
    private TextView mRightTv;//右边TextView
    private View mDividerView;//下划线

    /**
     * 初始化子View
     * @param context
     */
    private void initView(Context context) {
        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        LayoutParams dividerParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, mDividerHeight);

        mLeftLayout = new LinearLayout(context);
        mCenterLayout = new LinearLayout(context);
        mRightLayout = new LinearLayout(context);
        mStatusView = new View(context);
        mDividerView = new View(context);

        mLeftLayout.setGravity(Gravity.CENTER_VERTICAL);
        mCenterLayout.setOrientation(LinearLayout.VERTICAL);
        mRightLayout.setGravity(Gravity.CENTER_VERTICAL);

        mLeftTv = new TextView(context);
        mLeftTv.setGravity(Gravity.CENTER);
        mLeftTv.setLines(1);

        mTitleTv = new TextView(context);
        mSubTitleText = new TextView(context);

        mRightTv = new TextView(context);
        mRightTv.setGravity(Gravity.CENTER);
        mRightTv.setLines(1);

        mLeftLayout.addView(mLeftTv, params);
        mRightLayout.addView(mRightTv, params);
        addView(mLeftLayout, params);//添加左边容器
        addView(mCenterLayout, params);//添加中间容器
        addView(mRightLayout, params);//添加右边容器
        addView(mDividerView, dividerParams);//添加下划线View
        addView(mStatusView);//添加状态栏View
    }
  • 重新测量各View宽、高-保证标题始终居中和增加状态栏及下划线高度

TitleBarView控件原则是保证主要布局(即左边LinearLayout、中间LinearLayout、右边LinearLayout)高度的高度为xml配置的属性android:layout_height,状态栏mStatusView高度根据设置是否沉浸及获取当前系统状态栏高动态增加;下划线高度也是根据xml自定义属性title_dividerHeight动态增加;默认状态标题栏部分是永远居中的故得根据左、中、右容器的实际占用重新设置中间容器的宽度。

1、当左边容器+中间容器+右边容器实际占用宽度不超过屏幕宽度时为保证标题居中中间容器需重新测量宽度为:屏幕宽度-2x(左右容器中占用宽度多的容器宽度)

2、当左边容器+中间容器+右边容器实际占用宽度等于或超过屏幕宽度时为保证标题居中中间容器需重新测量宽度为:屏幕宽度-左边容器宽度-右边容器的宽度

语言表述能力有限,直接看代码吧(手动滑稽…)

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChild(mLeftLayout, widthMeasureSpec, heightMeasureSpec);
        measureChild(mRightLayout, widthMeasureSpec, heightMeasureSpec);
        measureChild(mCenterLayout, widthMeasureSpec, heightMeasureSpec);
        measureChild(mDividerView, widthMeasureSpec, heightMeasureSpec);
        measureChild(mStatusView, widthMeasureSpec, heightMeasureSpec);
        int left = mLeftLayout.getMeasuredWidth();
        int right = mRightLayout.getMeasuredWidth();
        int center = mCenterLayout.getMeasuredWidth();
        //判断左、中、右实际占用宽度是否等于或者超过屏幕宽度
        boolean isMuchScreen = left + right + center >= mScreenWidth;
        if (!mCenterGravityLeft) {//不设置中间布局左对齐才进行中间布局
            if (isMuchScreen) {
                center = mScreenWidth - left - right;
            } else {
                if (left > right) {
                    center = mScreenWidth - 2 * left;
                } else {
                    center = mScreenWidth - 2 * right;
                }
            }
            mCenterLayout.measure(MeasureSpec.makeMeasureSpec(center, MeasureSpec.EXACTLY), heightMeasureSpec);
        }
        //重新测量宽高--增加状态栏及下划线的高度
        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec) + mStatusBarHeight + mDividerHeight);
    }
  • 重新布局View位置

因TitleBarView是继承自ViewGroup故得根据实际情况重新布局上中下三部分(顶部-状态栏View、中间-(左边LinearLayout、中间LinearLayout、右边LinearLayout)、底部下划线View)、以及中间部分的位置-避免各子View有相互遮挡的情况以达到我们最终相要的效果。

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left = mLeftLayout.getMeasuredWidth();
        int right = mRightLayout.getMeasuredWidth();
        int center = mCenterLayout.getMeasuredWidth();
        mLeftLayout.layout(0, mStatusBarHeight, left, mLeftLayout.getMeasuredHeight() + mStatusBarHeight);
        mRightLayout.layout(mScreenWidth - right, mStatusBarHeight, mScreenWidth, mRightLayout.getMeasuredHeight() + mStatusBarHeight);
        boolean isMuchScreen = left + right + center >= mScreenWidth;
        if (left > right) {
            mCenterLayout.layout(left, mStatusBarHeight, isMuchScreen ? mScreenWidth - right : mScreenWidth - left, getMeasuredHeight() - mDividerHeight);
        } else {
            mCenterLayout.layout(isMuchScreen ? left : right, mStatusBarHeight, mScreenWidth - right, getMeasuredHeight() - mDividerHeight);
        }
        mDividerView.layout(0, getMeasuredHeight() - mDividerView.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight());
        mStatusView.layout(0, 0, getMeasuredWidth(), mStatusBarHeight);
    }
  • 沉浸式的开启或关闭实现及半透明状态栏实现

首先申明下大家都知道的原则:

1、Android 4.4 才支持沉浸式状态栏设置

2、Android 5.0才支持半透明状态栏设置

那么问题就来了:Android 4.4以下怎么实现沉浸呢?

记得有一个哲人说过:

如果一个人的手机版本还是在4.4以下,那么他(她)就不是你的目标用户

额,请不要纠结这是哪个哲人说的

不扯淡了,说下TitleBarView实现沉浸及半透明方案
只要是Android 4.4版本:设置Activity 沉浸并设置mStatusView的高度为系统状态栏的高度,通过控制mStatusView的背景色来控制是否沉浸及半透明效果。

其实就是设置了一层’假’状态栏View用于延伸至系统状态栏下边,用于控制背景色及透明度。

这样就可以设置Android 4.4以上的效果统一了(半透明效果亦然)

    /**
     * 设置沉浸式状态栏,4.4以上系统支持
     *
     * @param activity
     * @param immersible
     * @param isTransStatusBar 是否透明状态栏
     * @param isPlusStatusBar  是否增加状态栏高度--用于控制底部有输入框 (设置false/xml背景色必须保持和状态栏一致)
     */
    public void setImmersible(Activity activity, boolean immersible, boolean isTransStatusBar, boolean isPlusStatusBar) {
        this.mImmersible = immersible;
        if (isPlusStatusBar && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            mStatusBarHeight = getStatusBarHeight();
        } else {
            mStatusBarHeight = 0;
        }
        if (activity == null) {
            return;
        }
        //透明状态栏
        Window window = activity.getWindow();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            mStatusView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, mStatusBarHeight));
            // 透明状态栏
            window.addFlags(
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                systemUiVisibility = window.getDecorView().getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
                window.getDecorView().setSystemUiVisibility(systemUiVisibility);
                window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                window.setStatusBarColor(Color.TRANSPARENT);
            }
        }
        setStatusAlpha(immersible ? isTransStatusBar ? 0 : 102 : 255);
    }

说明:实际上TitleBarView一直处于沉浸状态,不沉浸只是设置了状态栏View透明度为255(即全黑色),显示的效果和非沉浸一致。

  • 状态栏文字及图标颜色变更–黑或白

    1、MIUI V6、Flyme 4.0、Android 6.0以上版本才支持
    2、MIUI 9开始回归原生适配方案即针对MIUI还得多做处理

代码有点多不粘贴了,详情请查看内置工具类StatusBarUtil

主要流程讲解完毕,来几张动图压压惊!

ROM GIF
真机-MIUI9-Android7.1.1
《Android自带沉浸状态栏效果标题栏控件-TitleBarView》

模拟器Android7.0
《Android自带沉浸状态栏效果标题栏控件-TitleBarView》

模拟器Android4.4
《Android自带沉浸状态栏效果标题栏控件-TitleBarView》

模拟器Android4.1
《Android自带沉浸状态栏效果标题栏控件-TitleBarView》

感谢

实现这个自定义标题栏控件效果参考了GitHub与简书上一些开源项目思路并加以整理,在此深表感谢!!

标题栏
沉浸效果
状态栏文字颜色变换
底部输入框软键盘弹起解决

结语

第一次写博文,心情忐忑,如有不足万望包涵!
如有问题或建议欢迎到GitHub提交issues

点赞