Android中自定义View的MeasureSpec使用

一、MeasureSpec概述

在 Android 中,我们经常会使用View进行UI开发,而View的大小则是由其父View来决定的,因此MeasureSpec就很重要了。它是设备中View大小的度量方法,用来告诉View此时应该在父View中处于什么状态。

MeasureSpec由32位的int类型值构成,其中高2位表示测量模式,低30位表示测量大小。

![MeasureSpec的结构](https://img-blog.csdn.net/20160329224115774)

MeasureSpec可以分成三种测量模式:

- EXACTLY:父View已经确定了子View的确切大小,子View需要遵守。这种情况通常出现在父View设置明确的大小,比如match_parent、具体值等。

- AT_MOST:子View的大小不能超过父View指定的大小,否则将导致不可预测的后果。通常出现在设置为wrap_content时,子View的大小是根据内容来确定的。

- UNSPECIFIED:父View没有对子View施加任何约束,子View可以是任意大小。很少使用。

在自定义View时,我们需要根据实际情况去处理MeasureSpec,具体使用方式下文中会讲到。

二、MeasureSpec使用方法

View中的MeasureSpec值是由父View给出的,而Measure过程则是由View自行完成的。

当View进行measure的时候,从其onMeasure()方法进入,该方法会被父View调用,调用者会按以下形式传递MeasureSpec信息:

```java

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

//自定义View的测量逻辑

}

```

在自定义View内部,我们可以得到父View传递下来的MeasureSpec,可以根据实际的情况进行处理。

在自定义View中,通常是根据View的类型特性以及父View的约束条件来决定自己的MeasureSpec决定自己的大小。下面是常用的例子。

1. 宽高都是具体数值

当View的宽和高是具体的数值时,比如`android:layout_width=100dp`,此时我们需要将具体的大小转换成MeasureSpec进行传递。可以使用以下方法:

```java

MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthSize), MeasureSpec.EXACTLY)

```

其中 `widthSize` 代表 `widthMeasureSpec` 中的 size 值, `MeasureSpec.EXACTLY` 代表测量模式为 `EXACTLY`,代表具体的数值大小。

2. 宽或高是match_parent或fill_parent

当View的宽或高是 match_parent 或 fill_parent(即父容器宽或高),此时它就需要尽量占满父容器空间,因此我们就需要传递`EXACTLY`模式和尽可能大的宽或高:

```java

// 比如宽是match_parent

MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthSize), MeasureSpec.EXACTLY)

```

3. 宽或高是wrap_content

当View的宽或高是wrap_content时,需要根据其内容计算出最终的大小,并将宽或高设置成AT_MOST,此时的size值就是基于内容计算的大小。

```java

// 比如宽是wrap_content

MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST)

```

三、MeasureSpec的案例

下面通过代码演示MeasureSpec的使用方法,我们自定义一个View,内含一个TextView和一个ImageView,布局方式如下:

```xml

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="这是一段文字"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

app:srcCompat="@drawable/ic_launcher_background"/>

```

1. 自定义TextView

```java

public class MeasureTextView extends View {

private Paint mPaint;

private String text;

public MeasureTextView(Context context) {

super(context);

init();

}

private void init() {

mPaint = new Paint();

mPaint.setTextSize(60);

text = "这是一段文字";

}

public MeasureTextView(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

@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);

switch (widthMode) {

case MeasureSpec.EXACTLY:

break;

case MeasureSpec.AT_MOST:

widthSize = (int) mPaint.measureText(text) + getPaddingLeft() + getPaddingRight();

break;

case MeasureSpec.UNSPECIFIED:

widthSize = (int) mPaint.measureText(text) + getPaddingLeft() + getPaddingRight();

break;

}

switch (heightMode) {

case MeasureSpec.EXACTLY:

break;

case MeasureSpec.AT_MOST:

heightSize = (int) (mPaint.descent() - mPaint.ascent()) + getPaddingTop() + getPaddingBottom();

break;

case MeasureSpec.UNSPECIFIED:

heightSize = (int) (mPaint.descent() - mPaint.ascent()) + getPaddingTop() + getPaddingBottom();

break;

}

setMeasuredDimension(widthSize, heightSize);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawText(text, getPaddingLeft(), getPaddingTop() - mPaint.ascent(), mPaint);

}

}

```

2. 自定义ImageView

```java

public class MeasureImageView extends View {

private Paint mPaint;

private Rect mSrcRect;

private Rect mDstRect;

private Bitmap mBitmap;

public MeasureImageView(Context context) {

super(context);

init();

}

public MeasureImageView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

init();

}

public MeasureImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init();

}

private void init() {

mPaint = new Paint();

mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background);

mSrcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());

}

@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);

switch(widthMode) {

case MeasureSpec.AT_MOST:

case MeasureSpec.EXACTLY:

int imgWidth = mBitmap.getWidth();

widthSize = Math.min(widthSize, imgWidth + getPaddingLeft() + getPaddingRight());

break;

case MeasureSpec.UNSPECIFIED:

break;

}

switch(heightMode) {

case MeasureSpec.AT_MOST:

case MeasureSpec.EXACTLY:

int imgHeight = mBitmap.getHeight();

heightSize = Math.min(heightSize, imgHeight + getPaddingTop() + getPaddingBottom());

break;

case MeasureSpec.UNSPECIFIED:

break;

}

setMeasuredDimension(widthSize, heightSize);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (mDstRect == null) {

mDstRect = new Rect(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());

}

canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, mPaint);

}

}

```

四、总结

MeasureSpec作为Android中View的测量方法是非常重要的,自定义View时一定要注意它的使用和处理。在大多数情况下,我们需要根据View的类型特性和父容器的测量模式来决定自己的MeasureSpec和最终的大小。 以上实现过程仅供参考,可能不完全正确,请在实际开发中根据需求进行判断并实现。 如果你喜欢我们三七知识分享网站的文章, 欢迎您分享或收藏知识分享网站文章 欢迎您到我们的网站逛逛喔!https://www.37seo.cn/

点赞(80) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
发表
评论
返回
顶部