一、自定义控件分类
1.1、原生控件拓展
修改原有控件我们只需要创建一个类继承该View(ViewGroup),再原有的逻辑上添加自己的实现即可。
a)文本框(TextView)默认是无法获取焦点的,想让它获取焦点,我们可以通过自定义控件,并重写isFocused来解决。
public class FocuseTextView extends TextView { public FocuseTextView(Context context) { this(context,null); } public FocuseTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override @ExportedProperty(category = "focus") public boolean isFocused() { //欺骗系统,误以为textview得到了焦点 return true; }}
b)绘制一个带矩形边框的TextView
public class MyTetView extends TextView { private Paint mPaint1; private Paint mpaint2; public MyTetView(Context context) { this(context,null); } public MyTetView(Context context, AttributeSet attrs) { super(context, attrs); initPaint(); } private void initPaint() { mPaint1 = new Paint(); mPaint1.setStyle(Paint.Style.FILL); mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light)); mpaint2 = new Paint(); mpaint2.setStyle(Paint.Style.FILL); mpaint2.setColor(Color.YELLOW); } @Override protected void onDraw(Canvas canvas) { // 绘制外层矩形 canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint1); // 绘制内层矩形 canvas.drawRect(10, 10, getMeasuredWidth() - 10, getMeasuredHeight() - 10, mpaint2); canvas.save(); super.onDraw(canvas); canvas.restore(); }}
1.2、自定义组合控件
(1) 组合原生控件
在自定义组合控件时,我们并不需要自己去绘制视图上显示的内容,而只是用系统原生的控件组合即可。
a) 我们首先制定好需要组合的控件,用它来达到我们想要的效果:
b) 在values目录下创建attrs.xml文件,并定义好属性:
c) 创建自定义控件类继承自ViewGroup,并实现带attrs的构造函数,再使用TypeArray来获取属性:
public class TextViewCheckBox extends RelativeLayout { private TextView mTvTitle,mTvContent; private CheckBox mCbClick; private String mTitle,mContentOn,mContentOff; public TextViewCheckBox(Context context) { this(context,null); } public TextViewCheckBox(Context context, AttributeSet attrs) { super(context, attrs); // 初始化布局和控件 View view = View.inflate(context, R.layout.ui_text_checkbox, this); mTvTitle = (TextView) view.findViewById(R.id.tv_title); mTvContent = (TextView) view.findViewById(R.id.tv_content); mCbClick = (CheckBox) view.findViewById(R.id.cb_click); // 将attrs.xml中定义的所有属性的值存储到TypeArray中 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.combinationView); mTitle = array.getString(R.styleable.combinationView_title); mContentOn = array.getString(R.styleable.combinationView_content_on); mContentOff = array.getString(R.styleable.combinationView_content_off); array.recycle(); // 初始化子控件描述和状态 if(mTitle != null){ mTvTitle.setText(mTitle); } if(mContentOff != null){ mTvContent.setText(mContentOff); } }}
d) 暴露方法给调用者来设置描述和状态:
/**判断是否被选中*/public boolean isChecked(){ return mCbClick.isChecked();}/**设置选中的状态*/public void setChecked(boolean isChecked){ mCbClick.setChecked(isChecked); if(isChecked){ mTvContent.setText(mContentOn); }else{ mTvContent.setText(mContentOff); }}
e) 在布局中引用该控件,引入名称空间,并设置自定义的属性。
注意:在使用自定义控件时需要引入名称空间:xmlns:review="http://schemas.android.com/apk/res/cn.legend.review"
如果想让控件响应事件的话,则直接重写事件即可,如果用到了wrap_content或match_parent则需要进行测量等操作。
1.3、自定义属性种类
1. reference:参考某一资源ID。
(1)属性定义:(2)属性使用:
2. color:颜色值
(1)属性定义:(2)属性使用:
3. boolean:布尔值
1)属性定义:(2)属性使用:
4. dimension:尺寸值
(1)属性定义:(2)属性使用:
5. float:浮点值
(1)属性定义:(2)属性使用:
6. integer:整型值
(1)属性定义:(2)属性使用:
7. string:字符串
(1)属性定义:(2)属性使用:
8. fraction:百分数
(1)属性定义:(2)属性使用:
9. enum:枚举值
(1)属性定义:(2)属性使用:
10. 位或运算
(1)属性定义:(2)属性使用:
注意:属性定义时可以指定多种类型值,使用“|”进行隔离多种类型。
(1)属性定义:(2)属性使用:
1.4、自定义属性用法
在我们自定义的View中有需要自定义的属性,则需要在values下建立attrs.xml,在其中定义你的属性。
a)在res/values文件下定义一个attrs.xml文件,代码如下:
b)在布局xml中如下使用该属性:
2、两种获取方式
我们可以在自定义View的构造函数中获取属性的值,有以下两种方式:
第一种获取方式:
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.ToolBar);int buttonNum = array.getInt(R.styleable.ToolBar_buttonNum, 5);int itemBg = array.getResourceId(R.styleable.ToolBar_itemBackground, -1);array.recycle();
第二种获取方式:
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.ToolBar);int count = array.getIndexCount();for (int i = 0; i < count; i++) { int attr = array.getIndex(i); switch (attr) { case R.styleable.ToolBar_buttonNum: int buttonNum = array.getInt(attr, 5); break; case R.styleable.ToolBar_itemBackground: int itemBg = array.getResourceId(attr, -1); break; }}
3、使用要点
属性类型:string、integer、dimension、reference、color、enum。
下面我看下使用自定义属性需要注意的一些地方,首先看下attrs.xml文件前面几种声明方式都是一致的,例如:
只有enum是不同的,用法举例:
如果该属性可同时传两种不同的属性,则可以用“|”分割开即可。让我们再看看布局xml中需要注意的事项,在自定义组件的构造函数中使用:
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.ToolBar);
获得对属性集的引用,然后就可以用“array”的各种方法来获取相应的属性值。这里需要注意的是,如果使用的方法和获取值的类型不对的话,则会返回默认值。
因此,如果一个属性是带两个及以上不用类型的属性,需要做多次判断,直到读取完毕后才能判断应该赋予何值。
1.5、自定义View演示
编写简单验证码程序:
a) 自定义View的属性,首先在res/value/下建立attrs.xml,里面定义我们的属性和声明我们的整个样式
b) 我们定义字体、字体颜色、字体大小等3个属性,foemat是指该属性的取值类型。
然后在布局文件中声明我们自定义的View,注意:自定义属性需要引入名称空间
c) 由于自定义属性,我们的View必须实现带attr的构造方法:
public class CustomTitleView extends View { private String mCustomText; private int mCustomTextColor; private int mCustomTextSize; private Rect mRect; private Paint mPaint; public CustomTitleView(Context context) { this(context, null); } public CustomTitleView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomTitleView); mCustomText = array.getString(R.styleable.CustomTitleView_titleText); mCustomTextColor = array.getColor(R.styleable.CustomTitleView_titleTextColor, Color.BLACK); mCustomTextSize = (int) array.getDimension(R.styleable.CustomTitleView_titleTextSize, 16); array.recycle(); initPaint(); } private void initPaint() { mPaint = new Paint(); mPaint.setTextSize(mCustomTextSize); mPaint.setColor(mCustomTextColor); // 文字的骨骼 mRect = new Rect(); mPaint.getTextBounds(mCustomText, 0, mCustomText.length(), mRect); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 画背景 mPaint.setColor(Color.YELLOW); canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); mPaint.setColor(mCustomTextColor); canvas.drawText(mCustomText, getWidth() / 2 - mRect.width() /2, getHeight() / 2 + mRect.height() / 2, mPaint); }}
运行效果:
d) 当我们将布局文件中的宽和高为明确的值时,系统测量的结果就是我们设置的结果,如果wrap_content或match_parent时,
系统默认会让宽和高和窗体同宽高,因为系统根本不知道我们的控件的具体大小,我们可以通过重写onMeasure方法
重写之前先了解MeasureSpec的specMode有三种类型:
- EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
- AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
- UNSPECIFIED:表示子布局想要多大就多大,很少使用
e) 测量是比较复杂的,我们来测量下该控件:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width,height; if(widthMode == MeasureSpec.EXACTLY){ // 明确的值或match_parent width = widthSize; }else{ mPaint.setTextSize(mCustomTextSize); mPaint.getTextBounds(mCustomText, 0, mCustomText.length(), mRect); int textWidth = mRect.width(); // 期望的值,左右边距加上文本的宽度 width = getPaddingLeft() + textWidth + getPaddingRight(); } if(heightMode == MeasureSpec.EXACTLY){ height = heightSize; }else{ mPaint.setTextSize(mCustomTextSize); mPaint.getTextBounds(mCustomText, 0, mCustomText.length(), mRect); int textHeight = mRect.height(); // 期望的值,上下边距加上文本的高度 height = getPaddingTop() + textHeight + getPaddingBottom(); } setMeasuredDimension(width, height);}
由于我们让宽度和高度分别是左边距 + 右边距 + 文本宽度 和 上边距 + 下边距 + 文本高度,所以我们布局必须加入padding值:
e) 接下来给该控件加入事件
this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 生成四位随机数 Random random = new Random(); Setset = new HashSet (); while(set.size() < 4){ int randomInt = random.nextInt(10); set.add(randomInt); } StringBuffer sb = new StringBuffer(); for (Integer integer : set) { sb.append("" + integer); } mCustomText = sb.toString(); // 重绘 postInvalidate(); }});
##############################################待续