BaseActivity自定义多样化标题布局

标题栏这种东西,每个App都会有,但是不是每个设计师都会为一个App设计Android和IOS两种风格,所以苦了Android开发的。不能用系统的,所以我们来自定义好了。(该篇文章主要提供布局文件在Acitivity使用时,标题和内容的解耦方案)
需求分析

  1. 标题栏出现的位置可选
  2. 标题栏与内容相互之间的位置可选
  3. 标题栏支持Toolbar调用
要优雅
  1. 基类实现方式,继承配置即可使用
  2. 不影响Super Activity的正常使用
  3. 良好的容错率
  4. 可配置标题栏位置及使用方式
  5. 支持自定义rootView及TitleStyle
OK,我们来写代码 导入依赖库
// 这里注意,如果只有一个app的module,可以使用implementation implementation 'com.android.support:appcompat-v7:26.1.0' // 如果BaseActivity的代码作为lib库,使用api api 'com.android.support:appcompat-v7:26.1.0'

创建TitleActivity类
/** * 自定义标题的BaseActivity * 使用时需配置style Theme.AppCompat.NoActionBar * setTitleView 设置标题 * setContentView 设置内容 * 复写titleStyle 设置标题风格 */ public class TitleActivity extends AppCompatActivity { // 获取自身实力的引用,防止代码里出现类似BaseActivity.this这种调用方式(个人习惯,查找类在哪里被引用的时候不乱) protected TitleActivity thisActivity; private LayoutInflater inflater; }

预定义TitleStyle
protected static final int TITLE_NONE = 0; // 无标题 protected static final int TITLE_ABOVE_CONTENT = 1; // 内容在标题下方 protected static final int TITLE_BELOW_CONTENT = 2; // 标题在底部,内容在上方 protected static final int TITLE_FLOAT_TOP = 3; // 内容铺满全局,标题悬浮在内容上方 protected static final int TITLE_FLOAT_BOTTOM = 3; // 内容铺满全局,标题悬浮在内容下方 protected static final int TITLE_CONTENT_SCROLL = 4; // 标题随内容滚动 /** * 标题栏风格 */ protected int titleStyle() { return TITLE_ABOVE_CONTENT; }

添加标题的设置方法
protected void setTitleView(int layoutResID) { if (titleStyle() == TITLE_NONE) { titleView = inflater.inflate(layoutResID, null); } else { titleView = inflater.inflate(layoutResID, rootView, false); notifyTitle(); } }private void notifyTitle() { switch (titleStyle()) { case TITLE_ABOVE_CONTENT: default: rootView.removeAllViews(); rootView.addView(titleView); if (contentView != null) { rootView.addView(contentView); } } }

复写setContentView
@Override public void setContentView(int layoutResID) { if (titleStyle() == TITLE_NONE) { super.setContentView(layoutResID); } else { contentView = inflater.inflate(layoutResID, rootView, false); notifyContent(contentView.getLayoutParams()); } }@Override public void setContentView(View view) { if (titleStyle() == TITLE_NONE) { super.setContentView(view); } else { contentView = view; notifyContent(view.getLayoutParams()); } }@Override public void setContentView(View view, ViewGroup.LayoutParams params) { if (titleStyle() == TITLE_NONE) { super.setContentView(view, params); } else { contentView = view; notifyContent(params); } }private void notifyContent(ViewGroup.LayoutParams params) { switch (titleStyle()) { case TITLE_ABOVE_CONTENT: default: // content置空 int childCount = rootView.getChildCount(); if (childCount > 2) { rootView.removeViewAt(childCount - 1); } if (contentView != null) {// 添加新的content rootView.addView(contentView, params); } } }

在onCreate中初始化rootView
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); thisActivity = this; inflater = LayoutInflater.from(thisActivity); if (titleStyle() != TITLE_NONE) { buildRootViewByStyle(); } }// 初始化rootView private void buildRootViewByStyle() { switch (titleStyle()) { case TITLE_ABOVE_CONTENT: default: LinearLayout ll = new LinearLayout(thisActivity); ll.setOrientation(LinearLayout.VERTICAL); rootView = ll; } super.setContentView(rootView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); }

是否开启lib id

/** * @return true 自动检索相关ids并做响应操作 false 不做固定id检索 */ protected boolean libTitleIdsEnable() { return false; }

/* 在标题初始化后 */ // init toolbar if (libTitleIdsEnable()) { try { tv_title = findViewById(R.id.TitleActivity_id_title); setCustomTitle(); } catch (ClassCastException e) { throw new RuntimeException("title must instanceof TextView"); } btn_back = findViewById(R.id.TitleActivity_id_back); if (btn_back != null) { if (backListener != null) { btn_back.setOnClickListener(backListener); } else { btn_back.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onBackPressed(); } }); } } }

对外提供修改方法
protected Toolbar getToolbar() { return toolbar; }private CharSequence titleCharSequence; private int titleResId = -1; protected void setCustomTitle(CharSequence title) { this.titleCharSequence = title; this.titleResId = -1; setCustomTitle(); }protected void setCustomTitle(int title) { this.titleResId = title; this.titleCharSequence = null; setCustomTitle(); }private void setCustomTitle() { if (tv_title != null) { if (titleCharSequence != null) { tv_title.setText(titleCharSequence); } else if (titleResId != -1) { tv_title.setText(titleResId); } } }private View.OnClickListener backListener; protected void setOnBackClickListener(View.OnClickListener listener) { backListener = listener; if (btn_back != null) { btn_back.setOnClickListener(listener); } }

使用方法
android:id="@id/TitleActivity_id_title"

public class TestTitleActivity extends TitleActivity {@Override protected boolean libTitleIdsEnable() { return true; }@Override protected int titleStyle() { return TITLE_ABOVE_CONTENT; }@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitleView(R.layout.layout_title); setContentView(R.layout.activity_title_test); // TODO: do other setCustomTitle("HaHaHa"); // 设置title } }

让TitleStyle优雅起来 为TitleStyle添加抽象工厂模式
创建接口
public interface ITitleStyle {boolean hasTitle(); // 是否包括标题 boolean bindSystemActionBar(); // Toolbar是否绑定系统ActionBar ViewGroup onBuildRootViewByStyle(Context context); // 创建rootView void onTitleLayoutSet(ViewGroup rootView, View titleView, View contentView); // 放置标题布局 void onContentLayoutSet(ViewGroup rootView, View titleView, View contentView, ViewGroup.LayoutParams params); // 放置内容布局位置}

创建默认Style并实现接口
/** * 线性布局 * 内容在标题下方 * 这里的逻辑是从BaseAcitivity中抽离的 */ public class DefaultTitleStyle implements ITitleStyle {@Override public boolean hasTitle() { return true; }@Override public boolean bindSystemActionBar() { return true; }@Override public void onTitleLayoutSet(ViewGroup rootView, View titleView, View contentView) { rootView.removeAllViews(); rootView.addView(titleView); if (contentView != null) { rootView.addView(contentView); } }@Override public void onContentLayoutSet(ViewGroup rootView, View titleView, View contentView, ViewGroup.LayoutParams params) { // content置空 int childCount = rootView.getChildCount(); if (childCount > 2) { rootView.removeViewAt(childCount - 1); } if (contentView != null) {// 添加新的content rootView.addView(contentView, params); } }@Override public ViewGroup onBuildRootViewByStyle(Context context) { LinearLayout ll = new LinearLayout(context); ll.setOrientation(LinearLayout.VERTICAL); return ll; }}

于是,BaseAcitivity中的实现变为
/** * 自定义标题的BaseActivity * 使用时需配置style Theme.AppCompat.NoActionBar * setTitleView 设置标题 * setContentView 设置内容 * 复写titleStyle 设置标题风格 */ public class TitleActivity extends AppCompatActivity { …… private ViewGroup rootView; private View titleView; private Toolbar toolbar; private View contentView; ……@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); …… titleStyle = titleStyle(); if (titleStyle.hasTitle()) { rootView = titleStyle.onBuildRootViewByStyle(thisActivity); super.setContentView(rootView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } }protected void setTitleView(int layoutResID) { if (!titleStyle.hasTitle()) { titleView = inflater.inflate(layoutResID, null); } else { titleView = inflater.inflate(layoutResID, rootView, false); titleStyle.onTitleLayoutSet(rootView, titleView, contentView); } // init toolbar if (titleView instanceof Toolbar) { toolbar = (Toolbar) titleView; if (titleStyle.bindSystemActionBar()) { setSupportActionBar(toolbar); } } else if (libTitleIdsEnable()) { toolbar = findViewById(R.id.TitleActivity_id_Toolbar); if (titleStyle.bindSystemActionBar()) { setSupportActionBar(toolbar); } } // init title and back button …… }@Override public void setContentView(int layoutResID) { if (!titleStyle.hasTitle()) { super.setContentView(layoutResID); } else { contentView = inflater.inflate(layoutResID, rootView, false); titleStyle.onContentLayoutSet(rootView, titleView, contentView, contentView.getLayoutParams()); } }……/** * 标题风格 */ protected ITitleStyle titleStyle() { return new DefaultTitleStyle(); }……}

与Activity解耦 那么,当使用时,如果遇到以存在BaseAcitivity,无法继承怎么办?哼,一点也不优雅
封装LayoutHandler
public class LayoutHandler {protected AppCompatActivity thisActivity; private LayoutInflater inflater; private ITitleStyle titleStyle; //protected static final int TITLE_BELOW_CONTENT = 2; // 标题在底部,内容在上方 //protected static final int TITLE_FLOAT_TOP = 3; // 内容铺满全局,标题悬浮在内容上方 //protected static final int TITLE_FLOAT_BOTTOM = 3; // 内容铺满全局,标题悬浮在内容下方 //protected static final int TITLE_CONTENT_SCROLL = 4; // 标题随内容滚动 private boolean libTitleIdsEnable; private ViewGroup rootView; private View titleView; private Toolbar toolbar; private View contentView; private TextView tv_title; private View.OnClickListener backListener; private CharSequence titleCharSequence; private int titleResId = -1; private View btn_back; private LayoutHandler() { }public boolean onCreate() { if (titleStyle.hasTitle()) { rootView = titleStyle.onBuildRootViewByStyle(thisActivity); return true; } return false; }public ViewGroup getRootView() { return rootView; }protected Toolbar getToolbar() { return toolbar; }public void setTitleView(int layoutResID) { if (!titleStyle.hasTitle()) { titleView = inflater.inflate(layoutResID, null); } else { titleView = inflater.inflate(layoutResID, rootView, false); titleStyle.onTitleLayoutSet(rootView, titleView, contentView); } // init toolbar if (titleView instanceof Toolbar) { toolbar = (Toolbar) titleView; if (titleStyle.bindSystemActionBar()) { thisActivity.setSupportActionBar(toolbar); } } else if (libTitleIdsEnable) { toolbar = thisActivity.findViewById(R.id.TitleActivity_id_Toolbar); if (titleStyle.bindSystemActionBar()) { thisActivity.setSupportActionBar(toolbar); } } // init title and back button if (libTitleIdsEnable) { try { tv_title = thisActivity.findViewById(R.id.TitleActivity_id_title); setCustomTitle(); } catch (ClassCastException e) { throw new RuntimeException("title must instanceof TextView"); } btn_back = thisActivity.findViewById(R.id.TitleActivity_id_back); if (btn_back != null) { if (backListener != null) { btn_back.setOnClickListener(backListener); } else { btn_back.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { thisActivity.onBackPressed(); } }); } } } }public boolean setContentView(View view, ViewGroup.LayoutParams params) { if (titleStyle.hasTitle()) { contentView = view; titleStyle.onContentLayoutSet(rootView, titleView, contentView, params); return true; } return false; }public boolean setContentView(View view) { if (titleStyle.hasTitle()) { contentView = view; titleStyle.onContentLayoutSet(rootView, titleView, contentView, contentView.getLayoutParams()); return true; } return false; }public boolean setContentView(int layoutResID) { if (titleStyle.hasTitle()) { contentView = inflater.inflate(layoutResID, rootView, false); titleStyle.onContentLayoutSet(rootView, titleView, contentView, contentView.getLayoutParams()); return true; } return false; }protected void setCustomTitle(CharSequence title) { this.titleCharSequence = title; this.titleResId = -1; setCustomTitle(); }protected void setCustomTitle(int title) { this.titleResId = title; this.titleCharSequence = null; setCustomTitle(); }private void setCustomTitle() { if (tv_title != null) { if (titleCharSequence != null) { tv_title.setText(titleCharSequence); } else if (titleResId != -1) { tv_title.setText(titleResId); } } }public void setOnBackClickListener(View.OnClickListener listener) { this.backListener = listener; if (btn_back != null) { btn_back.setOnClickListener(listener); } }}

为LayoutHandler添加建造者模式
public static class Builder {LayoutHandler handler; public Builder() { handler = new LayoutHandler(); }public Builder setActivity(AppCompatActivity act) { handler.thisActivity = act; handler.inflater = LayoutInflater.from(act); return this; }public Builder setTitleStyle(ITitleStyle titleStyle) { handler.titleStyle = titleStyle; return this; }public Builder setLibTitleIdsEnable(boolean libTitleIdsEnable) { handler.libTitleIdsEnable= libTitleIdsEnable; return this; }public LayoutHandler build() { return handler; }}

【BaseActivity自定义多样化标题布局】于是BaseActivity简化为
/** * 自定义标题的BaseActivity * 使用时需配置style Theme.AppCompat.NoActionBar * setTitleView 设置标题 * setContentView 设置内容 * 复写titleStyle 设置标题风格 */ public class TitleActivity extends AppCompatActivity { // 获取自身实力的引用,防止代码里出现类似BaseActivity.this这种调用方式(个人习惯,查找类在哪里被引用的时候不乱) protected TitleActivity thisActivity; private LayoutHandler layoutHandler; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); thisActivity = this; layoutHandler = new LayoutHandler.Builder() .setActivity(thisActivity) .setTitleStyle(titleStyle()) .setLibTitleIdsEnable(libTitleIdsEnable()) .build(); if (layoutHandler.onCreate()) { super.setContentView(layoutHandler.getRootView(), new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } }@Override public void setContentView(int layoutResID) { if (!layoutHandler.setContentView(layoutResID)) { super.setContentView(layoutResID); } }@Override public void setContentView(View view) { if (!layoutHandler.setContentView(view)) { super.setContentView(view); } }@Override public void setContentView(View view, ViewGroup.LayoutParams params) { if (!layoutHandler.setContentView(view, params)) { super.setContentView(view, params); } }/** * 标题风格 */ protected ITitleStyle titleStyle() { return new DefaultTitleStyle(); }/** * @return true 自动检索相关ids并做响应操作 false 不做固定id检索 */ protected boolean libTitleIdsEnable() { return false; } /** * 设置标题布局 */ protected void setTitleView(int layoutResID) { layoutHandler.setTitleView(layoutResID); }protected Toolbar getToolbar() { return layoutHandler.getToolbar(); }/** * 设置标题 */ protected void setCustomTitle(CharSequence title) { layoutHandler.setCustomTitle(title); } /** * 设置标题 */ protected void setCustomTitle(int title) { layoutHandler.setCustomTitle(title); }/** * id为TitleActivity_id_back的view被点击的回调 */ protected void setOnBackClickListener(View.OnClickListener listener) { layoutHandler.setOnBackClickListener(listener); }}

    推荐阅读