UIWidget系列一:TitleBarView-一个自带沉浸状态栏效果的Android自定义标题栏控件

  • 本文为 AriesHoo 原创,转载必须提前征求本人同意,违权必究!
  • 【 原文链接】
  • 【 GitHub-AriesHoo】
  • 【 -AriesHoo】

废话不多说,先上效果图

状态栏白色文字
状态栏黑色文字
真机Demo效果

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

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

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

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

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

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

  • 支持Java代码链式调用方便快捷

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

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

  • 支持左右TextView 按下效果透明度变化

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

GitHub地址

特别申明:2.0.0以后不做版本维护升级,2.0.0以后版本维护升级迁移至更全UI库:UIWidget

Demo下载

UIWidget系列一:TitleBarView-一个自带沉浸状态栏效果的Android自定义标题栏控件_第1张图片
扫描二维码下载demo

Gradle集成

allprojects {
    repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}
dependencies {
     //compile 'com.github.AriesHoo.UIWidget:widget-core:3.2.2'
     compile 'com.github.AriesHoo.UIWidget:widget-collapsing:${LATEST_VERSION}'
}

Maven集成


     
        jitpack.io
        https://jitpack.io
     


    com.github.AriesHoo.UIWidget
    widget-core
    3.2.2

TitleBarView XML可设置属性-相应属性均可Java代码设置-3.0.0大重构过部分属性有细微变化

    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

        
        
        
        
        
        
    

XML示例



Java代码示例

  • 设置文本-支持CharSequence和String资源
titleBar.setLeftText("左边文字");
titleBar.setRightText("右边文字");
titleBar.setTitleMainText(R.string.app_name);
titleBar.setTitleSubText("副标题");
  • 设置左右TextView图片资源-支持drawable(对象及资源id)
titleBar.setLeftTextDrawable(R.drawable.ic_share);
titleBar.setLeftTextDrawable(drawable);
titleBar.setRightTextDrawable(drawable);
titleBar.setRightTextDrawable(R.drawable.ic_close);
  • 获取自定义TextView、View及LinearLayout
//获取左边TextView
TextView tvLeft = titleBar.getTextView(Gravity.LEFT);
//获取右边TextView
TextView tvRight = titleBar.getTextView(Gravity.RIGHT);
//获取主标题TextView
TextView tvTitleMain = titleBar.getTextView( Gravity.CENTER | Gravity.TOP);
//获取副标题TextView
TextView tvTitleSub = titleBar.getTextView( Gravity.CENTER | Gravity.BOTTOM);
//获取状态栏View
View vStatus = titleBar.getView(Gravity.TOP);
//获取下划线View
View vDivider = titleBar.getView(Gravity.BOTTOM);
//获取左边LinearLayout-tvLeft父容器
LinearLayout lLayoutLeft = titleBar.getLinearLayout(Gravity.LEFT);
//获取右边LinearLayout-tvRight父容器
LinearLayout lLayoutRight = titleBar.getLinearLayout(Gravity.RIGHT);
//获取中间LinearLayout-tvTitleMain及tvTitleSub父容器
LinearLayout lLayoutCenter = titleBar.getLinearLayout(Gravity.CENTER);
  • 设置点击事件
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();
            }
        }));
addCenterAction
  • 设置状态栏透明度
titleBar.setStatusAlpha(102);//5.0 半透明模式alpha-102

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

特殊场景处理

  • 底部输入框处理--IM常见用法参考TitleFragment

方案一:推荐方案

 KeyboardHelper.with(activity)
                    .setControlNavigationBarEnable(controlEnable)
                    .setEnable();

原理:通过监听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);
}
UIWidget系列一:TitleBarView-一个自带沉浸状态栏效果的Android自定义标题栏控件_第2张图片
底部输入框处理

TitleBarView实现主要流程

  • 自定义子View
    /**
     * 自定义View
     */
    private View mStatusView;//状态栏View-用于单独设置颜色
    private LinearLayout mLeftLayout;//左边容器
    private LinearLayout mCenterLayout;//中间容器
    private LinearLayout mRightLayout;//右边容器
    private TextView mLeftTv;//左边TextView
    private TextView mTitleMain;//主标题
    private TextView mTitleSub;//副标题
    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
模拟器Android7.0
UIWidget系列一:TitleBarView-一个自带沉浸状态栏效果的Android自定义标题栏控件_第3张图片
模拟器Android4.4
UIWidget系列一:TitleBarView-一个自带沉浸状态栏效果的Android自定义标题栏控件_第4张图片
模拟器Android4.1
UIWidget系列一:TitleBarView-一个自带沉浸状态栏效果的Android自定义标题栏控件_第5张图片

感谢

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

标题栏
  • bingoogolapple/BGATitleBar-Android

  • bacy/titlebar

  • sandalli/TitleBarLibs

沉浸效果
  • gyf-dev/ImmersionBar

  • Zackratos/UltimateBar

  • laobie/StatusBarUtil

状态栏文字颜色变换
  • 白底黑字!Android浅色状态栏黑色字体模式
底部输入框软键盘弹起解决
  • gyf-dev/ImmersionBar/KeyboardPatch

  • Android爬坑之旅:软键盘挡住输入框问题的终极解决方案

  • 另外两种android沉浸式状态栏实现思路

结语

第一次写博文,心情忐忑,如有不足万望包涵!
如有问题或建议欢迎到GitHub提交issues!
如果觉得控件还不错的请顺手一个star/fork也是极好的!

关于我

: AriesHoo
GitHub: AriesHoo
Email: [email protected]

你可能感兴趣的:(UIWidget系列一:TitleBarView-一个自带沉浸状态栏效果的Android自定义标题栏控件)