Android自定义view详解及Measurepec深入解析
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>理解自定义View的三大流程</li><li>深入解析MeasureSpec</li><ul class="second_class_ul"><li>1. 三种测量模式的含义</li><li>2. MeasureSpec的确定规则</li></ul><li>实现自定义View的关键步骤</li><ul class="second_class_ul"><li>1. 继承View类并重写构造方法</li><li>2. 正确处理测量(重写onMeasure)</li><li>3. 实现绘制(重写onDraw)</li></ul><li>进阶技巧与优化建议</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>理解自定义View的三大流程</h2><p>自定义View的绘制主要围绕三个核心过程展开,它们依次执行,共同决定了View的最终呈现:</p>
<table><thead><tr><th><p>流程阶段</p></th><th><p>核心方法</p></th><th><p>主要职责</p></th></tr></thead><tbody><tr><td><p><strong>测量 (Measure)</strong></p></td><td><p><code>onMeasure(int widthMeasureSpec, int heightMeasureSpec)</code></p></td><td><p>确定View的<strong>测量宽度和高度</strong>。必须在此方法末尾调用<code>setMeasuredDimension()</code>来保存结果。</p></td></tr><tr><td><p><strong>布局 (Layout)</strong></p></td><td><p><code>onLayout(boolean changed, int l, int t, int r, int b)</code></p></td><td><p>确定View<strong>在父容器中的位置</strong>(四个顶点的坐标),对于ViewGroup,还需负责遍历和确定所有子View的位置。</p></td></tr><tr><td><p><strong>绘制 (Draw)</strong></p></td><td><p><code>onDraw(Canvas canvas)</code></p></td><td><p>将View的<strong>视觉内容</strong>绘制到屏幕上。通过<code>Canvas</code>(画布)和<code>Paint</code>(画笔)对象完成具体绘制工作。</p></td></tr></tbody></table>
<p>整个流程的发起者是<code>ViewRootImpl</code>的<code>performTraversals()</code>方法,它会根据情况决定是否执行完整的测量、布局和绘制。</p>
<p class="maodian"></p><h2>深入解析MeasureSpec</h2>
<p><code>MeasureSpec</code>(测量规格)是理解测量流程的钥匙。它是一个32位的int值,<strong>高2位代表测量模式(SpecMode),低30位代表在该模式下的规格大小(SpecSize)</strong>。其设计目的是为了高效地用一个变量同时携带模式和尺寸信息。</p>
<p class="maodian"></p><h3>1. 三种测量模式的含义</h3>
<table><thead><tr><th><p>模式</p></th><th><p>含义</p></th><th><p>常见对应场景</p></th></tr></thead><tbody><tr><td><p><strong>EXACTLY</strong></p></td><td><p><strong>精确模式</strong>:父容器已经为子View确定了一个精确的尺寸。此时子View的尺寸应直接设为<code>SpecSize</code>。</p></td><td><p>在布局中设置了具体数值(如<code>100dp</code>)或<code>match_parent</code>。</p></td></tr><tr><td><p><strong>AT_MOST</strong></p></td><td><p><strong>最大模式</strong>:父容器为子View指定了一个最大可用尺寸。子View的尺寸不能超过这个<code>SpecSize</code>,应根据自身内容需求决定大小。</p></td><td><p>在布局中设置了<code>wrap_content</code>。</p></td></tr><tr><td><p><strong>UNSPECIFIED</strong></p></td><td><p><strong>未指定模式</strong>:父容器对子View没有任何限制,子View可以取任意它需要的大小。这种模式不常用,通常出现在<code>ScrollView</code>、<code>ListView</code>等可滚动的容器中。</p></td></tr></tbody></table>
<p class="maodian"></p><h3>2. MeasureSpec的确定规则</h3>
<p>一个子View的<code>MeasureSpec</code>并非凭空产生,而是由<strong>父容器的</strong><code>MeasureSpec</code>和子View自身的<code>LayoutParams</code>(布局参数,如<code>match_parent</code>、<code>wrap_content</code>或固定尺寸)共同决定的。这个规则封装在<code>ViewGroup</code>的<code>getChildMeasureSpec()</code>方法中。</p>
<p>其决策规律可以总结为下表:</p>
<table><thead><tr><th><p>子View的LayoutParams</p></th><th><p>父容器的SpecMode</p></th><th><p>子View的SpecMode</p></th></tr></thead><tbody><tr><td><p><strong>固定值(如100dp)</strong></p></td><td><p>任意模式</p></td><td><p><code>EXACTLY</code></p></td></tr><tr><td><p><strong>match_parent</strong></p></td><td><p><code>EXACTLY</code></p></td><td><p><code>EXACTLY</code></p></td></tr><tr><td></td><td><p><code>AT_MOST</code></p></td><td><p><code>AT_MOST</code></p></td></tr><tr><td><p><strong>wrap_content</strong></p></td><td><p><code>EXACTLY</code>/ <code>AT_MOST</code></p></td><td><p><code>AT_MOST</code></p></td></tr></tbody></table>
<p class="maodian"></p><h2>实现自定义View的关键步骤</h2>
<p class="maodian"></p><h3>1. 继承View类并重写构造方法</h3>
<p>通常需要实现三个构造函数,以正确处理在代码中创建和在XML布局中声明的情况。</p>
<div class="jb51code"><pre class="brush:java;">public class MyCustomView extends View {
public MyCustomView(Context context) {
super(context);
init();
}
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 初始化画笔、属性等
}
}</pre></div>
<p class="maodian"></p><h3>2. 正确处理测量(重写onMeasure)</h3>
<p>这是自定义View,特别是处理<code>wrap_content</code>时最容易出错的地方。</p>
<div class="jb51code"><pre class="brush:java;">@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int defaultWidth = 200; // 自定义View的默认宽度
int defaultHeight = 200; // 自定义View的默认高度
int width = resolveSize(defaultWidth, widthMeasureSpec);
int height = resolveSize(defaultHeight, heightMeasureSpec);
setMeasuredDimension(width, height);
}</pre></div>
<p><strong>关键点</strong>:如果不重写<code>onMeasure</code>,或者处理不当,当在布局中使用<code>wrap_content</code>时,其效果会与<code>match_parent</code>相同。因为系统的默认实现(<code>getDefaultSize()</code>)在<code>AT_MOST</code>和<code>EXACTLY</code>模式下都返回相同的<code>SpecSize</code>。因此,你需要为<code>wrap_content</code>(即<code>AT_MOST</code>模式)指定一个默认大小。</p>
<p class="maodian"></p><h3>3. 实现绘制(重写onDraw)</h3>
<p>在此方法中,使用<code>Canvas</code>和<code>Paint</code>进行绘制。</p>
<div class="jb51code"><pre class="brush:java;">@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 示例:绘制一个圆形
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = Math.min(centerX, centerY) - 10;
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(centerX, centerY, radius, paint);
}</pre></div>
<p><strong>注意</strong>:应考虑<code>padding</code>的影响,绘制内容时应减去相应的<code>padding</code>值,否则<code>padding</code>属性会失效。</p>
<p class="maodian"></p><h2>进阶技巧与优化建议</h2>
<ul><li><strong>性能优化</strong>:
<ul><li>避免在<code>onDraw</code>中创建对象:例如<code>Paint</code>,应在初始化时(如构造方法)创建并复用。</li><li>使用<code>Canvas.clipRect()</code>:避免绘制View边界之外的内容,减少过度绘制。</li><li><strong>考虑使用硬件加速</strong>:在XML中设置<code>android:layerType="hardware"</code>可以提高某些绘制操作的性能。</li></ul></li><li><strong>自定义属性</strong>:你可以为自定义View定义属性,使其更灵活。步骤包括:在<code>res/values/</code>下创建<code>attrs.xml</code>定义属性;在构造方法中使用<code>TypedArray</code>解析属性;在布局文件中使用自定义命名空间声明属性。</li><li><strong>处理触摸事件</strong>:通过重写<code>onTouchEvent(MotionEvent event)</code>方法,可以让你的View具有交互能力。</li></ul>
<p>到此这篇关于Android的自定义view详解及Measurepec详解的文章就介绍到这了,更多相关android的自定义view内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>Android实现自定义View控件的流程详解</li><li>Android自定义View绘制流程详解</li><li>Android自定义view实现侧滑栏详解</li><li>详解Android如何自定义view实现圆形进度条</li><li>Android自定义View实现体重表盘详解流程</li><li>Android自定义view之利用drawArc方法实现动态效果(思路详解)</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]