曦曦老爸 發表於 2024-7-23 15:11:00

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 &lt; 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 &gt; 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 &lt; getChildCount(); i++) {
      View child = getChildAt(i);

      if (x + childWidth &gt; 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]
查看完整版本: Android开发 - onMeasure、onLayout和onDraw方法解析