安卓开发中,经常会遇到项目的标题栏基本样式都是一致的,只有个别界面需要定制化,而TitltBar或许跟我们的业务需求不是很相符,所以很多时候我们都想要去封装一个含有通用标题栏并且可定制化的BaseActivity。本文希望对有这方面需求的童鞋提供一种参考。效果图类似于这样
首先很多开发者比较喜欢的一种做法是写一份通用的标题栏布局,然后在每次创建的Activity的布局文件中去选择include的方式加入布局,这种方式其实在我看来工作量其实还是挺大,为了使用的时候更方便,可以将写好的标题栏布局作为一个View添加到根布局中。
下面先贴出标题栏的布局:
<?xml version="1.0" encoding="utf-8"?>
<!-- 默认TitleBar布局文件 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@color/white">
<TextView
android:id="@+id/tv_titlebar_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:paddingLeft="90dp"
android:paddingRight="90dp"
android:singleLine="true"
android:text="@string/app_name"
android:textColor="@color/color_333333"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_titlebar_left"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:drawableLeft="@mipmap/icon_back"
android:gravity="center"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_centerVertical="true"
android:textColor="@color/white"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_titlebar_right"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="15dp"
android:text="@string/app_name"
android:textColor="@color/color_333333"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_titlebar_more"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/tv_titlebar_right"
android:gravity="center"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="@string/app_name"
android:textColor="@color/color_333333"
android:textSize="14sp" />
</RelativeLayout>
</LinearLayout>
这里讲一下为什么返回按键,中间标题,右边两个小标题都选用了TextView,因为我的想法是为了让标题栏按钮是图标的时候,可以更快捷的使用。毕竟TextView还可以在它的上下左右动态添加Drawable嘛。还有一点,为什么选用线性布局中嵌套了一个相对布局,那是因为我考虑到以后可能会存在需要在标题栏下面添加一些类似于间隔啊之类的布局,所以就给预留出来了,如果不需要的话,可以选择去掉外层的线性布局。标题栏的右侧我自己是添加了两个预留的按钮,可以方便右上角存在比如保存、分享之类的操作。
下面贴一下不含标题栏的基类BaseActivity的代码:
/**
* Activity基类
*/
public class BaseActivity extends FragmentActivity {
protected int mResultCode = RESULT_CANCELED;
private int mStatusHeight;// 状态栏高度
private int mTitleHeight;// 标题栏高度
private int mScreenWidth, mScreenHeight;// 屏幕宽度、高度
protected boolean mIsFirst = true;// true-界面第一次聚焦
private RequestUploadDialog mUploadDialog;// 加载框
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityStackUtils.getInstance().pushActivity(this);// activity入栈管理
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityStackUtils.getInstance().finishActivity(this);// activity出栈管理
}
/**
* 隐藏键盘
*/
public void hideKeyBoard() {
try {
View rootView = this.getWindow().getDecorView();
View focusView = rootView.findFocus();
if (focusView != null) {
int viewId = focusView.getId();
View view = findViewById(viewId);
if (view instanceof EditText) {
InputMethodManager manager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
manager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取状态栏高度
* @return 状态栏高度(px)
*/
public int statusHeight() {
if (mStatusHeight == 0) {
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
int x = Integer.parseInt(c.getField("status_bar_height").get(c.newInstance()).toString());
mStatusHeight = getResources().getDimensionPixelSize(x);
} catch (Exception e1) {
e1.printStackTrace();
}
}
return mStatusHeight;
}
/**
* 获取屏幕宽度
* @return 屏幕宽度(px)
*/
public int screenWidth() {
if (mScreenWidth == 0) {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
mScreenWidth = displayMetrics.widthPixels;
}
return mScreenWidth;
}
/**
* 获取屏幕高度
* @return 屏幕高度(px)
*/
public int screenHeight() {
if (mScreenHeight == 0) {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
mScreenHeight = displayMetrics.heightPixels;
}
return mScreenHeight;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return executeKeyDownBack(keyCode, event);
}
return super.onKeyDown(keyCode, event);
}
/**
* 监听返回键
* @param keyCode 键值
* @param event 事件
* @return true-消耗事件,false-不消耗事件
*/
protected boolean executeKeyDownBack(int keyCode, KeyEvent event) {
return super.onKeyDown(keyCode, event);
}
/**
* Home键监听回调方法
* @param context 上下文环境
* @param intent 意图
*/
public void onHomeWatcherReceiver(Context context, Intent intent) {
}
/**
* 显示加载框
* @param uploadResId 资源ID
*/
public void showUpload(int uploadResId) {
showUpload(getString(uploadResId));
}
/**
* 显示加载框
* @param uploadContent 文本
*/
public void showUpload(String uploadContent) {
if (mUploadDialog == null)
mUploadDialog = new RequestUploadDialog(this);
mUploadDialog.showText(uploadContent).show();
}
/**
* 隐藏加载框
*/
public void hideUpload() {
if (mUploadDialog == null)
return;
mUploadDialog.dismiss();
}
/**
* 显示提示语
* @param toastResId 资源ID
*/
public void showToast(int toastResId) {
Toast.makeText(this, toastResId, Toast.LENGTH_SHORT).show();
}
/**
* 显示提示语
* @param toastText 文本
*/
public void showToast(String toastText) {
Toast.makeText(this, toastText, Toast.LENGTH_SHORT).show();
}
/**
* 设置TextView的left图片(不添加文字)
* @param textView 控件
* @param resIds 资源ID
*/
public void drawableLeft(TextView textView, int resIds) {
textView.setVisibility(TextView.VISIBLE);
textView.setText("");
Drawable drawable = getResources().getDrawable(resIds);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
textView.setCompoundDrawables(drawable, null, null, null);
}
/**
* 设置TextView的right图片(不添加文字)
* @param textView 控件
* @param resIds 资源ID
*/
public void drawableRight(TextView textView, int resIds) {
textView.setVisibility(TextView.VISIBLE);
textView.setText("");
Drawable drawable = getResources().getDrawable(resIds);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
textView.setCompoundDrawables(null, null, drawable, null);
}
/**
* 设置TextView颜色
* @param textView 控件
* @param resId 资源ID
*/
public void textColor(TextView textView, int resId) {
textView.setTextColor(getResources().getColor(resId));
}
/**
* 着色状态栏(4.4以上系统有效)
*/
protected void SetStatusBarColor() {
StatusBarCompat.setStatusBarColor(this, ContextCompat.getColor(this, R.color.base_color));
}
/**
* 着色状态栏(4.4以上系统有效)
*/
protected void SetStatusBarColor(int color) {
StatusBarCompat.setStatusBarColor(this, color);
}
/**
* 沉浸状态栏(4.4以上系统有效),全屏模式
*/
protected void SetTranslanteBar() {
StatusBarCompat.translucentStatusBar(this);
}
@Override
public void startActivity(Intent intent) {
super.startActivity(intent);
overridePendingTransition(R.anim.anim_right2enter, R.anim.anim_left2exit);
}
/**
* 通过 Class 跳转界面,不含参数
**/
public void startActivity(Class<?> cls) {
startActivity(cls, null);
}
/**
* 含有 Bundle 通过 Class 跳转界面
* 带有跳转动画
**/
public void startActivity(Class<?> cls, Bundle bundle) {
Intent intent = new Intent();
intent.setClass(this, cls);
if (bundle != null) {
intent.putExtras(bundle);
}
startActivity(intent);
overridePendingTransition(R.anim.anim_right2enter, R.anim.anim_left2exit);
}
/**
* 通过 Class 跳转界面
**/
public void startActivityForResult(Class<?> cls, int requestCode) {
startActivityForResult(cls, null, requestCode);
overridePendingTransition(R.anim.anim_right2enter, R.anim.anim_left2exit);
}
/**
* 含有Bundle通过Class跳转界面
**/
public void startActivityForResult(Class<?> cls, Bundle bundle,
int requestCode) {
Intent intent = new Intent();
intent.setClass(this, cls);
if (bundle != null) {
intent.putExtras(bundle);
}
startActivityForResult(intent, requestCode);
overridePendingTransition(R.anim.anim_right2enter, R.anim.anim_left2exit);
}
/**
* 跳转到登录界面
*/
public void jumpToLoginActivity(int requestCode) {
Intent intent = new Intent(IntentAction.ACTION_LOGIN);
startActivityForResult(intent, requestCode);
overridePendingTransition(R.anim.anim_bottom_in, R.anim.anim_bottom_window);
}
@Override
public void finish() {
super.finish();
}
/**
* finish 当前界面,手动控制页面关闭动画
*
* @param closeAnim true:关闭时有动画,false:关闭是无动画
*/
public void finish(boolean closeAnim) {
super.finish();
if (closeAnim) {
overridePendingTransition(R.anim.anim_left2enter, R.anim.anim_right2exit);
}
}
}
里面封装了一些其他比较常用的方法,有几个细节的点可以描述一下
1.里面封装了通用的弹toast和弹加载框的方法,当然加载框大家也可以封装到网络请求模块和本地数据库操作中去,这个看个人的喜好
2.基类中包含有软键盘的关闭操作,因为安卓的软键盘一直是一个比较头疼的问题,所以我选择直接封装到基类中,如果需要用到某些操作需要关闭软键盘的可以直接调用
3.封装的有对某个TextView添加Drawable的方法,这样标题栏需要加载图标的时候,方便调用
4.封装的有修改状态栏颜色和主题的方法,但是StatusBarCompat这个工具类我没有贴出来,不知道其中操作细节的可以去网上自行搜索查看
下面就是对含有标题栏的Activity的封装了,思路文章开头已经提到过,就直接上代码吧:
/**
* Activity基类:含TitleBar
*/
public class BaseTitleBarActivity extends BaseViewStubActivity {
protected TextView mTvCenter, mTvLeft, mTvRight, mTvMore;
protected View mViewTitleBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
}
@Override
protected void addTitleBarView() {
mViewTitleBar = View.inflate(this, addTitleBarLayout(), null);
mTvCenter = mViewTitleBar.findViewById(R.id.tv_titlebar_name);
mTvLeft = mViewTitleBar.findViewById(R.id.tv_titlebar_left);
mTvRight = mViewTitleBar.findViewById(R.id.tv_titlebar_right);
mTvMore = mViewTitleBar.findViewById(R.id.tv_titlebar_more);
mTvLeft.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onTitleBarClickLeft(v);
}
});
mTvRight.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onTitleBarClickRight(v);
}
});
mTvCenter.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onTitleBarClickCenter(v);
}
});
mTvMore.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onTitleBarClickMore(v);
}
});
onChangedTitleBar(mTvLeft, mTvCenter, mTvRight);
mLayoutContent.addView(mViewTitleBar);
}
/**
* 设置TitleBar布局
* @return TitleBar布局
*/
protected int addTitleBarLayout() {
return R.layout.layout_base_titlebar;
}
/**
* 修改TitleBar
* @param left 左边按钮
* @param center 中间文本
* @param right 右边按钮,默认隐藏
*/
protected void onChangedTitleBar(TextView left, TextView center, TextView right) {
right.setVisibility(View.GONE);
mTvMore.setVisibility(View.GONE);
}
/**
* TitleBar左边按钮点击事件
* @param view 控件
*/
protected void onTitleBarClickLeft(View view) {
setResult(mResultCode);
finish();
}
/**
* TitleBar右边按钮点击事件
* @param view 控件
*/
protected void onTitleBarClickRight(View view) {
}
/**
* TitleBar右边(更多)按钮点击事件
* @param view 控件
*/
protected void onTitleBarClickMore(View view) {
}
/**
* TitleBar中间按钮点击事件
* @param view 控件
*/
protected void onTitleBarClickCenter(View view) {
}
/**
* 获取焦点
* @param view 控件
*/
public void requestFocus(final View view) {
view.postDelayed(new Runnable() {
@Override
public void run() {
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.requestFocus();
}
}, 100);
}
}
BaseViewStubActivity的代码如下:
/**
* Activity基类:不含TitleBar
*/
public class BaseViewStubActivity extends BaseActivity {
protected LinearLayout mLayoutContent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void setContentView(int layoutResID) {
// 设置状态栏颜色
// SetTranslanteBar();
super.setContentView(R.layout.activity_base_titlebar);
mLayoutContent = findViewById(R.id.ll_content_view);
addTitleBarView(); // 添加TitleBar
addContentView(layoutResID);// 添加ContentView
}
/**
* 添加TitleBar
*/
protected void addTitleBarView() {
}
/**
* 添加ContentView
* @param layoutResID 布局
*/
protected void addContentView(int layoutResID) {
View.inflate(this, layoutResID, mLayoutContent);
}
}
这里相关的activity_base_titlebar布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- Activity基类默认加载的布局文件 -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_content_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</RelativeLayout>
这样一来,使用的时候只需要继承自BaseTitlebarActivity并实现onChangedTitleBar就可以随意对标题栏进行定制化了,对左上角的按钮点击方法是默认的对出操作,如果需要重写,也可以自己重新对该方法进行实现,标题栏每个按钮的点击操作都是单独出来的,需要点击操作的时候再进行重写。
效果图就不上了,谈一谈封装过程中遇到的一个最主要的问题吧
最初在封装BaseViewStubActivity的时候addContentView方法中我是使用的
mLayoutContent.addView(View.inflate(this, layoutResID, null));
这种方式进行添加,运行起来最初没有发现什么问题,但是一旦遇到相对布局的时候,需要在底部添加一个按钮的时候,发现最底部的控件的margin_bottom会失效,后来查阅了很多资料,原来是执行这句代码的时候,子布局其实并没有挂载到父布局中,所以造成了控件的布局宽高属性都为默认的LayoutParams.WRAP_CONTENT自适应,不清楚的可以去阅读以下addView的源码,所以解决这个的方法有两种:
第一种:修改添加子布局的方式为View.inflate(this, layoutResID, mLayoutContent);
第二种:挂载的方式修改为
mLayoutContent.addView(View.inflate(this, layoutResID, null), new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
也就是手动赋予子布局一个布局参数。
补充:
在继承自BaseTitlebarActivity的activity中同样也可以调用addTitleBarLayout方法来加载另外的标题栏布局,但是此时由于在上一级基类Activity中已经对标题栏进行了操作,所以请务必保持加载的新标题栏布局的每个控件id和通用标题栏的控件id一致,否则会造成空指针异常,当然如果只是对标题栏的颜色或者字体颜色那些修改的话,也提供两种思路,一种是定义另外一个主题颜色的标题栏布局,但是id与通用标题栏每个控件id一致;第二种是在上一级基类Activity中开放另外一个方法,调用时动态更新主题颜色等。
如果有问题欢迎指出,非常感谢!