一、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:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> android:layout_height="wrap_content" android:text="这是一段文字"/> 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/
发表评论 取消回复