目录
在上一次的分享中我使用构建者模式打造了一个通用的Dialog——《Builder设计模式构建通用型Dialog》,受此启发,我决定本篇再次通过构建者模式来实现一个通用型的标题栏,也就是我们的TitleBar。我们平时开发中很多时候都是自定义一个TitleBar或者ToolBar,然后封装在BaseActivity中来使用的,不知道你是不是也是这样的,最近通过学习,有了一丝的感悟,可以使用Builder模式来封装,这种方式其实更加灵活,当你需要使用的时候,直接调用Builder构建出一个TitleBar即可,当你有特殊的标题栏需求时,也可以及时的单独处理,不至于那么死板,并且这样做还有一个好处,看过我上一篇文章的哥们肯定都知道,我是把它封装在了一个Android Library中,这样做可以方便我们后期的项目架构成模块化和组件化的工程结构,跟业务层分离,当然你也可以直接单独将其封装成一个组件来使用都是OK的,那废话不多说了,一起来开始今天的内容吧!
一、基本框架搭建
先说明一下,当我们使用Builder模式构建完成TitleBar之后,我们就不用再在xml文件中去引入布局了,或者这样说我们是在代码中引入,实际上是通过代码将我们的标题栏插入到根布局的第一个位置了,防止后面有些小伙伴在xml文件中找不到然后有点懵。那下面就开始代码实战了,同样的我们第一步还是先来搭建基本的框架。
首先我们先来给我们的标题栏制定一个规范,所以先来定义一个接口,内部两个方法,一个用来绑定标题栏布局,一个用来绑定标题栏所需的参数:
package com.jarchie.lib_common.titlebar;
/**
* 作者: 乔布奇
* 日期: 2020-05-06 21:53
* 邮箱: jarchie520@gmail.com
* 描述: 头部标题栏规范
*/
public interface ITitleBar {
//绑定头部视图
public int bindLayoutId();
//绑定头部参数
public void applyView();
}
然后我们为了这个组件的通用性,考虑到产品可能存在的不确定性,因为整个App中可能80%的标题栏样式都是通用的,比如:左侧一个返回按钮,中间一个标题,右侧一个文本或者功能按钮,但是你保不准也有某些特殊的,为了后面对于特殊情况的处理尽可能的复用我们的这个组件,那我们必须考虑自身代码的可拓展性,所以在这里我们先来写一个最基本的TitleBar。
因为是构建者模式,所以首先要了解构建者模式,不了解的可以先去百度一下或者直接看一下我上一篇关于构建Dialog的文章,地址本篇开头已经给出了,里面有相关知识的介绍。其实对于设计模式它都是一些套路的东西(个人感觉写代码就是套路,主要还是思想和架构),比如构建者模式,我们肯定要有的BaseBar、Builder、Params等几个类。因为现在要写的是一个最基本的类,所以我将这个类定义成一个抽象类,将Params定义成泛型,后续子类继承我们这个基类,方便通用型的或者特殊场景下的标题栏单独做定制化实现。现在来看一下这个类的代码实现,因为有了上一篇的构建Dialog的经验,我想如果你看这一篇的内容应该很容易理解的:
/**
* 作者: 乔布奇
* 日期: 2020-05-06 21:54
* 邮箱: jarchie520@gmail.com
* 描述: 头部标题基类
*/
public abstract class AbsTitleBar<P extends AbsTitleBar.Builder.AbsTitleParams> implements ITitleBar {
private P mParams;
private View mTitleView;
public AbsTitleBar(P params) {
this.mParams = params;
createAndBindView();
}
public P getParams() {
return mParams;
}
public void setText(int viewId, String text) {
TextView textView = findViewById(viewId);
if (!TextUtils.isEmpty(text)) {
textView.setVisibility(View.VISIBLE);
textView.setText(text);
}
}
public void setIcon(int viewId,int resId){
ImageView imageView = findViewById(viewId);
imageView.setImageResource(resId);
}
public void setOnClickListener(int viewId, View.OnClickListener listener) {
findViewById(viewId).setOnClickListener(listener);
}
public <T extends View> T findViewById(int viewId) {
return (T) mTitleView.findViewById(viewId);
}
//绑定和创建View
private void createAndBindView() {
//处理无id情况,AppCompatActivity可以这样设置
if (mParams.mParent == null) {
//获取Activity的根布局
ViewGroup activityRoot = (ViewGroup) ((Activity) mParams.mContext).findViewById(android.R.id.content);
mParams.mParent = (ViewGroup) activityRoot.getChildAt(0);
}
if (mParams.mParent == null) return;
//创建View
mTitleView = LayoutInflater.from(mParams.mContext).inflate(bindLayoutId(), mParams.mParent, false);
//添加,添加到第0个位置
mParams.mParent.addView(mTitleView, 0);
applyView();
}
public abstract static class Builder {
public Builder(Context context, ViewGroup parent) {
}
public abstract AbsTitleBar build();
public static class AbsTitleParams {
public Context mContext;
public ViewGroup mParent;
public AbsTitleParams(Context context, ViewGroup parent) {
this.mContext = context;
this.mParent = parent;
}
}
}
}
这里需要注意的点我提一下:当我们将TitleBar添加到根布局的最上方显示的时候,你可以传入你的根布局的id,通过id我们找到对应的布局进行添加显示,当然你也可以不传,这里如果你不传,我们默认帮你获取一次Android系统中AppCompatActivity的id为content的根布局。其它的就是一些很简单的代码了,设置文本设置图片资源设置点击事件这些,相信你肯定能看的懂!
二、构建通用型标题栏
在实际项目中,这一部分就是属于和公司业务相关的内容了,所以理论上来说应该再次封装一个介于app module和base module之间的business module,然后将与业务相关的通用性的东西放在这一层,我这里偷个懒,都放在commonLib中了,但是你需要了解这个分层的思想。
2.1、布局编写
先来写一个通用型标题栏的布局,因为我们一会要用到,内容也很简单,左侧一个返回按钮,中间一个文本标题,右侧文本或者Icon资源,来看代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="45dp"
xmlns:tools="http://schemas.android.com/tools"
android:background="#e5e5e5">
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/back"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="#09cb97"
tools:text="标题"
android:layout_centerInParent="true"/>
<TextView
android:id="@+id/right_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:textSize="16sp"
android:textColor="#09cb97"
tools:text="工具栏"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"/>
<ImageView
android:id="@+id/right_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"/>
</RelativeLayout>
2.2、逻辑代码编写
这里我们新建一个通用型标题栏的类,让它继承自上面的AbsTitleBar,然后实现父类中的方法:
/**
* 作者: 乔布奇
* 日期: 2020-05-06 22:12
* 邮箱: jarchie520@gmail.com
* 描述: 通用型标题栏,默认标题栏样式
*/
public class DefaultTitleBar extends AbsTitleBar<DefaultTitleBar.Builder.DefaultTitleParams> {
public DefaultTitleBar(DefaultTitleBar.Builder.DefaultTitleParams params) {
super(params);
}
@Override
public int bindLayoutId() {
return R.layout.title_bar;
}
@Override
public void applyView() {
//绑定效果
setText(R.id.title, getParams().mTitle);
setText(R.id.right_text, getParams().mRightText);
setIcon(R.id.right_icon,getParams().mRightIcon);
setOnClickListener(R.id.back,getParams().mLeftClickListener);
setOnClickListener(R.id.right_text, getParams().mRightClickListener);
setOnClickListener(R.id.right_icon, getParams().mRightClickListener);
}
public static class Builder extends AbsTitleBar.Builder {
DefaultTitleParams P;
public Builder(Context context) {
super(context, null);
P = new DefaultTitleParams(context, null);
}
public Builder(Context context, ViewGroup parent) {
super(context, parent);
P = new DefaultTitleParams(context, parent);
}
@Override
public DefaultTitleBar build() {
DefaultTitleBar titleBar = new DefaultTitleBar(P);
return titleBar;
}
//设置标题
public DefaultTitleBar.Builder setTitle(String title) {
P.mTitle = title;
return this;
}
//设置右侧文本
public DefaultTitleBar.Builder setRightText(String rightText) {
P.mRightText = rightText;
return this;
}
//设置右侧图片
public DefaultTitleBar.Builder setRightIcon(int rightIcon) {
P.mRightIcon = rightIcon;
return this;
}
//左侧点击事件
public DefaultTitleBar.Builder setLeftClickListener(View.OnClickListener leftListener) {
P.mLeftClickListener = leftListener;
return this;
}
//右侧点击事件
public DefaultTitleBar.Builder setRightClickListener(View.OnClickListener rightListener) {
P.mRightClickListener = rightListener;
return this;
}
public static class DefaultTitleParams extends DefaultTitleBar.Builder.AbsTitleParams {
//放置所有效果
public String mTitle;
public String mRightText;
public int mRightIcon;
public View.OnClickListener mLeftClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
//关闭当前的Activity
((Activity) mContext).finish();
}
};
public View.OnClickListener mRightClickListener;
public DefaultTitleParams(Context context, ViewGroup parent) {
super(context, parent);
}
}
}
}
这里也说几点需要注意的点吧:
第一个:Builder的构造方法我们给了两个,一个单参构造,一个双参构造,单参构造就是AbsTitleBar中默认获取了AppCompatActivity根布局的那种情况,这种情况你无需给页面根布局指定id。
第二个:左侧Icon的点击事件一般情况下都是关闭页面,所以这里再DefaultTitleParams类中定义这个变量的时候默认给出了关闭页面的这种实现,如果你需要处理别的业务逻辑,则在调用方使用的时候重新new OnClickListener就行了。
三、业务层调用
在Activity页面的java代码中添加我们的TitleBar,因为我们使用了Builder模式进行了封装,所以这里的代码就相当简单了,一句话一条链路下来即可搞定:
new DefaultTitleBar.Builder(this)
.setTitle("玩安卓")
.setRightIcon(R.drawable.share)
.setRightClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"鸿洋牛逼",Toast.LENGTH_LONG).show();
}
})
.build();
看到这里,是不是感觉很简单呢,写起来是不是有点爽!
四、效果展示
来看下最后实现的效果,如下图所示:
OK,今天的内容不多,理清套路,及时上车!我的废话,也不再多!各位,咱们下次见!