Android开发 - onMeasure、onLayout和onDraw方法解析
<h1 id="onlayoutonmeasure和ondraw方法介绍">onLayout、onMeasure和onDraw方法介绍</h1><h2 id="onmeasureint-widthmeasurespec-int-heightmeasurespec"><code>onMeasure(int widthMeasureSpec, int heightMeasureSpec)</code></h2>
<ul>
<li>
<p><strong>onMeasure</strong>方法用于测量<strong>View</strong>的大小。在<strong>自定义View</strong>中,我们需要重写这个方法,根据<strong>自定义View.xml视图</strong>的<strong>宽高测量模式(<code>MeasureSpec</code>)</strong>来计算并设置<strong>自定义View</strong>的宽高</p>
<ul>
<li>
<p><strong>MeasureSpec</strong>:测量规范,以<strong>自定义View.xml视图</strong>为规范进行测量。在<strong>Android</strong>开发中,<strong>MeasureSpec</strong>是一个<strong>32位的int值</strong>,用于描述<strong>View</strong>的宽度和高度信息。它由两部分组成:<strong>模式(mode)</strong>和<strong>尺寸(size)</strong>,模式占据高2位,尺寸占据低30位。其中,它有3种模式:</p>
<ol>
<li><strong>EXACTLY</strong>:精确模式,对应于<strong>LayoutParams</strong>中的<strong>match_parent</strong>和具体的数值,表示<strong>父View</strong>希望<strong>子View</strong>的大小应该是一个确切的值</li>
<li><strong>AT_MOST</strong>:最大模式,对应于<strong>LayoutParams</strong>中的<strong>wrap_content</strong>,表示子<strong>View</strong>的大小最多是指定的值,它可以决定自己的大小</li>
<li><strong>UNSPECIFIED</strong>:未指定模式,通常在系统内部使用,表示<strong>父View</strong>没有给<strong>子View</strong>任何限制,<strong>子View</strong>可以设置任何大小</li>
</ol>
<ul>
<li><strong>总的来说,MeasureSpec是Android中测量View大小的一个重要机制,它帮助我们理解和处理View的测量过程</strong></li>
</ul>
</li>
<li>
<p><strong>widthMeasureSpec</strong>、<strong>heightMeasureSpec</strong>:分别对应于<strong>自定义View.xml视图</strong>的宽度和高度信息。在<strong>测量View</strong>的过程中,<strong>父View</strong>会根据自己的尺寸和<strong>子View</strong>的<strong>LayoutParams</strong>,计算出合适的<strong>widthMeasureSpec</strong>和<strong>heightMeasureSpec</strong>,然后通过<strong>onMeasure方法</strong>传递给<strong>子View</strong></p>
</li>
</ul>
</li>
</ul>
<h2 id="onlayoutboolean-changed-int-left-int-top-int-right-int-bottom"><strong><code>onLayout(boolean changed, int left, int top, int right, int bottom)</code></strong></h2>
<ul>
<li><strong>onLayout</strong>方法用于确定<strong>View</strong>位置的属性。在自定义<strong>ViewGroup(视图容器基类:可以理解为它可以调用所有的View)</strong>中,我们需要重写这个方法,根据<strong>子View</strong>的测量宽高来确定它们的位置</li>
</ul>
<h2 id="ondrawcanvas-canvas"><code>onDraw(Canvas canvas)</code></h2>
<ul>
<li><strong>onDraw</strong>方法用于绘制<strong>View</strong>的内容。在自定义<strong>View</strong>中,我们需要重写这个方法,利用<strong>Canvas</strong>进行绘制操作,如绘制<strong>形状</strong>、<strong>文本</strong>、<strong>图片</strong>等</li>
</ul>
<h1 id="自定义view案例">自定义View案例</h1>
<ul>
<li><strong>下面我们将通过一个简单的自定义View案例来演示使用这三个方法绘制一个带有边框的圆形</strong></li>
</ul>
<h2 id="创建circleview类">创建CircleView类</h2>
<ul>
<li>
<p>首先,创建一个名为<strong>CircleView</strong>的类,继承自<strong>View</strong>,并实现构造方法</p>
<pre><code class="language-java">public class CircleView extends View {
public CircleView(Context context) {
super(context);
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
</code></pre>
</li>
</ul>
<h2 id="重写onmeasure方法">重写onMeasure方法</h2>
<ul>
<li>
<p>在<strong>CircleView</strong>类中,重写<strong>onMeasure</strong>方法,根据<strong>MeasureSpec</strong>来计算并设置<strong>View</strong>的宽高。这里我们假设圆形的半径为<strong>100dp</strong></p>
<pre><code class="language-java">@Override
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 desiredSize = (int) (100 * getResources().getDisplayMetrics().density);
int width = measureDimension(desiredSize, widthMode, widthSize);
int height = measureDimension(desiredSize, heightMode, heightSize);
setMeasuredDimension(width, height);
}
private int measureDimension(int desiredSize, int mode, int size) {
int result;
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else if (mode == MeasureSpec.AT_MOST) {
result = Math.min(desiredSize, size);
} else {
result = desiredSize;
}
return result;
}
</code></pre>
</li>
</ul>
<h2 id="重写ondraw方法">重写onDraw方法</h2>
<ul>
<li>
<p>在<strong>CircleView</strong>类中,重写<strong>onDraw</strong>方法,使用<strong>Canvas</strong>绘制圆形和边框</p>
<pre><code class="language-java">@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int radius = Math.min(width, height) / 2;
Paint paint = new Paint();
paint.setAntiAlias(true);
// 绘制圆形
paint.setColor(Color.BLUE);
canvas.drawCircle(width / 2, height / 2, radius, paint);
// 绘制边框
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
canvas.drawCircle(width / 2, height / 2, radius - 2.5f, paint);
}
</code></pre>
</li>
</ul>
<h2 id="小结">小结</h2>
<ul>
<li>我们已经完成了一个简单的<strong>自定义View - CircleView</strong>。在<strong>布局文件xml</strong>中使用这个<strong>自定义View</strong>,就可以看到一个带有边框的蓝色圆形。通过这个案例可以看到<strong>onMeasure</strong>和<strong>onDraw</strong>这两个方法在<strong>自定义View</strong>中的重要作用。<strong>onMeasure</strong>方法用于测量<strong>View</strong>的大小,<strong>onDraw</strong>方法用于绘制<strong>View</strong>的内容,而<strong>onLayout</strong>方法在此例中并未涉及,因为我们的<strong>CircleView</strong>直接继承自<strong>View</strong>,没有<strong>子View</strong>的布局需求。<strong>但如果我们需要自定义一个ViewGroup,那么onLayout方法将会用于确定子View的位置</strong></li>
</ul>
<h1 id="自定义viewgroup案例">自定义ViewGroup案例</h1>
<ul>
<li>演示<strong>onLayout</strong>方法的使用中将创建一个名为<strong>CustomLayout</strong>的<strong>自定义ViewGroup</strong>,它将简单地将<strong>子View</strong>按照从左到右、从上到下的顺序排列</li>
</ul>
<h2 id="创建customlayout类">创建CustomLayout类</h2>
<ul>
<li>
<p>创建一个名为<strong>CustomLayout</strong>的类,继承自<strong>ViewGroup</strong>,并实现构造方法</p>
<pre><code class="language-java">public class CustomLayout extends ViewGroup {
public CustomLayout(Context context) {
super(context);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
</code></pre>
</li>
</ul>
<h2 id="重写onmeasure方法-1">重写onMeasure方法</h2>
<ul>
<li>
<p>在<strong>CustomLayout类</strong>中,重写<strong>onMeasure方法</strong>,根据<strong>MeasureSpec</strong>来计算并设置<strong>ViewGroup</strong>的宽高。这里我们假设每个<strong>子View</strong>的宽高为<strong>100dp</strong>,水平间距和垂直间距均为<strong>20dp</strong></p>
<pre><code class="language-java">@Override
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);
// 定义子View的宽高和水平、垂直间距
int childWidth = (int) (100 * getResources().getDisplayMetrics().density);
int childHeight = (int) (100 * getResources().getDisplayMetrics().density);
int horizontalSpacing = (int) (20 * getResources().getDisplayMetrics().density);
int verticalSpacing = (int) (20 * getResources().getDisplayMetrics().density);
// 初始化ViewGroup的宽高和当前行的宽高
int width = 0; // ViewGroup的宽
int height = 0; // ViewGroup的高
int rowWidth = 0; //当前行的宽
int rowHeight = childHeight; //当前行的高
// 遍历所有子View
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
// 测量子View的大小
measureChild(child, MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
// 更新当前行的宽度
rowWidth += childWidth + horizontalSpacing; // rowWidth = childWidth + horizontalSpacing + rowWidth
// 检查当前行宽度是否超过ViewGroup的宽度
if (rowWidth > widthSize) {
// 更新ViewGroup的宽度
width = Math.max(width, rowWidth - horizontalSpacing);
// 累加高度
height += rowHeight + verticalSpacing; // height = rowHeight + verticalSpacing + height
// 重置当前行的宽度
rowWidth = childWidth + horizontalSpacing;
}
}
// 更新ViewGroup的宽度和高度
width = Math.max(width, rowWidth - horizontalSpacing);
height += rowHeight; // height = rowHeight + height
// 设置ViewGroup的测量宽高
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width, heightMode == MeasureSpec.EXACTLY ? heightSize : height);
}
</code></pre>
</li>
<li>
<p>在这段代码中,先获取宽度和高度的测量模式和尺寸。然后定义<strong>子View</strong>的宽高和水平、垂直间距,并初始化<strong>ViewGroup</strong>的宽高和当前行的宽高。接着遍历所有的<strong>子View</strong>,测量<strong>子View</strong>的大小,并更新当前行的宽度。检查当前行宽度是否超过<strong>ViewGroup</strong>的宽度,如果超过就更新<strong>ViewGroup</strong>的宽度并累加高度,并重置当前行的宽度。最后更新<strong>ViewGroup</strong>的宽度和高度,并设置<strong>ViewGroup</strong>的测量宽高</p>
</li>
</ul>
<h2 id="重写onlayout方法">重写onLayout方法</h2>
<ul>
<li>
<p>在<strong>CustomLayout类</strong>中,重写<strong>onLayout方法</strong>,根据<strong>子View</strong>的测量宽高来确定它们的位置</p>
<pre><code class="language-java">@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = getWidth();
int childWidth = (int) (100 * getResources().getDisplayMetrics().density);
int childHeight = (int) (100 * getResources().getDisplayMetrics().density);
int horizontalSpacing = (int) (20 * getResources().getDisplayMetrics().density);
int verticalSpacing = (int) (20 * getResources().getDisplayMetrics().density);
int x = 0;
int y = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (x + childWidth > width) {
x = 0;
y += childHeight + verticalSpacing;
}
child.layout(x, y, x + childWidth, y + childHeight);
x += childWidth + horizontalSpacing; // x = childWidth + horizontalSpacing + x
}
}
</code></pre>
</li>
</ul>
<h2 id="小结-1">小结</h2>
<ul>
<li>以上完成了一个简单的<strong>自定义ViewGroup - CustomLayout</strong>。在布局文件中使用这个<strong>自定义ViewGroup</strong>,然后添加多个<strong>子View</strong>,就可以看到它们按照从左到右、从上到下的顺序排列。通过这个案例我们可以看到<strong>onLayout方法</strong>在<strong>自定义ViewGroup</strong>中的重要作用。它用于确定<strong>子View</strong>的位置,根据<strong>子View</strong>的测量宽高来进行布局。在实际开发中,我们可以根据需求自定义不同的布局方式,实现各种复杂的界面效果</li>
</ul>
<h1 id="总结">总结</h1>
<ul>
<li>通过本文了解了<strong>onLayout</strong>、<strong>onMeasure</strong>和<strong>onDraw</strong>这三个方法在<strong>自定义View</strong>和<strong>自定义ViewGroup</strong>中的作用和用法。<strong>onMeasure</strong>方法用于测量<strong>View</strong>的大小,<strong>onDraw</strong>方法用于绘制<strong>View</strong>的内容,<strong>onLayout</strong>方法用于确定<strong>子View</strong>的位置。这三个方法对于实现<strong>自定义View</strong>和<strong>自定义ViewGroup</strong>至关重要,有助于我们在实际开发中更好地满足设计需求,提高界面的交互性和美观性</li>
</ul><br><br>
来源:https://www.cnblogs.com/ajunjava/p/18318483
頁:
[1]