学习简单自定义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标签定义具体的属性其中的name和format分别是属性名称和它的类型,如果类型具有多种则用“|”隔开。取值时将这些属性的值存入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); } }}
- 学习简单自定义TitleBar
- 简单自定义View之TitleBar
- 自定义 TitleBar
- 自定义TitleBar
- 自定义titlebar
- 自定义TitleBar
- 自定义TitleBar
- android学习——自定义标题栏titlebar
- 学习android笔记(1):隐藏系统自定义titleBar
- 自定义Android标题栏TitleBar
- 王立平--自定义TitleBar
- android自定义Titlebar
- android自定义titlebar
- Android自定义TitleBar
- android 自定义TitleBar...
- android 自定义Titlebar
- android 自定义Titlebar
- TitleBar 自定义布局
- 正交表测试用例设计
- Lab01 Editing text files on the command line
- 分布式系统的事务处理(转)
- The 7 stages of refactoring(转)
- 水平可见直线
- 学习简单自定义TitleBar
- 【Z10】引水入城
- 从10万条(int)数组中找到重复的数
- 弹性盒模型的实际应用
- 地图切片详解
- C#语言之“实例化的正确顺序”
- php pear基本使用方法
- Windows7如何进入应用程序的安装目录
- Hadoop安装配置