自定义View绘制心得(自定义view和自定义viewGroup)

自定义View主要分为两大部分:

第一部分是view的绘制:

大体步骤

1.测量大小 


2.放置位置


3绘制

从view 说起一套完整的自定义view流程: 

1.onMeasure: 重要的类 Measuresprc 类 MODE 和SIZE  当测量属性的参数是WRAP_CONTEXT 时这些参数将会起作用

测量主要和父容器和自身的Layoutparms有关。

继承自view测量时当属性参数是WRAP_context时 需要重写onMeasure() 通过Measuresprc类的Mode和size,判断自行填写大小(覆写父类setMeasuredDimension(,))

//2.onLayout: 这个方法会确定被放置的位置, 获取父布局layoutparams 或者layout()方法 这个方法不是必须重写的

3.onDraw : 就是绘制了

4.onTouchEvent: 回调事件

示例代码(一个按钮代码):

@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

		if (mSwitchBackground != null) {
			int width = mSwitchBackground.getWidth();
			int height = mSwitchBackground.getHeight();

			setMeasuredDimension(width, height);
		} else {
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		}
	}

这里选择的大小是图片的大小!

ondraw方法,和onTouchEvent方法是自定义view的核心与关键:

	@Override
	protected void onDraw(Canvas canvas) {

		// 绘制背景的显示
		if (mSwitchBackground != null) {

			int left = 0;
			int top = 0;
			canvas.drawBitmap(mSwitchBackground, left, top, mPaint);
		}

		if (mSwitchSlide == null) {
			return;
		}

		int slideWidth = mSwitchSlide.getWidth();// 滑块的宽度
		int switchWidth = mSwitchBackground.getWidth();

		switch (mState) {
		case STATE_DOWN:
		case STATE_MOVE:
			// 当按下的时候
			if (!isOpened) {
				// 如果滑块是关闭的
				// 点击滑块的左侧,滑块不动
				if (mCurrentX < slideWidth / 2f) {
					// 绘制在左侧
					canvas.drawBitmap(mSwitchSlide, 0, 0, mPaint);
				} else {
					// 点击滑块的右侧,滑块的中线和按下的x坐标对齐
					float left = mCurrentX - slideWidth / 2f;
					float maxLeft = switchWidth - slideWidth;
					if (left > maxLeft) {
						left = maxLeft;
					}
					canvas.drawBitmap(mSwitchSlide, left, 0, mPaint);
				}
			} else {
				// 如果滑块是打开的
				float middle = switchWidth - slideWidth / 2f;
				if (mCurrentX > middle) {
					// 绘制为打开
					canvas.drawBitmap(mSwitchSlide, switchWidth - slideWidth,
							0, mPaint);
				} else {
					float left = mCurrentX - slideWidth / 2f;
					if (left < 0) {
						left = 0;
					}
					canvas.drawBitmap(mSwitchSlide, left, 0, mPaint);
				}
			}
			break;
		case STATE_UP:
		case STATE_NONE:
			if (!isOpened) {
				canvas.drawBitmap(mSwitchSlide, 0, 0, mPaint);
			} else {
				canvas.drawBitmap(mSwitchSlide, switchWidth - slideWidth, 0,
						mPaint);
			}
			break;
		default:
			break;
		}

	}

事件方法通知重绘时,做了标记处理:

	@Override
	public boolean onTouchEvent(MotionEvent event) {

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mState = STATE_DOWN;

			mCurrentX = event.getX();

			invalidate();// 触发刷新-->主线程调用
			// postInvalidate();// 触发刷新--->子线程中调用

			break;
		case MotionEvent.ACTION_MOVE:
			mState = STATE_MOVE;

			mCurrentX = event.getX();

			invalidate();
			break;
		case MotionEvent.ACTION_UP:
			mState = STATE_UP;

			mCurrentX = event.getX();

			// 判断状态改变--> isOpened
			int switchWidth = mSwitchBackground.getWidth();

			if (switchWidth / 2f > mCurrentX && isOpened) {
				// 关闭状态
				isOpened = false;

				if (mListener != null) {
					mListener.onSwitchChanged(isOpened);
				}

			} else if (switchWidth / 2f <= mCurrentX && !isOpened) {
				isOpened = true;

				if (mListener != null) {
					mListener.onSwitchChanged(isOpened);
				}
			}
			invalidate();
			break;
		default:
			break;
		}

		// 消费touch事件
		return true;
	}

注意通知重绘方法!


从viewGroup:

1.onMeasure 和 onLayout 其实没有真正的实现内容只不过是对下面的子view 进行了计算和位置的摆放

2.onMeasure : 会回调子类的方法 

而继承自ViewGroup类测量时。当属则需要通测量过子View大小从而重新决定自己的大小。(通常宽高可能是子view的和)。注意:只能获取的是测量值Measureheight.

放置

3.onLayout:  viewGroup放置则先onLayout()方法对其中的子布局调用Layout()进行依次放置(getcount(),和getchildAt()获取子控件)

4.onDraw: 在viewGroup中这个方法如果不用 绘制背景颜色可能都不会使用。

示例代码:

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        int widthMeasureSpeccode = MeasureSpec.getMode(widthMeasureSpec);
        int widthMeasureSpecsize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMeasureSpeccode = MeasureSpec.getMode(heightMeasureSpec);
        int heightMeasureSpecsize = MeasureSpec.getSize(heightMeasureSpec);
        if (count == 0) {
            setMeasuredDimension(0, 0);
        } else if (widthMeasureSpeccode == MeasureSpec.AT_MOST && heightMeasureSpeccode == MeasureSpec.AT_MOST) {
            final   View childview0 = getChildAt(0);
            //只能获取到测量值
            int childheigt = childview0.getMeasuredHeight();
            int childwidth = childview0.getMeasuredWidth();
                setMeasuredDimension(childwidth*count,childheigt);
        }else if(widthMeasureSpeccode == MeasureSpec.AT_MOST){
            View childview =getChildAt(0);
            int measurewidth=childview.getMeasuredWidth()*count;
            setMeasuredDimension(measurewidth,heightMeasureSpecsize);
        }else if(heightMeasureSpeccode == MeasureSpec.AT_MOST){
            View childview =getChildAt(0);
            int measuredHeight=childview.getMeasuredHeight();
            setMeasuredDimension(widthMeasureSpecsize,measuredHeight);
        }
    }


onlayout方法:
   @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int clidrencount = getChildCount();
        int childrenleft = 0;
        View childview1=getChildAt(0);
        int chileviewheight1=childview1.getMeasuredHeight();
        int chileviewwidth1=childview1.getMeasuredWidth();
        for (int c = 0; c < clidrencount; c++) {
            View childrenView = getChildAt(c);
            childrenView.layout(childrenleft, 0, childrenleft + chileviewwidth1,chileviewheight1);
            childrenleft = childrenleft + chileviewwidth1;
        }
    }
//需要特别注意的是,需要调用每一个子view的layout方法,这里我们直接给他位置放好!


 


ondraw()方法中参提供了一个Canvas,这个Canvas系统提供了许多绘制API,例如drawPoint,drawline,drawPath,drawBitmap,drawshape等

同时提供了方法,这些方法为坐标点绘制计算提供方便,rotate()和traslate()分别对坐标原点进行角度转换和平移。Save,和restore对图层保存和合并,另外对图层的管理通过栈的方式。两个方法,saveLayer(),saveLayerALpha(),restoreTocount(),restore(),对图层操作后决定了后面的操作是在哪个图层上。

viewGroup注意: 

viewGroup可用addview在构造函数中添加子控件或者布局。要注意的是子控件的点击事件可以通过新建一个接口的,在viewGroup中回调接口方法,同时给出一个方法暴露接口。这样调用者自己就可以在用viewGroup的暴露的接口实现自己的逻辑了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值