纯手工打造一个通使用的标题栏TitleBar

作者 : 开心源码 本文共10262个字,预计阅读时间需要26分钟 发布时间: 2022-05-12 共162人阅读

Github传送地址,欢迎Star,Pull及issue

首先看一个项目已经有标题栏

这个自己设置组合控件的写法是用布局填充器(LayoutInflater)初始化XML布局

但是有没有想过这样一个问题,LayoutInflater需要通过XML解析再用代码初始化View的,假如我们直接用代码初始化View呢?效率和性能是不是更好了?显而易见当然就是了。

  • 整容前(TitleActionBar)

  • 整容后(TitleBar)

有细心的同学就会发现了第一张图中的状态栏颜色和Title的颜色显著不搭,这是由于我们用了沉迷式状态,而这个TitleActionBar对沉迷式状态不是很友好,第二张图就显得比较友好了,接下来让我们使用纯手工编写一个通使用的TitleBar吧

TitleBar布局解析

/** * 标题栏 */public class TitleBar extends FrameLayout {    public TitleBar(Context context) {        this(context, null, 0);    }    public TitleBar(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public TitleBar(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }}

创立一个Builder

用这个Builder构建一个LinearLayout,而后往LinearLayout增加三个TextView,最后将这个LinearLayout增加到TitleBar中

static class Builder {    private LinearLayout mMainLayout;    private TextView mLeftView;    private TextView mTitleView;    private TextView mRightView;    private View mLineView;    Builder(Context context) {        mMainLayout = new LinearLayout(context);        mMainLayout.setOrientation(LinearLayout.HORIZONTAL);        mMainLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        mLeftView = new TextView(context);        mLeftView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));        mLeftView.setPadding(Builder.dp2px(context, 15), 0, Builder.dp2px(context, 15), 0);        mLeftView.setCompoundDrawablePadding(Builder.dp2px(context, 5));        mLeftView.setGravity(Gravity.CENTER_VERTICAL);        mLeftView.setSingleLine();        mLeftView.setEllipsize(TextUtils.TruncateAt.END);        mLeftView.setEnabled(false);        mTitleView = new TextView(context);        LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(1, ViewGroup.LayoutParams.MATCH_PARENT);        titleParams.weight = 1;        titleParams.leftMargin = Builder.dp2px(context, 10);        titleParams.rightMargin = Builder.dp2px(context, 10);        mTitleView.setLayoutParams(titleParams);        mTitleView.setGravity(Gravity.CENTER);        mTitleView.setSingleLine();        mTitleView.setEllipsize(TextUtils.TruncateAt.END);        mTitleView.setEnabled(false);        mRightView = new TextView(context);        mRightView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));        mRightView.setPadding(Builder.dp2px(context, 15), 0, Builder.dp2px(context, 15), 0);        mRightView.setCompoundDrawablePadding(Builder.dp2px(context, 5));        mRightView.setGravity(Gravity.CENTER_VERTICAL);        mRightView.setSingleLine();        mRightView.setEllipsize(TextUtils.TruncateAt.END);        mRightView.setEnabled(false);        mLineView = new View(context);        FrameLayout.LayoutParams lineParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1);        lineParams.gravity = Gravity.BOTTOM;        mLineView.setLayoutParams(lineParams);    }    LinearLayout getMainLayout() {        return mMainLayout;    }    View getLineView() {        return mLineView;    }    TextView getLeftView() {        return mLeftView;    }    TextView getTitleView() {        return mTitleView;    }    TextView getRightView() {        return mRightView;    }    /**     * 获取ActionBar的高度     */    static int getActionBarHeight(Context context) {        TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.actionBarSize});        int actionBarSize = (int) ta.getDimension(0, 0);        ta.recycle();        return actionBarSize;    }    /**     * dp转px     */    static int dp2px(Context context, float dpValue) {        final float scale = context.getResources().getDisplayMetrics().density;        return (int) (dpValue * scale + 0.5f);    }    /**     * sp转px     */    static int sp2px(Context context, float spValue) {        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;        return (int) (spValue * fontScale + 0.5f);    }}

初始化View

private LinearLayout mMainLayout;private TextView mLeftView;private TextView mTitleView;private TextView mRightView;private View mLineView;private void initView(Context context) {    Builder builder = new Builder(context);    mMainLayout = builder.getMainLayout();    mLineView = builder.getLineView();    mTitleView = builder.getTitleView();    mLeftView = builder.getLeftView();    mRightView = builder.getRightView();    mMainLayout.addView(mLeftView);    mMainLayout.addView(mTitleView);    mMainLayout.addView(mRightView);    addView(mMainLayout, 0);    addView(mLineView, 1);}

定义少量属性值

<declare-styleable name="TitleBar">    <!-- 标题 -->    <attr name="title" format="string" />    <attr name="title_left" format="string"/>    <attr name="title_right" format="string" />    <!-- 图标 -->    <attr name="icon_left" format="reference" />    <attr name="icon_right" format="reference" />    <!-- 返回按钮,默认开 -->    <attr name="icon_back" format="boolean" />    <!-- 文字颜色 -->    <attr name="color_title" format="color" />    <attr name="color_right" format="color" />    <attr name="color_left" format="color" />    <!-- 文字大小 -->    <attr name="size_title" format="dimension" />    <attr name="size_right" format="dimension" />    <attr name="size_left" format="dimension" />    <!-- 按钮背景 -->    <attr name="background_left" format="reference|color" />    <attr name="background_right" format="reference|color" />    <!-- 分割线 -->    <attr name="line" format="boolean" />    <attr name="color_line" format="color" /></declare-styleable>

初始化属性样式

private void initStyle(AttributeSet attrs) {    TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.TitleBar);    //标题设置    if (ta.hasValue(R.styleable.TitleBar_title_left)) {        setLeftTitle(ta.getString(R.styleable.TitleBar_title_left));    }    if (ta.hasValue(R.styleable.TitleBar_title)) {        setTitle(ta.getString(R.styleable.TitleBar_title));    } else {        //假如当前上下文对象是Activity,就获取Activity的标题        if (getContext() instanceof Activity) {            //获取清单文件中的label属性值            CharSequence label = ((Activity) getContext()).getTitle();            //假如Activity没有设置label属性,则默认会返回APP名称,需要过滤掉            if (label != null && !label.toString().equals("")) {                try {                    PackageManager packageManager = getContext().getPackageManager();                    PackageInfo packageInfo = packageManager.getPackageInfo(                            getContext().getPackageName(), 0);                    if (!label.toString().equals(packageInfo.applicationInfo.loadLabel(packageManager).toString())) {                        setTitle(label);                    }                } catch (PackageManager.NameNotFoundException ignored) {}            }        }    }    if (ta.hasValue(R.styleable.TitleBar_title_right)) {        setRightTitle(ta.getString(R.styleable.TitleBar_title_right));    }    // 图标设置    if (ta.hasValue(R.styleable.TitleBar_icon_left)) {        setLeftIcon(getContext().getResources().getDrawable(ta.getResourceId(R.styleable.TitleBar_icon_left, 0)));    } else {        // 显示返回图标        if (ta.getBoolean(R.styleable.TitleBar_icon_back, true)) {            setLeftIcon(getContext().getResources().getDrawable(R.mipmap.ico_back_black));        }    }    if (ta.hasValue(R.styleable.TitleBar_icon_right)) {        setRightIcon(getContext().getResources().getDrawable(ta.getResourceId(R.styleable.TitleBar_icon_right, 0)));    }    //文字颜色设置    mLeftView.setTextColor(ta.getColor(R.styleable.TitleBar_color_left, 0xFF666666));    mTitleView.setTextColor(ta.getColor(R.styleable.TitleBar_color_title, 0xFF222222));    mRightView.setTextColor(ta.getColor(R.styleable.TitleBar_color_right, 0xFFA4A4A4));    //文字大小设置    mLeftView.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.TitleBar_size_left, Builder.sp2px(getContext(), 14)));    mTitleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.TitleBar_size_title, Builder.sp2px(getContext(), 16)));    mRightView.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.TitleBar_size_right, Builder.sp2px(getContext(), 14)));    //背景设置    mLeftView.setBackgroundResource(ta.getResourceId(R.styleable.TitleBar_background_left, R.drawable.selector_selectable_transparent));    mRightView.setBackgroundResource(ta.getResourceId(R.styleable.TitleBar_background_right, R.drawable.selector_selectable_transparent));    //分割线设置    mLineView.setVisibility(ta.getBoolean(R.styleable.TitleBar_line, true) ? View.VISIBLE : View.GONE);    mLineView.setBackgroundColor(ta.getColor(R.styleable.TitleBar_color_line, 0xFFECECEC));    //回收TypedArray    ta.recycle();    //设置默认背景    if (getBackground() == null) {        setBackgroundColor(0xFFFFFFFF);    }}public void setTitle(CharSequence text) {    mTitleView.setText(text);    postDelayed(this, 100);}public void setLeftTitle(CharSequence text) {    mLeftView.setText(text);    postDelayed(this, 100);}public void setRightTitle(CharSequence text) {    mRightView.setText(text);    postDelayed(this, 100);}public void setLeftIcon(Drawable drawable) {    mLeftView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);    postDelayed(this, 100);}public void setRightIcon(Drawable drawable) {    mRightView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null);    postDelayed(this, 100);}// Runnable@Overridepublic void run() {    //升级中间标题的内边距,避免向左或者者向右偏移    int leftSize = mLeftView.getWidth();    int rightSize = mRightView.getWidth();    if (leftSize != rightSize) {        if (leftSize > rightSize) {            mTitleView.setPadding(0, 0, leftSize - rightSize, 0);        } else {            mTitleView.setPadding(rightSize - leftSize, 0, 0, 0);        }    }    //升级View状态    if (!"".equals(mLeftView.getText().toString()) || mLeftView.getCompoundDrawables()[0] != null) {        mLeftView.setEnabled(true);    }    if (!"".equals(mTitleView.getText().toString())) {        mTitleView.setEnabled(true);    }    if (!"".equals(mRightView.getText().toString()) || mRightView.getCompoundDrawables()[2] != null) {        mRightView.setEnabled(true);    }}

这个有个地方需要特别注意的是:标题栏的默认标题是来自Activity在清单文件中的label属性,为什么要那么做呢,由于系统原生的ActionBar也是那么做,这样做的好处是可以在清单文件中快速查找到需要的Activity,所以强烈建议大家那么做

定义默认TitleBar的默认高度为Action的高度值

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    //设置TitleBar默认的高度    if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST || MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Builder.getActionBarHeight(getContext()), MeasureSpec.EXACTLY));    } else {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }}

解决监听事件

@Overrideprotected void onAttachedToWindow() {    super.onAttachedToWindow();    //设置监听    mTitleView.setOnClickListener(this);    mLeftView.setOnClickListener(this);    mRightView.setOnClickListener(this);}@Overrideprotected void onDetachedFromWindow() {    //移除监听    mTitleView.setOnClickListener(null);    mLeftView.setOnClickListener(null);    mRightView.setOnClickListener(null);    super.onDetachedFromWindow();}public void setOnTitleBarListener(OnTitleBarListener l) {    mListener = l;}public interface OnTitleBarListener {    void onLeftClick(View v);    void onTitleClick(View v);    void onRightClick(View v);}// View.OnClickListener@Overridepublic void onClick(View v) {    if (mListener == null) return;    if (v == mLeftView) {        mListener.onLeftClick(v);    }else if (v == mTitleView) {        mListener.onTitleClick(v);    }else if (v == mRightView) {        mListener.onRightClick(v);    }}

大功告成

接下来让我们比照一组数据

类名Java行数XML行数Layout数View数支持扩展执行效率
TitleActionBar3867445不支持一般
TitleBar311024支持自己设置
  • 整容前(TitleActionBar)需要:Java 386行代码 + XML 74行代码

  • 整容后(TitleBar)需要:纯Java 311行代码

控件的性能和代码执行数有肯定的关联,但是最重要的是不需要再通过XML去解析,同时用LayoutInflater会多出一层多余的布局嵌套(由于LayoutInflater最终会调使用ViewGroup中的addView方法,具体介绍请查看源码,这里不再细说)

应使用的标题栏是我们十分常使用的控件,也是APP最重要的UI控件之一,标题栏的优化关乎整个APP,由于每个界面几乎都会用到这个控件,所以更应该做好性能优化

点此下载Demo,最后记得点赞 + Star

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 纯手工打造一个通使用的标题栏TitleBar

发表回复