android 自定义View对Title的封装

  1. 设计思路
    很多时候我们应用的activity的title风格基本一致,只有微小的差异。这个时候我们可以把相同的部分写好,不同的部分设置一个adapter动态的填充不同的内容及动作。
  2. 具体实现

效果图
这里写图片描述

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

  1. setTitleText()方法,给标题设置title;
  2. setOnBackClickListener()方法,给标题设置回退的动作;
  3. setAdapter()方法,给title动态添加不同的内容和动作。

是不是很炫酷,这样就可以在一个项目当中重构所有风格类似的title

/**
* --------------
* 欢迎转载 | 转载请注明
* --------------
* 如果对你有帮助,请点击|顶|
* --------------
* 请保持谦逊 | 你会走的更远
* --------------
* @author css
* @github https://github.com/songsongbrother
* @blog http://blog.csdn.net/xiangxi101
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值