- 设计思路
很多时候我们应用的activity的title风格基本一致,只有微小的差异。这个时候我们可以把相同的部分写好,不同的部分设置一个adapter动态的填充不同的内容及动作。 - 具体实现
效果图
BaseCustomTitleFragmentActivity的布局主要文件base_customtitle_fragment.xml
上面是一个自定义封装的标题部分,一面是填充fragment或者其他布局的容器。
<com.mobogenie.view.CustomTitleView
android:id="@+id/title_layout"
android:layout_width="match_parent"
android:layout_height="48dp" />
<LinearLayout
android:id="@+id/base_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/ads_buttom_banner_view"
android:layout_below="@+id/custom_layout_title"
android:orientation="vertical" />
自定义CustomTitleView的布局
LinearLayout 布局的左边是回退左箭头,右边是准备用adapter动态添加布局的预留容器。
<?xml version="1.0" encoding="utf-8"?>
<!-- 自定义控件com.mobogenie.view.CustomTitleView专用Layout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/customtitleview_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal" >
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
android:id="@+id/customtitleview_titletext"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/title_btn_selector"
android:drawableLeft="@drawable/back"
android:drawablePadding="14dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:singleLine="true"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="18dp" />
</FrameLayout>
<LinearLayout
android:id="@+id/customtitleview_iconbtn_box"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/title_btn_selector"
android:gravity="center"
android:orientation="horizontal" >
</LinearLayout>
</LinearLayout>
主要实现CustomTitleView部分,继承LinearLayout的组合只能定义View
/**
* 最常见的标题栏,左侧是带有标题文字和箭头的后退按钮,右侧可以添加功能按钮。
* 右侧功能按钮通过适配器添加。
*/
public class CustomTitleView extends LinearLayout {
/**
* 不添加间隔线。
*/
public static final int DIVIDER_NONE = -1;
/**
* 两边都添加间隔线。
*/
public static final int DIVIDER_BOTH = 0;
/**
* 仅左侧添加间隔线。
*/
public static final int DIVIDER_LEFT = 1;
/**
* 仅右侧添加间隔线。
*/
public static final int DIVIDER_RIGHT = 2;
/**
* 用于右侧图标按钮的数据修改。
*/
private class TitleDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
fillRightBtn(mAdapter);
};
@Override
public void onInvalidated() {
fillRightBtn(mAdapter);
}
}
/**
* @param context
*/
public CustomTitleView(Context context) {
super(context);
initView(context);
}
/**
* @param context
* @param attrs
*/
public CustomTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
initView(context);
}
/**
* @param context
* @param attrs
* @param defStyle
*/
@SuppressLint("NewApi")
public CustomTitleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initAttrs(context, attrs);
initView(context);
}
private CustomTitleAdapter mAdapter;
private String mTitle;
private TextView mTitleTV;
private DataSetObserver mDataSetObserver;
/** 右侧按钮容器布局。 */
protected LinearLayout mRightBtnBox;
protected int dividerEnabled;
protected int dividerLeftEnabled;
protected int dividerRightEnabled;
protected int dp_12;
/**
* 获取右侧图标按钮设置适配器的对象。
* @return 右侧图标按钮设置适配器。
*/
public CustomTitleAdapter getAdapter() {
return this.mAdapter;
}
/**
* 为右侧图标按钮设置适配器,用来控制图标的数量、View样式、点击事件等。具体设计方式
* @param adapter 实现CustomTitleAdapter。
*/
public void setAdapter(CustomTitleAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
destroyRightBtn();
}
this.mAdapter = adapter;
if (adapter != null) {
mDataSetObserver = new TitleDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
fillRightBtn(adapter);
} else {
destroyRightBtn();
}
}
/**
* 清空图标按钮。
*/
private void destroyRightBtn() {
if (mRightBtnBox == null) {
LogUtil.e("customtitleview_iconbtn_box is NULL !");
return;
}
mRightBtnBox.removeAllViews();
}
/**
* 设置后退按钮的点击事件。被点击的 View Id 为 {@link R.id#customtitleview_titletext}.
* @param listener 点击事件。
*/
public void setOnBackClickListener(OnClickListener listener) {
mTitleTV.setOnClickListener(listener);
}
/**
* 设置标题文字。
* @param title 标题文字。
*/
public void setTitleText(CharSequence title) {
if (title != null) {
this.mTitleTV.setText(title);
this.mTitle = this.mTitleTV.getText().toString();
}
}
/**
* 设置标题文字。
* @param title 标题文字。
*/
public void setTitleText(int title) {
this.mTitleTV.setText(title);
this.mTitle = this.mTitleTV.getText().toString();
}
/**
* 获取是否有间隔线。
* @return 是否有间隔线。
*/
public int isDividerEnabled() {
return dividerEnabled;
}
/**
* 设置是否有间隔线,默认为 {@link CustomTitleView#DIVIDER_NONE}。
* 设置为 true 会在两个按钮之间添加间隔线,设置为 {@link CustomTitleView#DIVIDER_NONE} 则不添加间隔线。
* 最左和最右两侧的间隔线由 {@link #setDividerLeftEnabled(boolean)} 和 {@link #setDividerRightEnabled(boolean)} 设置。
* @param dividerEnabled 是否有间隔线。
*/
public void setDividerEnabled(int dividerEnabled) {
this.dividerEnabled = dividerEnabled;
}
/**
* 获取最左侧是否有间隔线。
* @return 最左侧是否有间隔线。
*/
public int isDividerLeftEnabled() {
return dividerLeftEnabled;
}
/**
* 设置最左侧是否有间隔线,默认为 {@link CustomTitleView#DIVIDER_NONE}。
* @param dividerLeftEnabled 最左侧是否有间隔线。
*/
public void setDividerLeftEnabled(int dividerLeftEnabled) {
this.dividerLeftEnabled = dividerLeftEnabled;
}
/**
* 获取最右侧是否有间隔线。
* @return 最右侧是否有间隔线。
*/
public int isDividerRightEnabled() {
return dividerRightEnabled;
}
/**
* 设置最右侧是否有间隔线,默认为 {@link CustomTitleView#DIVIDER_NONE}。
* @param dividerRightEnabled 最右侧是否有间隔线。
*/
public void setDividerRightEnabled(int dividerRightEnabled) {
this.dividerRightEnabled = dividerRightEnabled;
}
/**
* 获取标题文字。
* @return 标题文字。
*/
public String getTitleText() {
return mTitle;
}
/**
* 获取后退图标和标题文字的TextView。
* @return
*/
public TextView getTitleBackTextView() {
return mTitleTV;
}
/**
* 获取自定义属性。
* @param attrs
*/
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomTitleView);
mTitle = typedArray.getString(R.styleable.CustomTitleView_titleText);
dividerEnabled = typedArray.getInt(R.styleable.CustomTitleView_dividerEnabled, DIVIDER_NONE);
dividerLeftEnabled = typedArray.getInt(R.styleable.CustomTitleView_dividerLeftEnabled, DIVIDER_NONE);
dividerRightEnabled = typedArray.getInt(R.styleable.CustomTitleView_dividerRightEnabled, DIVIDER_NONE);
typedArray.recycle();
}
/**
* 初始化界面。
* @param context
*/
@SuppressWarnings("deprecation")
private void initView(Context context) {
if (getBackground() == null) {
setBackgroundDrawable(context.getResources().getDrawable(R.drawable.title_bg));
}
dp_12 = Utils.dip2px(12);
View root = View.inflate(context, getRootLayout(), this);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
this.setLayoutParams(params);
findView(root);
}
/**
* 找到 root 中的各控件,并可以赋予初始值。
* @param root 根布局。
* @since 2014年8月19日
*/
protected void findView(View root) {
mRightBtnBox = (LinearLayout) root.findViewById(R.id.customtitleview_iconbtn_box);
mTitleTV = (TextView) root.findViewById(R.id.customtitleview_titletext);
if (mTitleTV != null) {
// 设置标题文字。
mTitleTV.setText(mTitle);
}
}
/**
* 配置自定义标题栏的布局文件。
* @return 布局ID.
*/
protected int getRootLayout() {
return R.layout.layout_custom_titleview;
}
/**
* 添加右侧图标按钮。
* @param adapter 适配器。
*/
private void fillRightBtn(CustomTitleAdapter adapter) {
if (mRightBtnBox == null || adapter == null) {
LogUtil.e("mIconbtnBox = " + mRightBtnBox + "; CustomTitleAdapter = " + adapter);
return;
}
int count = adapter.getCount();
mRightBtnBox.removeAllViews();
// 最左侧的间隔线
if (dividerLeftEnabled == DIVIDER_BOTH || dividerLeftEnabled == DIVIDER_RIGHT) {
mRightBtnBox.addView(adapter.getDividerView(getContext()));
}
for (int i = 0; i < count; i++) {
// 两个按钮之间的间隔线
if (i > 0 && dividerEnabled == DIVIDER_BOTH || dividerEnabled == DIVIDER_RIGHT) {
mRightBtnBox.addView(adapter.getDividerView(getContext()));
}
FrameLayout layout = new FrameLayout(getContext());
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
params.gravity = Gravity.CENTER;
layout.setLayoutParams(params);
layout.setPadding(dp_12, 0, dp_12, 0);
layout.setBackgroundResource(R.drawable.title_btn_selector);
View icon = adapter.getView(i, layout);
layout.addView(icon);
mRightBtnBox.addView(layout);
// 设置点击监听
layout.setTag(i);
OnClickListener iconBtnClickListener = adapter.getIconBtnClickListener(i, layout);
layout.setEnabled(iconBtnClickListener != null);
layout.setOnClickListener(iconBtnClickListener);
}
// 最右侧的间隔线
if (dividerRightEnabled == DIVIDER_BOTH || dividerRightEnabled == DIVIDER_RIGHT) {
mRightBtnBox.addView(adapter.getDividerView(getContext()));
}
}
}
/**
* 右侧图标按钮的适配器。
*/
public abstract class CustomTitleAdapter implements Adapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
@Override
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public int getItemViewType(int position) {
return 0;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public boolean isEmpty() {
return getCount() == 0;
}
/**
* 更新右侧图标按钮。
*/
public void notifyDatasetChanged() {
mDataSetObservable.notifyChanged();
}
// public void notifyDataSetInvalidated() {
// mDataSetObservable.notifyInvalidated();
// }
@Override
public final View getView(int position, View convertView, ViewGroup parent) {
return this.getView(position, (FrameLayout) parent);
}
/**
* 获取对应{@code position}的图标按钮的点击事件。被点击的 View TAG 为 {@code position}。
* @param position 按钮位置。
* @param container 装按钮的FrameLayout,点击事件是设置在这个FrameLayout上的。
* @return 点击事件。
*/
public abstract OnClickListener getIconBtnClickListener(int position, FrameLayout container);
/**
* 返回一个图标按钮的View。
* @param position 按钮位置。
* @param parent 装按钮的FrameLayout,点击事件是设置在这个FrameLayout上的。
* @return 图标按钮的View。
*/
public abstract View getView(int position, FrameLayout parent);
/**
* 获取对应位置的图标是否可以点击。默认可以点击。
* @param position 按钮位置。
* @return 是否可以点击。
*/
/*
public boolean isEnabled(int position) {
return true;
}*/
/**
* 覆盖此方法,提供间隔线的View。
* 默认使用白色,width=1dp,height=18dp的竖线。
* @param context 上下文。
* @return 间隔线的View。
*/
public View getDividerView(Context context) {
View divider = new View(context);
int dp_18 = Utils.dip2px(18);
int dp_1 = Utils.dip2px(1);
LayoutParams params = new LayoutParams(dp_1, dp_18);
params.gravity = Gravity.CENTER;
divider.setLayoutParams(params);
divider.setBackgroundResource(R.drawable.title_split);
return divider;
}
}
最后在这activity里面实现CustomTitleView
- setTitleText()方法,给标题设置title;
- setOnBackClickListener()方法,给标题设置回退的动作;
- setAdapter()方法,给title动态添加不同的内容和动作。
是不是很炫酷,这样就可以在一个项目当中重构所有风格类似的title
/**
* --------------
* 欢迎转载 | 转载请注明
* --------------
* 如果对你有帮助,请点击|顶|
* --------------
* 请保持谦逊 | 你会走的更远
* --------------
* @author css
* @github https://github.com/songsongbrother
* @blog http://blog.youkuaiyun.com/xiangxi101
*/