Android自定义View和ViewGroup

一.定义属性的声明与获取

<?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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值