学习简单自定义TitleBar

来源:互联网 发布:2017淘宝如何提高销量 编辑:程序博客网 时间:2024/06/05 12:48

开发中有些包含数个控件集合的布局可能会经常重复使用,可能有时候直接写个layout布局,然后在需要时通过include将其添加进来使用,但是会发现所有有添加这个layout的地方我们都需要对其中的控件做绑定findViewById,而且定义之后的控件做相同的操作。因此最好的方法就是将这些具有相同功能效果的控件集合包装起来,自定义做个复合控件。
自定义复合控件方式可以直接将不同的控件添加到layout布局然后使用LayoutInflater绑定,也可在代码中组合控件。此处仅仅是三个控件的组合,所以采用后者。下面以一个常见的标题栏做个简单的例子。
此处定义一个TitleBar的标题栏,功能相对简单,仅仅是左右两个Button和中间的一个TextView。效果如下:
这里写图片描述
这里我按钮中使用文字,Button有默认的padding会显得太大,为图个方便我就都采用TextView了,因为Button继承自TextView,所以没多大关系。
由这里看出我们需要的属性有左右两边按钮的文字、文字颜色及背景,中间标题的文字、文字颜色、文字大小。当然左右文字也可以设置文字大小,可以根据需要添加,方法和中间的标题文字一样。
确定下需要设置的属性后就可以在values目录下创建一个attrs.xml的属性定义的文件。

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="TitleBar">        <attr name="title" format="string"/>        <attr name="titleTextSize" format="dimension"/>        <attr name="leftText" format="string"/>        <attr name="rightText" format="string"/>        <attr name="titleTextColor" format="color"/>        <attr name="leftTextColor" format="color"/>        <attr name="rightTextColor" format="color"/>        <attr name="leftBackground" format="reference|color"/>        <attr name="rightBackground" format="reference|color"/>        <attr name="titlebarBackground" format="color"/>    </declare-styleable></resources>

代码中通过declare-styleable标签声明自定义属性,该标签中name是用来引用这些属性名称。attr标签定义具体的属性其中的nameformat分别是属性名称和它的类型,如果类型具有多种则用“|”隔开。取值时将这些属性的值存入TypedArray中,通过get各属性获取其对应的值。 如获取标题文字的字体大小,其查找的名字格式为{declare-styleable标签中的name}_{attr标签中的name},两个名称中间用下划线“_”连接,后面参数为默认值。注意:在取完值后加上recycle()方法以避免重新创建时发生错误。

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TitleBar);titleTextSize = ta.getFloat(R.styleable.TitleBar_titleTextSize,15);ta.recycle();

复合控件通常继承自ViewGroup,但可根据布局方式复杂程度做不同的选择,如此控件可以通过继承RelativeLayout实现。接下来开始获取attrs中的值

public class TitleBar extends RelativeLayout {    // 取出attrs中styleable的值    // 文字    private String titleText,leftText,rightText;    // titilebar背景颜色和三个控件文字颜色    private int titlebarBackgroundColor,titleTextColor,leftTextColor,rightTextColor;    // 左右两个TextView的背景资源    private int leftBackground,rightBackground;    // 标题的文字字体大小    private float titleTextSize;    public TitleBar(Context context, AttributeSet attrs) {        super(context, attrs);        initVariable(context,attrs);    }    /**     初始化变量     */    private void initVariable(Context context,AttributeSet attrs) {        // 将attrs中的值存储到TypedArray中        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TitleBar);        titleText = ta.getString(R.styleable.TitleBar_title);        leftText = ta.getString(R.styleable.TitleBar_leftText);        rightText = ta.getString(R.styleable.TitleBar_rightText);        titlebarBackgroundColor = ta.getColor(R.styleable.TitleBar_titlebarBackground, 0);        titleTextColor = ta.getColor(R.styleable.TitleBar_titleTextColor,                ContextCompat.getColor(context,R.color.titleTextColor));        leftTextColor = ta.getColor(R.styleable.TitleBar_leftTextColor,                ContextCompat.getColor(context,R.color.leftTextColor));        rightTextColor = ta.getColor(R.styleable.TitleBar_rightTextColor,                ContextCompat.getColor(context,R.color.rightTextColor));        leftBackground = ta.getResourceId(R.styleable.TitleBar_leftBackground,                R.drawable.titlebar_left_btn_bg);        rightBackground = ta.getResourceId(R.styleable.TitleBar_rightBackground,                R.drawable.titlebar_right_btn_bg);        titleTextSize = ta.getFloat(R.styleable.TitleBar_titleTextSize,15);        // 注意!此处获取完属性值后要添加recycle()方法,避免重新创建时发生错误        ta.recycle();    }}

此处我添加了文字的默认颜色及按钮的背景图片,三个文字的颜色的色值都一样,只是名称不同,可以自己更改。。
<color name="titleTextColor">#795548</color>
按钮的背景使用两个shape做成的selector图片选择器,我也做成一样的效果。以下是其中的一个

selector

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <item android:drawable="@drawable/btn_bg_pres" android:state_checked="true"></item>    <item android:drawable="@drawable/btn_bg_pres" android:state_selected="true"></item>    <item android:drawable="@drawable/btn_bg_pres" android:state_pressed="true"></item>    <item android:drawable="@drawable/btn_bg_nor"></item></selector>

正常状态下的背景

<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android">    <corners        android:radius="3dp"/>    <solid        android:color="@android:color/transparent"/>    <stroke        android:width="1dp"        android:color="@color/titlebar_btn_nor"        /></shape>

按下时的背景

<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android">    <corners        android:radius="3dp"/>    <solid        android:color="@android:color/transparent"/>    <stroke        android:width="1dp"        android:color="@color/titlebar_btn_pres"        /></shape>

两种状态的背景色

    <!-- titlebar -->    <color name="titlebar_btn_nor">#795548</color>    <color name="titlebar_btn_pres">#5D4037</color>

当属性的数值都已经设置完毕后,就开始组合控件了。组合时先实例化出三个TextView,设置上面所获取来的属性,然后设置这三个控件的位置及大小,最后将这些控件添加到整个组件中。

public class TitleBar extends RelativeLayout {    // 取出attrs中styleable的值    // 文字    private String titleText,leftText,rightText;    // titilebar背景颜色和三个控件文字颜色    private int titlebarBackgroundColor,titleTextColor,leftTextColor,rightTextColor;    // 左右两个TextView的背景资源    private int leftBackground,rightBackground;    // 标题的文字字体大小    private float titleTextSize;    // 定义组件    private TextView leftButton,rightButton;    private TextView titleTextView;    // 布局属性,用来控制组件元素在ViewGroup中的位置    private LayoutParams mLeftParams, mTitlepParams, mRightParams;    public TitleBar(Context context, AttributeSet attrs) {        super(context, attrs);        initVariable(context,attrs);        initView(context);    }    /**     初始化变量     */    private void initVariable(Context context,AttributeSet attrs) {    }    /**    初始化控件     */    private void initView(Context context) {        setBackgroundColor(titlebarBackgroundColor);        // 创建childView        leftButton = new TextView(context);        rightButton = new TextView(context);        titleTextView = new TextView(context);        // 设置childview属性        leftButton.setTextColor(leftTextColor);        leftButton.setBackgroundResource(leftBackground);        leftButton.setText(leftText);        leftButton.setPadding(20,10,20,10);        rightButton.setTextColor(rightTextColor);        rightButton.setBackgroundResource(rightBackground);        rightButton.setText(rightText);        rightButton.setPadding(20,10,20,10);        titleTextView.setText(titleText);        titleTextView.setTextColor(titleTextColor);        titleTextView.setTextSize(titleTextSize);        titleTextView.setGravity(Gravity.CENTER);        // 设置布局并添加到ViewGroup中,此处最好是WRAP_CONTENT,不然Measure时控件会过大,会使设置控件高度为wrap_content时失效        mLeftParams = new LayoutParams(                LayoutParams.WRAP_CONTENT,                LayoutParams.WRAP_CONTENT);        mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);        mLeftParams.addRule(RelativeLayout.CENTER_VERTICAL, TRUE);        mLeftParams.setMargins(10,10,10,10);        // 添加到ViewGroup        addView(leftButton, mLeftParams);        mRightParams = new LayoutParams(                LayoutParams.WRAP_CONTENT,                LayoutParams.WRAP_CONTENT);        mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);        mRightParams.addRule(RelativeLayout.CENTER_VERTICAL, TRUE);        mRightParams.setMargins(10,10,10,10);        addView(rightButton, mRightParams);        mTitlepParams = new LayoutParams(                LayoutParams.WRAP_CONTENT,                LayoutParams.WRAP_CONTENT);        mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);        addView(titleTextView, mTitlepParams);    }

测量。现在整个复合控件已初步有了雏形,接下来为我们在layout中设置layout_width和layout_height做测量工作,当然这里宽可以确定就是屏幕的宽度了,直接设为match_parent,高就是测量控件中所有子控件所占的高,一般设为wrap_content,或者是我们自己给定的值。View的测量过程在onMeasure()方法中进行,而且Android系统提供了一个帮助我们测量的类——MeasureSpec类。测量的模式有三种:

  • EXACTLY
    精确模式。layout_width和layout_height有确定的数值,如:android:layout_height=”50dp”
  • AT_MOST
    最大模式。如:android:layout_height=”wrap_content”
  • UNSPECIFIED
    未指定模式。一般用在需要绘制的自定义中

在测量之后会把测量的宽高传给setMeasuredDimension()方法,可以通过MeasureSpec的getMode()方法和getSize()方法获取对象的测量模式和测量出的宽高。当测量模式为EXACTLY时,则直接将获取的测量值传给setMeasuredDimension();当测量模式为AT_MOST时,也就是宽高属性为”wrap_content”时,传给setMeasuredDimension的值就是我们设置的默认值和最后测量值之间较小的那个。

@Override    protected void onMeasure(int widthMeasureSpec,                             int heightMeasureSpec) {        // 如果当前ViewGroup的宽高为wrap_content的情况        int width = 0; // 自己测量的宽度        int height = 0;// 自己测量的高度        // 获取子view的个数        int childCount = getChildCount();        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            // 测量子View的宽和高            measureChildWithMargins(child, widthMeasureSpec,0, heightMeasureSpec,0);            // 得到LayoutParams            MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();            // 子View占据的宽度            int childWidth = child.getMeasuredWidth() + lp.leftMargin                    + lp.rightMargin;            // 子View占据的高度            int childHeight = child.getMeasuredHeight() + lp.topMargin                    + lp.bottomMargin;            width += childWidth;            if (childHeight > height){                height = childHeight;            }        }        setMeasuredDimension(                measureDimension(width,widthMeasureSpec),                measureDimension(height,heightMeasureSpec));        super.onMeasure(widthMeasureSpec,heightMeasureSpec);    }    private int measureDimension(int defaultDimension,int measureSpec) {        int result;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        if (specMode == MeasureSpec.EXACTLY) {            result = specSize;        } else {            result = defaultDimension;            if (specMode == MeasureSpec.AT_MOST) {                result = Math.min(result, specSize);            }        }        return result;    }

由于三个TextView呈一行排列,因此控件的宽度为三个childview的宽总和加上它们之间的Margin值,高为高度最高的那个childview的高度加上它的上下Margin值。至此测量就已经结束了,在layout中直接添加控件设置宽高就可以显示最上面的效果。
接下来就是将控件的操作暴露给使用者,既然控件中有点击操作,那就需要添加OnClickListener。由于点击的实现逻辑是在自定义类之外,因此可以定义个回调接口,并将接口暴露出来。另外添加一个设置按钮显示与否的方法。这里也可以添加其他方法以增加灵活性,如在代码中设置按钮文字和标题文字等。此处仅仅举一个栗子。

public class TitleBar extends RelativeLayout {    // 定义组件    private TextView leftButton,rightButton;    // 点击监听接口    private titlebarClickListener clickListener;    public TitleBar(Context context, AttributeSet attrs) {        super(context, attrs);        initVariable(context,attrs);        initView(context);    }    /**     初始化变量     */    private void initVariable(Context context,AttributeSet attrs) {        // 将attrs中的值存储到TypedArray中    }    /**    初始化控件     */    private void initView(Context context) {        // 其他代码........        // 设置监听        leftButton.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View view) {                clickListener.leftClick();            }        });        rightButton.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View view) {                clickListener.rightClick();            }        });    }    @Override    protected void onMeasure(int widthMeasureSpec,                             int heightMeasureSpec) {        // 测量        super.onMeasure(widthMeasureSpec,heightMeasureSpec);    }    /**    添加按钮监听     */    public void setOnTitleBarClickListener(titlebarClickListener titlebarListener){        this.clickListener = titlebarListener;    }    /**    设置按钮点击监听回调接口     */    public interface titlebarClickListener{        void leftClick();        void rightClick();    }    /**    设置按钮是否显示     */    public void setButtonVisable(boolean leftBtnVisable,boolean rightBtnVisable){        if (leftBtnVisable){            leftButton.setVisibility(View.VISIBLE);        }else{            leftButton.setVisibility(View.GONE);        }        if (rightBtnVisable){            rightButton.setVisibility(View.VISIBLE);        }else{            rightButton.setVisibility(View.GONE);        }    }}

具体使用,此处也可以将TitleBar单独写在一个layout中,然后用时include进来

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:orientation="vertical">    <com.doublecc.basicandroid.widget.TitleBar        android:id="@+id/titlebar"        android:layout_width="match_parent"        android:layout_height="wrap_content"        app:title="详情"        app:leftText="返回"        app:rightText="收藏"        >    </com.doublecc.basicandroid.widget.TitleBar>    <!--其他布局--></LinearLayout>
    titleBar.setOnTitleBarClickListener(new TitleBar.titlebarClickListener() {            @Override            public void leftClick() {                // 左边按钮点击操作            }            @Override            public void rightClick() {                // 右边按钮点击操作            }        });    // 设置左右按钮显示与否    titleBar.setButtonVisable(true,false);

完整代码

public class TitleBar extends RelativeLayout {    // 取出attrs中styleable的值    // 文字    private String titleText,leftText,rightText;    // titilebar背景颜色和三个控件文字颜色    private int titlebarBackgroundColor,titleTextColor,leftTextColor,rightTextColor;    // 左右两个TextView的背景资源    private int leftBackground,rightBackground;    // 标题的文字字体大小    private float titleTextSize;    // 定义组件    private TextView leftButton,rightButton;    private TextView titleTextView;    // 布局属性,用来控制组件元素在ViewGroup中的位置    private LayoutParams mLeftParams, mTitlepParams, mRightParams;    // 点击监听接口    private titlebarClickListener clickListener;    public TitleBar(Context context, AttributeSet attrs) {        super(context, attrs);        initVariable(context,attrs);        initView(context);    }    /**     初始化变量     */    private void initVariable(Context context,AttributeSet attrs) {        // 将attrs中的值存储到TypedArray中        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TitleBar);        titleText = ta.getString(R.styleable.TitleBar_title);        leftText = ta.getString(R.styleable.TitleBar_leftText);        rightText = ta.getString(R.styleable.TitleBar_rightText);        titlebarBackgroundColor = ta.getColor(R.styleable.TitleBar_titlebarBackground, 0);        titleTextColor = ta.getColor(R.styleable.TitleBar_titleTextColor,                ContextCompat.getColor(context,R.color.titleTextColor));        leftTextColor = ta.getColor(R.styleable.TitleBar_leftTextColor,                ContextCompat.getColor(context,R.color.leftTextColor));        rightTextColor = ta.getColor(R.styleable.TitleBar_rightTextColor,                ContextCompat.getColor(context,R.color.rightTextColor));        leftBackground = ta.getResourceId(R.styleable.TitleBar_leftBackground,                R.drawable.titlebar_left_btn_bg);        rightBackground = ta.getResourceId(R.styleable.TitleBar_rightBackground,                R.drawable.titlebar_right_btn_bg);        titleTextSize = ta.getFloat(R.styleable.TitleBar_titleTextSize,15);        // 注意!此处获取完属性值后要添加recycle()方法,避免重新创建时发生错误        ta.recycle();    }    /**    初始化控件     */    private void initView(Context context) {        setBackgroundColor(titlebarBackgroundColor);        // 创建childView        leftButton = new TextView(context);        rightButton = new TextView(context);        titleTextView = new TextView(context);        // 设置childview属性        leftButton.setTextColor(leftTextColor);        leftButton.setBackgroundResource(leftBackground);        leftButton.setText(leftText);        leftButton.setPadding(20,10,20,10);        rightButton.setTextColor(rightTextColor);        rightButton.setBackgroundResource(rightBackground);        rightButton.setText(rightText);        rightButton.setPadding(20,10,20,10);        titleTextView.setText(titleText);        titleTextView.setTextColor(titleTextColor);        titleTextView.setTextSize(titleTextSize);        titleTextView.setGravity(Gravity.CENTER);        // 设置布局并添加到ViewGroup中,此处最好是WRAP_CONTENT,不然Measure时控件会过大,会使设置控件高度为wrap_content时失效        mLeftParams = new LayoutParams(                LayoutParams.WRAP_CONTENT,                LayoutParams.WRAP_CONTENT);        mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);        mLeftParams.addRule(RelativeLayout.CENTER_VERTICAL, TRUE);        mLeftParams.setMargins(10,10,10,10);        // 添加到ViewGroup        addView(leftButton, mLeftParams);        mRightParams = new LayoutParams(                LayoutParams.WRAP_CONTENT,                LayoutParams.WRAP_CONTENT);        mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);        mRightParams.addRule(RelativeLayout.CENTER_VERTICAL, TRUE);        mRightParams.setMargins(10,10,10,10);        addView(rightButton, mRightParams);        mTitlepParams = new LayoutParams(                LayoutParams.WRAP_CONTENT,                LayoutParams.WRAP_CONTENT);        mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);        addView(titleTextView, mTitlepParams);        // 设置监听        leftButton.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View view) {                clickListener.leftClick();            }        });        rightButton.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View view) {                clickListener.rightClick();            }        });    }    @Override    protected void onMeasure(int widthMeasureSpec,                             int heightMeasureSpec) {        // 如果当前ViewGroup的宽高为wrap_content的情况        int width = 0; // 自己测量的宽度        int height = 0;// 自己测量的高度        // 获取子view的个数        int childCount = getChildCount();        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            // 测量子View的宽和高            measureChildWithMargins(child, widthMeasureSpec,0, heightMeasureSpec,0);            // 得到LayoutParams            MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();            // 子View占据的宽度            int childWidth = child.getMeasuredWidth() + lp.leftMargin                    + lp.rightMargin;            // 子View占据的高度            int childHeight = child.getMeasuredHeight() + lp.topMargin                    + lp.bottomMargin;            width += childWidth;            if (childHeight > height){                height = childHeight;            }        }        setMeasuredDimension(                measureDimension(width,widthMeasureSpec),                measureDimension(height,heightMeasureSpec));        super.onMeasure(widthMeasureSpec,heightMeasureSpec);    }    private int measureDimension(int defaultDimension,int measureSpec) {        int result;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        if (specMode == MeasureSpec.EXACTLY) {            result = specSize;        } else {            result = defaultDimension;            if (specMode == MeasureSpec.AT_MOST) {                result = Math.min(result, specSize);            }        }        return result;    }    /**    添加按钮监听     */    public void setOnTitleBarClickListener(titlebarClickListener titlebarListener){        this.clickListener = titlebarListener;    }    /**    设置按钮点击监听回调接口     */    public interface titlebarClickListener{        void leftClick();        void rightClick();    }    /**    设置按钮是否显示     */    public void setButtonVisable(boolean leftBtnVisable,boolean rightBtnVisable){        if (leftBtnVisable){            leftButton.setVisibility(View.VISIBLE);        }else{            leftButton.setVisibility(View.GONE);        }        if (rightBtnVisable){            rightButton.setVisibility(View.VISIBLE);        }else{            rightButton.setVisibility(View.GONE);        }    }}
0 0
原创粉丝点击