一.定义属性的声明与获取
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="titleText" format="string" />
<attr name="titleTextColor" format="color" />
<attr name="titleTextSize" format="dimension" />
<declare-styleable name="ChangeColorIconWithText">
<attr name="icon" />
<attr name="color" />
<attr name="text" />
<attr name="text_size" />
</declare-styleable>
</resources>
/**
* 文本
*/
private String mText;
/**
* 文本的颜色
*/
private int mTextColor;
//文本的大小
private int mTextSize;
//图标
private Bitmap mIcon;
// 绘制时控制文本绘制的范围
private Rect mBound;
private Paint mPaint;
//两个参数和一个参数的构造方法都是不包含自定义属性的
public ChangeColorIconWithText(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public ChangeColorIconWithText(Context context)
{
this(context, null);
}
/**
* 获得我自定义的样式属性
*
* @param context
* @param attrs
* @param defStyle
*/
public ChangeColorIconWithText(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
/**
* 获得我们所定义的自定义样式属性
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ChangeColorIconWithText, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.ChangeColorIconWithText_Icon:
BitmapDrawable drawable=(BitmapDrawable)a.getDrawable(attr)
mIcon = drawable.getBitmap();
break;
case R.styleable.ChangeColorIconWithText_Color:
// 默认颜色设置为黑色
mColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.ChangeColorIconWithText_Text:
// 默认
mText = a.getString(atr);
break;
case R.styleable.ChangeColorIconWithText_Size:
// 默认设置为16sp,TypeValue也可以把sp转化为px
mSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
/**
* 获得绘制文本的宽和高
*/
mPaint = new Paint();
mPaint.setTextSize(mSize);
// mPaint.setColor(mColor);
mBound = new Rect();
mPaint.getTextBounds(mText, 0, mText.length(), mBound);
}
二.测量onMeasure
决定自身是多大范围是什么样式
在Android里面测量由两个数字决定
1).测量的模式;
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
2).测量的值;
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;
int height ;
if (widthMode == MeasureSpec.EXACTLY)
{
width = widthSize;
} else
{
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
float textWidth = mBounds.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
if(mode == MeasureSpec.AT_MOST){
width = Math.min(desired,widthSize);
}
}
if (heightMode == MeasureSpec.EXACTLY)
{
height = heightSize;
} else
{
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
float textHeight = mBounds.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
if(mode == MeasureSpec.AT_MOST){
height = Math.min(desired,heightSize);
}
}
setMeasuredDimension(width, height);
}
三.布局onLayout(ViewGroup)
父控件决定子控件显示的位置,单纯的自定义View是没有这个部分的,但是 ViewGroup是必须要的
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
super.onLayout(changed, l, t, r, b);
if(changed){
this.scrollTo(mMenuWidth, 0);
}
}
四.绘制onDraw
空间显示的样子
protected void onDraw(Canvas canvas)
{ //使用canvas的API绘制你喜欢的
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
mPaint.setColor(mTitleTextColor);
canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
}
Canvas.drawXXX,translate,rotate,scale,skew等方法,如果属性发生变化可以用invalidate或者postinvalidate()去重新绘制后者在子线程中使用
五.onTouchEvent与用户交互部分
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE
break;
//多点触控,待补充。。。
case MotionEvent_POINTER_DOWN:
break;
case MotionEvent_POINTER_UP:
break;
}
return super.onTouchEvent(ev);
}
六.onInterceptTouchEvent(ViewGroup)
public boolean disPatchTouchEvent(MotionEvent ev)
用来进行事件的分发,如果事件能够传递给当前的View,那么此方法一定会被调用,Android中所有的点击事件都必须经过这个方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理。返回true表示不继续分发,事件没有被消费,返回false则继续往下分发,如果是ViewGroup则分发给onInterceptTouchEvent进行判断是否拦截该事件,这个方法的返回结果受到当前View的onTouchEvent和下级View的disPatchTouchEvent方法的影响,返回结果表示是否消耗当前事件。
public boolean onTouchEvent(MotionEvent ev)
在diaPatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列当中,当前View无法再次接收到事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
是ViewGroup中才有的方法,View中没有,它的作用是负责事件的拦截,返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent进行处理。返回false则不拦截,继续往下传。这是ViewGroup特有的方法,因为ViewGroup中可能还有子View,而在Android中View中是不能再包含子View的(iOS可以),在上述方法内部被调用,如果当前View拦截了某个事件,那么同一个事件序列中(指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,中间含有不定的ACTION_MOVE事件,最终以ACTION_UP事件结束)此方法不会被再次调用,返回结果表示是否拦截当前事件。
这三个方法可以用如下伪代码表示:
public boolean disPathchTouchEvent(MotionEvent ev){
//consume指代点击事件是否被消耗
boolean consume=false;
//表示当前父布局要拦截该事件
if(onInterceptTouchEvent(MotionEvent ev)){
consume=onTouchEvent(ev);
}else{
//传递给子元素去处理
child.disPatchTouchEvent(ev);
}
return consume;
}