1.onMeasure方法的理解
首先我们新建一个组件MyView
package main.com.taiji.component;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
我们首先测试宽和高都是match_parent的情况
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<main.com.taiji.component.MyView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:background="#ffff00"/>
</LinearLayout>
效果如图:
接下来我们测试宽和高为wrap_content情况
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<main.com.taiji.component.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#ffff00"/>
</LinearLayout>
效果不变 (图2)
接下来我们指定宽和高为具体的值
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<main.com.taiji.component.MyView
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_margin="10dp"
android:background="#ffff00"/>
</LinearLayout>
效果如下:
我们修改MyView中的OnMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int dp=px2dip(getContext(),widthSize);
setMeasuredDimension(200,200);
}
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
我们发现组件的宽和高被修改了 (注意这里的200,单位是px)
同时我们通过debug得到如下信息
然后我们可以推断:onMeasure方法中的widthMeasureSpec和heightMeasureSpec是父组件View中的layout_width和layout_height中指定的宽和高(需要换算一下)
如何正确使用OnMeasure?
我们以widthMeasureSpec值举例:
widthMeasureSpec这个值由高32位和低16位组成,高32位保存的值叫specMode,可以通过如代码中所示的MeasureSpec.getMode()获取;低16位为specSize,同样可以由MeasureSpec.getSize()获取
specMode的三种可能:
MeasureSpec.EXACTLY:父视图希望子视图的大小应该是specSize中指定的。EXACTLY相当于我们设置了match_parent或一个具体的值
MeasureSpec.AT_MOST:子视图的大小最多是specSize中指定的值,也就是说不建议子视图的大小超过specSize中给定的值。AT_MOST相当于我们设置了wrap_content
MeasureSpec.UNSPECIFIED:我们可以随意指定视图的大小。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}
public int measureWidth(int measureSpec){
int result=0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if(specMode==MeasureSpec.EXACTLY){
result=specSize;
}else {
result=300;
if(specMode==MeasureSpec.AT_MOST){
result=Math.min(result,specSize);
}
}
return result;
}
public int measureHeight(int measureSpec){
int result=0;
int specMode=MeasureSpec.getMode(measureSpec);
int specSize=MeasureSpec.getSize(measureSpec);
if(specMode==MeasureSpec.EXACTLY){
result=specSize;
}else {
result=300;
if(specMode==MeasureSpec.AT_MOST){
result=Math.min(result,specSize);
}
}
return result;
}
然后我们进行测试:
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="400dp"
android:layout_height="400dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
可以看出,当我们在设置wrap_content时,该自定义组件的宽和高被限制为不能超过300px(如果按照原来的情况会铺满全屏,见图2)