自定义view,viewgroup

本文详细介绍了如何自定义View和ViewGroup,包括构造方法、测量大小、确定位置、绘制内容以及移动方法。同时提供了关于ViewGroup的重写方法如onMeasure、onLayout和onTouchEvent的使用案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


自定义view:

一般系统控件满足不了我们的需求时,这时候要考虑要使用自定义控件了。由于自定义控件也是一个对象,把这个自定义控件想象成一个简单的创建对象
的过程就可以了,例如下面我们要创建一个Person对象,

Person p = new Person("张三","22","男");
或者
Person p = new Person();
p.setName("张三");
p.setAge("22");
p.setGender("男");

首先肯定要new出来这个对象,其次要给对象必要的初始化操作。完成这两部基本一个对象就可以使用了。那么自定义对象也是
一样的,首先要new出来,其次做必要的设置.于是步骤基本如下:

调用构造new 出对象:我们自定义控件一般在布局文件中引用,那么控件的创建会由系统来替我们完成。
初始化设置:
测量出view对象的大小:对应的方法为onMeasure
确定view的位置:      对应的方法为onLayout ,view的位置是由父view来决定的。
绘制View的内容:      对应的方法为onDraw

那么一个view显示在屏幕上 依次经过的过程大致就是:
1:首先创建出来MyView(Context context, AttributeSet attrs)。
2:调用测量方法测量 view的大小(如果需要onMeasure)
3:定位view的位置(如果需要onLayout)
4:绘制view的内容(如果需要onDraw)
5:绘制内容时一般结合手势事件等做相应业务处理。(如果需要onTouchEvent)
-------------------------------------------------------------------------------------------

一、继承 View类,并复写构造方法。构造方法的作用 说明如下。

	//在代码中创建此自定义控件时  调用此方法。
	public MyView(Context context){
		super(context);
	
	}
	/**当我们在布局文件中使用系统控件时,系统会给我们自动创建,调用的就是此构造方法。由此,当我们自定义控件时,
	 * 必须复写此构造方法,否则运行时会报错。
	 */	
	public MyView(Context context,AttributeSet attrs){
		super(context);
	//
	
	}
	// 和第二个构造方法一样,只不过多了一个样式参数,因此此构造用来给设置样式(如果需要的话)。
	public MyView(Context context,AttributeSet attrs,int Style){
		super(context);
	
	}

二、测量大小:

onMeasure:这个方法是测量大小的回调方法

//从名字可以看出是“设置测量数值”。
//设置view的宽高,单位都是像素值。
                setMeasuredDimension()

	@Override
	/**
	 * 测量尺寸时的回调方法 
	 */
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		
		/**
		 * 设置当前view的大小
		 * width  :view的宽度
		 * height :view的高度   (单位:像素)
		 */
		setMeasuredDimension(backgroundBitmap.getWidth(),backgroundBitmap.getHeight());
	}

三、确定位置:onLayout:自定义View的时候作用不大。

四、绘制内容onTouchEvent 和 onDraw:

onDraw(Canvas canvas){
//画线,圆,背景图片,等等。。。。。
}


onTouchEvent(){
//对触摸事件解析,并根据业务做相应处理
}

五、关于控件的移动。

1、改变坐标(横坐标或者纵坐标),并通过刷新坐标值来达到移动的目的。例如
onDraw(Canvas canvas){
canvas.drawBitmap(slideBtn, left, top, paint);//left 距离原点横坐标,top纵坐标
//根据业务刷新
invalidate();
}


自定义ViewGroup:


1、继承viewGroup:这时会强制让复写构造方法,这里复写带2个参数的public MyLayout(Context context, AttributeSet attrs) {}构造

2.重写onMeasure:在自定义view时,我们一般调用setMeasuredDimension来设置我们view的大小。但作为一个viewgroup,只计算自己的大小还不够,还
得计算子View的大小。那么怎么怎么计算子view大小呢?:一次获取子view,每个子view测量即可。

/**
	 * @param widthMeasureSpec
	 * @param heightMeasureSpec
	 * 
	 * 1、View的布局大小由父View和子View共同决定。
	 * 
	 * 2、View的测量最终调用的是View的setMeasuredDimension方法,所以我们可以通过
	 *    setMeasuredDimension设定死值来完成测量。但是一个好的自定义View应该会根据子视图的
	 *    measureSpec来设置mMeasuredWidth和mMeasuredHeight的大小.
	 * 3、widthMeasureSpec  heightMeasureSpec 这两个参数表示父元素传入的
	 * 4、测量从根view开始依次遍历整个view树结构。每个view都会经过测量。
	 * 5、我们这里先测量子view,最后测量当前viewgroup。
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		//先测量所有的子view这里获取父viewgroup提供的大小 ,测试运行结果例如:viewgroup:1073742524-1073742958
		System.out.println("viewgroup:"+widthMeasureSpec+"-"+heightMeasureSpec);
		
		int sizeW = MeasureSpec.getSize(widthMeasureSpec);
		int sizeH = MeasureSpec.getSize(heightMeasureSpec);
		//
		int modeW = MeasureSpec.getMode(widthMeasureSpec);
		int modeH = MeasureSpec.getMode(heightMeasureSpec);
		
		//测试运行结果例如:getSize:700-modeW:1073741824
		System.out.println("getSize:"+sizeW+"-modeW:"+modeW);
		//一、因此 到这里基本上可以理解到 MeasureSpec.getSize(widthMeasureSpec); 实际上得到的是当前view的大小。
		//二、由于是viewgroup,当然也是可供分配的空间大小.如果其下的子view超过这个值,就摆放不下了。
		
		
		for(int i=0;i<this.getChildCount();i++){
			View child = this.getChildAt(i);
			
			
			int tempSetMode = 0;
			switch (modeW) {
			case MeasureSpec.AT_MOST:
				tempSetMode = MeasureSpec.AT_MOST;
				System.out.println("MeasureSpec.AT_MOST");
				break;
			case MeasureSpec.UNSPECIFIED:
				tempSetMode = MeasureSpec.UNSPECIFIED;
				System.out.println("MeasureSpec.UNSPECIFIED");
				break;
			case MeasureSpec.EXACTLY:
				tempSetMode = MeasureSpec.EXACTLY;
				System.out.println("MeasureSpec.EXACTLY");
				break;
			}
			
			
			 //在测量的时候有三种规则如下:
			//MeasureSpec.UNSPECIFIED://表示view想要多大就是多大。
			//MeasureSpec.AT_MOST://表示view想要的大小不能超过某个值。(  wrap) 
			//MeasureSpec.EXACTLY://表示view就是只能是这么大     ( match_fill  固定大小(100dp))
			
			//一、指定固定大小来测量  
			int myWidth = MeasureSpec.makeMeasureSpec(100,tempSetMode);//这里的参数表示当前view想要按照这种模式测量。
			int myHeight = MeasureSpec.makeMeasureSpec(100,tempSetMode);
			//child.measure(myWidth, myHeight);
			//二、指定固定大小来测量
			//measureChild(child, myWidth, myHeight);
			
			//三、将父viewgroup的大小作为参数来测量子view的大小。我理解意思实际上就是 设置子view的大小为父元素的大小。
			//运用场景:例如要定义一个类似viewpager效果,那么在测量子view时可以这样设置。
			//child.measure(widthMeasureSpec, heightMeasureSpec);  
			
			//四、子view随意大小(根据布局中的设置来)
			child.measure(MeasureSpec.UNSPECIFIED,MeasureSpec.UNSPECIFIED);  
			
			//final:这集中子view的测量方式都可以,可以根绝业务需要来变动。总结起来就2个:
			//child.measure(xxx, xxx);
			//measureChild(child, xxx, xxx);
		}

		
		//这句意思是调用父类的来测量,如果不测量,那么下面必须调用setMeasuredDimension测量,否则报错。
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		
		//我们可以根据业务需要来指定当前viewgroup大小.如果不指定 当然就是setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);了
		//setMeasuredDimension(480, 800);
	   
	    
	}


3、重写onLayout()方法:为每一个子View确定位置。自定义ViewGroup的主要业务在与onLayout。就是是怎么排版当前布局。

/**
	 *	若为true说明布局发生了变化。l,t,r,b :当前viewgroup在其父view中的位置(基本用不到)。
	 * 定位子view 的位置
	 */
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		
		
		for(int i =0;i<this.getChildCount();i++){
			View view = getChildAt(i);
			
		
			//一、getWidth,getHeight :得到当前控件实际的大小,只有当onlayout方法执行完后才确定这个大小。
			//二、getMeasuredWidth,getMeasuredHeight  :得到当前控件的测量的大小,当onmeasure方法执行完后就可以得到。
			//三、从源码得知getWidth是mLeft-mRight后得到的,而mLeft,mRight是在onLayout执行后确定的。因此,getWidth是在onLayout执行后才确定。
			//     同样getMeasuredWidth从源码得知是一个&运算后的结果(mMeasuredWidth&MEASURED_SIZE_MASK),而mMeasuredWidth是在onMeasure
			//       执行后确定的,因此getMeasuredWidth在onMeasure执行后就可以有值。
			//四、一般情况下getWidth和getMeasureWidth是一样的,getMeasureWidth是当前view自身想要多大,而getWidth是父view给指定的大小
			System.out.println("viewgroup:getWidth:"+getWidth()+   
					"getHeight:"+getHeight()+
					"getMeasureW:"+getMeasuredWidth()+
					"getMeasureH:"+getMeasuredHeight()
					);
			System.out.println("view:getWidth:"+view.getWidth()+
					"getHeight:"+view.getHeight()+
					"getMeasureW:"+view.getMeasuredWidth()+
					"getMeasureH:"+view.getMeasuredHeight()
					);
			
			
			//根据业务需要 确定控件的位置
			//父view会根据子view的需求和自身的情况来确定 子view的位置。
			view.layout(i*30, i*30, i*30+view.getMeasuredWidth(), i*30+view.getMeasuredHeight());

		}
	}

4、重写onTouchEvent方法,监听touch事件,并用scrollTo()或scrollBy()方法移动view(根据业务需要).

//	手势识别工具类
	GestureDetector g ;
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		super.onTouchEvent(event);
		//g.onTouchEvent(event);
		return true;
	}

OnGestureListener listener = new OnGestureListener() {
		/**
		 * 当单个点击抬起的时候触发这个方法。比如按下了好几个手指,只要有一个手指抬起来就会触发这个一个方法。
		 */
		@Override
		public boolean onSingleTapUp(MotionEvent e) {
			// TODO Auto-generated method stub
			return false;
		}
		/**
		 * 当有手指点到屏幕上的时候  会回调这个方法
		 */
		@Override
		public void onShowPress(MotionEvent e) {
			// TODO Auto-generated method stub
			
		}
		/**
		 * 当手指在屏幕上划动的时候 回调这个方法。
		 * e1:
		 * e2:
		 * distanceX:
		 * distanceY:
		 */
		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
				float distanceY) {
			//重要点:
			//1、将当前view移动到某个点上。这个点由x,y制定。
			//2、
			//MyLayout.this.scrollTo(x, y);
			
			
			//1、scrollBy移动的是viewgroup里面的子元素
			//2、distanceX/distanceY:  x/y方向上移动的距离
			//3、distanceX/distanceY:   手指向左移动:为正值    手指向右移动:为负值
			//4、scrollBy---调用的是-->scrollTo(mScrollX + x, mScrollY + y);
			MyLayout.this.scrollBy((int)distanceX, (int)distanceY);//x,y方向上都移动
			MyLayout.this.scrollBy((int)distanceX, 0);//只有 x水平方向移动.
			MyLayout.this.scrollBy(0, (int)distanceY);//只有 y竖直方向移动。
			
			return false;
		}
		/**
		 * 在屏幕上长按的时候回调
		 */
		@Override
		public void onLongPress(MotionEvent e) {
			// TODO Auto-generated method stub
			
		}
		/**
		 * 当快速划动的时候触发这个方法。
		 * velocityX>0 :快速向右滑动
		 * velocityX<0 :快速向左滑动
		 */
		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {
			
			return false;
		}
		
		@Override
		public boolean onDown(MotionEvent e) {
			// TODO Auto-generated method stub
			return false;
		}
	};


int startX;
int endX;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
gesture.onTouchEvent(event);

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
endX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
endX = (int) event.getX();

break;
case MotionEvent.ACTION_UP:

滑动时:view向右滑动   则为正值,view向左滑动则为负值。
int directionAndDistance = endX-startX ;


更多关于自定义viewgroup使用的Draghelper,请参考:

Android ViewDragHelper完全解析 自定义ViewGroup神器


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值