Android 自定义View之处理wrap_content,padding问题分析

本文详细探讨了在Android自定义View中处理wrap_content和padding的问题。通过示例展示了一个自定义的MyCircleView,指出在wrap_content情况下宽度与预期不符,并分析了原因。解决方案包括重写onMeasure()方法来正确处理wrap_content,以及在onDraw()中考虑padding影响以确保其生效。最后,文章提到了处理wrap_content时判断条件的严谨性问题。

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

一,写在前面         

我们知道自定义控件有多种实现方式,1:继承View;2:继承ViewGroup;3:继承具体的容器控件(如:LinearLayout);4:继承一个特定的View(如:TextView等)。

        今天主要是演示第一种情况,并列出处理继承View的自定义控件需要注意的一些问题。接下来演示一个简单的Demo,自定义一个MyCircleView,展示一个圆。后面会提出需要注意的问题,并提出了解决方案。

二,示例展示

下面直接贴代码了,如下:

xml布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textColor="#fff"
        android:background="#00f"
        android:text="下面绘制是我绘制的圆1" />
    
   <com.my.mycircleview.view.MyCircleView 
        android:id="@+id/mcc"
        android:layout_width="match_content"
        android:layout_height="100dp"
        android:layout_below="@id/tv"/>
</RelativeLayout>
自定义的MyCircleView:

public class MyCircleView extends View {
	
	private Paint paint = new Paint();

	public MyCircleView(Context context) {
		super(context);
	}

	public MyCircleView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MyCircleView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		
		//获取控件circleView宽高
		int width = getWidth();
		int height = getHeight();
		
		//圆心坐标
		int cx = width / 2;
		int cy = height / 2;
		
		//取两者最小值作为圆的直径
		int minSize = Math.min(width, height);
		
		canvas.drawColor(Color.WHITE);//设置画布颜色
		paint.setColor(Color.RED);//设置画笔颜色
		paint.setStyle(Paint.Style.STROKE);//设置圆为空心
		paint.setStrokeWidth(3.0f);//设置线宽
		
		//cx,cy坐标点是相对于MyCircleView的左上角顶点坐标
		canvas.drawCircle(cx, cy, minSize / 2, paint);
	}
}
运行程序,展示结果如图1:

图1                                                                                                                             图2

           

        重写onDraw方法,在画布中用画笔画圆,代码都有注释,应该比较清楚。

        继续尝试,将match_parent改为wrap_content,添加margin_top,添加padding,布局文件修改后如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textColor="#fff"
        android:background="#00f"
        android:text="下面绘制是我绘制的圆1" />
    
    <com.my.mycircleview.view.MyCircleView 
        android:id="@+id/mcc"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_below="@id/tv"
        android:layout_marginTop="20dp"
        android:paddingTop="20dp"/>
</RelativeLayout>		

        运行之后,结果上图2所示。我们发现,外边距20dp产生了效果,padding没有效果,wrap_content的效果居然和match_parent一样,宽度都是占据整个屏幕宽度(画笔的颜色设置为white,故白色区域是MyCircleView的宽高范围)。下面,将针对wrap_content和padding提出解决的方案。

三,处理wrap_content

       先处理wrap_content,为什么wrap_content的效果和match_parent一样呢?简单说:在View类中,当该View的布局宽高值为wrap_content,或match_parent时,该View测量的最终大小就是MeasureSpec中的测量大小-->SpecSize。因此,在自定义View时,需要重写onMeasure(w,h)用来处理wrap_content的情况,然后调用setMeasuredDimession(w,h)完成测量。若需要从源码角度了解该原理,可以先全面了解下onMeasure,就比较好理解这里的结论了。见文章Android自定义控件之测量onMeasure

      下面,我们设定:当布局宽为wrap_content时,设置specSize为400px;当布局高为wrap_content时,设置specSize为200px。于是重写onMeasure(w,h)代码如下:

	//两个参数是父View给的测量建议值MeasureSpec,代码执行到onMeasure(w,h),说明MyCircleView的measure(w,h)在执行中
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		
		int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);//宽的测量大小,模式
		int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
		
		int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);//高的测量大小,模式
		int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
		
		int w = widthSpecSize;   //定义测量宽,高(不包含测量模式),并设置默认值,查看View#getDefaultSize可知
		int h = heightSpecSize;
		
		//处理wrap_content的几种特殊情况
		if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
			w = 400;  //单位是px
			h = 200;
		} else if (widthSpecMode == MeasureSpec.AT_MOST) {
			//只要宽度布局参数为wrap_content, 宽度给固定值200dp(处理方式不一,按照需求来)
			w = 400;        
			//按照View处理的方法,查看View#getDefaultSize可知
			h = heightSpecSize;   
		} else if (heightSpecMode == MeasureSpec.AT_MOST) {
			w = widthSpecSize;
			h = 200;
		}
		//给两个字段设置值,完成最终测量
		setMeasuredDimension(w, h);
	}

        解释一下上面代码:当布局宽高为wrap_content时,它的测量模式specMode必然为AT_MOST。于是取出宽高的测量模式进行判断,如果布局宽高的不是wrap_content时,就按照View$onMeasure(w,h)中方式处理,也就是用父view给的建议测量大小specSize,作为最终测量大小值。重写onMeasure(w,h)后执行程序,展示结果如下图3:

                                                     图3                                                                       图4

            

        由图3可知:当布局宽度为wrap_content时,MyCircleView的宽度不再是整个屏幕,而是400px。其他几种情况,感兴趣哥们可以自己去验证过,布局宽高wrap_content时效果都是我们希望的。

        注意:上面的处理方式可能存在问题,因为widthSpecMode == MeasureSpec.AT_MOST是布局参数宽度为wrap_content的必要不充分条件,具体原因见文章(Android自定义控件之测量onMeasure)的博主自我评论,算是我的一个小纠正吧。判断getLayoutParams().width的值为wrap_content情况,处理wrap_content更严密。但在研读一些书籍时,作者使用上面的方式进行判断,小王同学大胆认为不严密吧。(*^__^*) 

四,处理padding

         接下来处理padding,要使padding起作用,需要在重写onDraw(canvas)方法时,对padding的情况进行处理。这里主要是:修改圆心位置,修改圆的半径。修正后,onDraw(canvas)代码如下:

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		
		//获取控件circleView宽高
		int width = getWidth();
		int height = getHeight();
		
		//圆心坐标
		int cx = width / 2;
		int cy = height / 2;
		
		//取两者最小值作为圆的直径
		int minSize = Math.min(width, height);
		
		canvas.drawColor(Color.WHITE);//设置画布颜色
		paint.setColor(Color.RED);//设置画笔颜色
		paint.setStyle(Paint.Style.STROKE);//设置圆为空心
		paint.setStrokeWidth(3.0f);//设置线宽
		
		//-----开始处理padding
		//获取padding的值
		int paddingLeft = getPaddingLeft();
		int paddingRight = getPaddingRight();
		int paddingTop = getPaddingTop();
		int paddingBottom = getPaddingBottom();
		
		//计算MyCircleView减去padding后的可用宽高
		int canUsedWidth = width - paddingLeft - paddingRight;
		int canUsedHeight = height - paddingTop - paddingBottom;
		
		//圆心坐标
		cx = canUsedWidth / 2 + paddingLeft;
		cy = canUsedHeight / 2 + paddingTop;
		//圆的直径
		minSize = Math.min(canUsedWidth, canUsedHeight);
		//cx,cy坐标点是相对于MyCircleView的左上角顶点坐标
		canvas.drawCircle(cx, cy, minSize / 2, paint);
	}

运行程序后,展示效果如上面图4,圆上面有了一个MyCircleView的内填充paddingTop,大小为20dp,这个正是我们需要的。上述代码基本添加有注释,这里另外附加一个图展示加入padding后,圆心的位置计算示意图,如下:



到这里,相信大家对完成一个比较规范的继承View的自定义控件有所认识了,嘿嘿(*^__^*) 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值