Android 自定义View简单归纳
什么是自定义View
自定义View即继承与View和ViewGroup的自定义控件,可以实现系统控件以外的功能,也可以继承与系统控件,对系统控件进行所需要的修改。我也做了一个简单的自定义TextView,但是并没有完成其OnTouchEvent事件。这是个人学习归纳,如有错误请留言,谢谢大家。
在这里我将自定义View分成五步
第一步创建自定属性
在res文件夹下创建一个attrs文件。然后通过declare-styleable创建一个自定义的控件,写下所需要实现的自定义属性即可。
<resources>
<!--name 最好是你自定义View的名字-->
<declare-styleable name="mTextView">
<!--
name属性名称
format输入格式:
string文字 color颜色 dimension宽高(字体大小) integer数字 reference资源文件
-->
<attr name="text" format="string"/>
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
<attr name="maxLength" format="integer"/>
<!-- background 自定义View都是继承自View , 背景是由View管理的-->
<!-- <attr name="background" format="reference|color"/>-->
<!--枚举-->
<attr name="inputType">
<enum name="number" value="1"/>
<enum name="text" value="2"/>
<enum name="password" value="3"/>
</attr>
</declare-styleable>
</resources>
第二步完成其三个构造方法OnCreat
在完成构造方法前,我们需要创建所需要的参数并对必要参数设置默认值,且必须创建一个画笔Paint。完成构造方法后,需要在构造方法中获取自定义属性的值,建议三个构造方法都依次调用含有3个参数的构造方法,保证了代码的复用性。
在这里讲解一下每一个构造方法什么时候会被调用
一个参数的构造方法将在是在代码中new的时候会被调用的
二个参数的构造方法将在xml文件中添加控件时被调用
三个参数的构造方法则是在需要用到自定义属性的时候被调用
public mTextView(Context context) {
this(context,null);
}
public mTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public mTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.mTextView);
mText = array.getString(R.styleable.mTextView_text);
mTextSize = array.getDimensionPixelSize(R.styleable.mTextView_textSize,SptoPx(mTextSize));//第二个参数为默认值
mTextColor = array.getColor(R.styleable.mTextView_textColor,mTextColor);//第二个参数为默认值
//回收 避免浪费资源及内存溢出
array.recycle();
mPaint = new Paint();
//抗锯齿
mPaint.setAntiAlias(true);
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
// 默认给一个背景
// setBackgroundColor(Color.TRANSPARENT);
}
第三步完成其测量方法OnMeasure
三种测量模式 :
AT_MOST(wrap_content,需要计算所占长宽)
EXACTLY(确定的值,不用计算,包括macth_parent也是一个固定值)
UNSPECIFIED(尽可能大,一般只用于系统组件)
widthMeasureSpec 和 heightMeasureSpec介绍
widthMeasureSpec 和 heightMeasureSpec都是一个int值且都包含32位数字 前2位表示信息模式 后面30位表示其具体值 (系统将这两个信息自动拼接到了一块)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//1.确定的值,这个时候不用计算 获取宽度
int width = MeasureSpec.getSize(widthMeasureSpec);
//2.wrap_content需要计算
if(widthMode == MeasureSpec.AT_MOST)
{
//计算宽度 用画笔测量
Rect bounds = new Rect();
mPaint.getTextBounds(mText,0,mText.length(),bounds);
width = bounds.width()+getPaddingLeft()+getPaddingRight();
}
//1.确定的值,这个时候不用计算 获取高度
int height = MeasureSpec.getSize(heightMeasureSpec);
//2.wrap_content需要计算
if(heightMode == MeasureSpec.AT_MOST)
{
//计算宽度 用画笔测量
Rect bounds = new Rect();
mPaint.getTextBounds(mText,0,mText.length(),bounds);
height = bounds.height()+getPaddingTop()+getPaddingBottom();
}
}
第四步完成其OnDraw绘画方法
这里涉及到了一个绘画位置的问题,x表示的是绘画其实位子,而y并非表示绘画的y轴位置而是表示基线位子baseLine(需要我们自己算)
在这里有一个问题一定要考虑,关于添加了padding以后基线的位子也会随之改变。由于继承与View所以margin则不需要我们修改。
在这里我们还要关注一个问题即如果自定义控件继承与ViewGroup其Ondraw方法在设置背景前不会被调用,因为ViewGroup在内部已经设置了flags使其不会调用
ondraw方法,而在设置background后会调用其ondraw方法。那么怎么解决了,我们可以在其dispatchDraw方法中完成绘画功能。这样我们即使不设置背景依然
可以做绘画。
关于基线问题:这幅图详细介绍了基线和bottom、top的含义
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// x 就是开始的位置 0 y 基线 baseLine 需要运算
//dy 代表的是:高度的一半到 baseLine的距离
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
// top 是一个负值 bottom 是一个正值 top,bttom的值代表是 bottom是baseLine到文字底部的距离(正值)
// 必须要清楚的,可以自己打印就好
int dy = (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
int baseLine = getHeight()/2 + dy+getPaddingTop()/2-getPaddingBottom()/2;
int x = getPaddingLeft();
canvas.drawText(mText,x,baseLine,mPaint);
}
我们需要在OnTouchEvent中做的事情比较多,包括事件的分发和事件的拦截。由于我只是写了一个简单的自定义TextView所以OnTouchEvent中没有做什么处理
/**
* 处理跟用户交互的,手指触摸等等
* @param event 事件分发事件拦截
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// 手指按下
Log.e("TAG","手指按下");
break;
case MotionEvent.ACTION_MOVE:
// 手指移动
Log.e("TAG","手指移动");
break;
case MotionEvent.ACTION_UP:
// 手指抬起
Log.e("TAG","手指抬起");
break;
}
invalidate();
return super.onTouchEvent(event);
}
如果我们的自定义控件是继承于ViewGroup则我们还需要完成其OnLayout的方法。
Github地址: mTextView