接着上一篇自定义view 相关的,揭秘 Android Graphics2D 实现动态效果之——invalidate() 内容的介绍,这一篇主要介绍下自定义view 中的 onMeasure()方法的使用。
在介绍前,先简单回顾下自定义view 中的 onDraw()方法,该方法主要是将图形通过Paint画在Canvas上,View 上的所有内容都最终显示在Canvas 对象上,但这仅仅是绘制出来图形,但是想要设置View 的大小要怎么设置呢?是在xml 里给自定义view设置wrap_content或者match_parent?还是给自定义view限制一个精确的宽高值呢?带着这几个问题,不妨和我一起看一看:
一、自定义View宽高如何设置
1、设置wrap_content或者match_parent,view都会占满全屏(很简单,就是直接在onDraw()中绘制图形);
![](https://img-blog.csdnimg.cn/20190517115949892.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FkbWlubHhiODk=,size_16,color_FFFFFF,t_70)
2、布局修改,给view限制一个宽高;
![限制高度](https://img-blog.csdnimg.cn/20190517115404608.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FkbWlubHhiODk=,size_16,color_FFFFFF,t_70)
3、动态添加view修改宽高
/*方式一*/
CoordinateView coordinateView = new CoordinateView(this);
coordinateView.setBackgroundColor(getResources().getColor(R.color.albumColorPrimary));
linRoot.addView(coordinateView);
/*方式二*/
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(500,1500);
CoordinateView customView = new CoordinateView(this);
customView.setBackgroundColor(getResources().getColor(R.color.albumColorPrimary));
customView.setLayoutParams(layoutParams);
linRoot.addView(customView);
两个方式区别是,单位不一样,new LinearLayout.LayoutParams(500,1500);一个是像素单位,一个是dp单位
二、认识onMeasure
(1)一般情况重写onMeasure()方法作用是为了自定义View尺寸的规则,如果你的自定义View的尺寸是根据父控件行为一致,就不需要重写onMeasure()方法 ;
(2)如果不重写onMeasure方法,那么自定义view的尺寸默认就和父控件一样大小,当然也可以在布局文件里面写死宽高,而重写该方法可以根据自己的需求设置自定义view大小;
1、onMeasure (int widthMeasureSpec, int heightMeasureSpec)是view自己的方法
2、onMeasure 方法简单的理解就是是用于测量视图的大小,主要是用来测量自己和内容的来确定宽度和高度
3、onMeasure有两个参数 ( int widthMeasureSpec, int heightMeasureSpec),该参数表示控件可获得的空间以及关于这个空间描述的元数据.
4、widthMeasureSpec和heightMeasureSpec这两个值通常情况下都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。
/**
* Created by ${lxb} on 2019/5/16.
* 邮箱:207***1410@qq.com
* TIP:坐标转换 学习
*/
public class CoordinateView extends View {
public CoordinateView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CoordinateView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//自定义view 如何设置宽高?
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
//设置默认宽高
setMeasuredDimension(width, height);
}
private int measureWidth(int measureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
//wrap_content
if (mode == MeasureSpec.AT_MOST) { // wrap_content
} else if (mode == MeasureSpec.EXACTLY) { // fill_parent 或者精确值
// size = 500;
}
return size;//默认
}
private int measureHeight(int measureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.AT_MOST) { // wrap_content
} else if (mode == MeasureSpec.EXACTLY) { // fill_parent 或者精确值
// size = 3000;
}
return size;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
三、MeasureSpec 作用
在测量自定义view的大小之前,我们需要认识一个类MeasureSpec,它封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求 MeasureSpec由size和mode组成。
specMode一共有三种类型,如下所示:
1. EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,简单的说(当设置width或height为match_parent时,模式为EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的)
2. AT_MOST
表示子视图最多只能是specSize中指定的大小。(当设置为wrap_content时,模式为AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸)
3. UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
四、案例分析
/**
* Created by ${lxb} on 2019/5/17.
* 邮箱:207***1410@qq.com
* TIP: 自定义view onMeasure() 理解
*/
public class OnMeasureView extends View {
private static final String TAG = OnMeasureView.class.getSimpleName();
private Context mContext;
/*定义一个画笔*/
private Paint mPaint;
private final String mText = "自定义view,测试文字内容";
// private final String mText = "韬睿科技,移动互联网卓越品牌!我爱 Android!";
/*绘制时控制文本绘制的范围*/
private Rect mBound;
private int defaultWidth;
private int defaultHeight;
public OnMeasureView(Context context) {
this(context, null);
}
public OnMeasureView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public OnMeasureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
mBound = new Rect();
mPaint = new Paint();
mPaint.setTextSize(40);
mPaint.getTextBounds(mText, 0, mText.length(), mBound);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GRAY);
mPaint.setColor(Color.RED);
//绘制文字
canvas.drawText(
mText,
getWidth() / 2 - mBound.width() / 2,
getHeight() / 2 + mBound.height() / 2,
mPaint);
}
/**
* 比onDraw() 先执行
* 1.基准点是baseline
* 2.ascent:是baseline之上至字符最高处的距离
* 3.descent:是baseline之下至字符最低处的距离
* 4.leading:是上一行字符的descent到下一行的ascent之间的距离,也就是相邻行间的空白距离
* 5.top:是指的是最高字符到baseline的值,即ascent的最大值
* 6.bottom:是指最低字符到baseline的值,即descent的最大值
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int suggestedMinimumWidth = getSuggestedMinimumWidth();//最低限度宽度
int suggestedMinimumHeight = getSuggestedMinimumHeight();//最低限度高度
Log.d(TAG, "suggestedMinimumWidth:" + suggestedMinimumWidth);
Log.d(TAG, "suggestedMinimumHeight:" + suggestedMinimumHeight);
//设置测量宽高
int width = measureWith(suggestedMinimumWidth, widthMeasureSpec);
int height = measureHeight(suggestedMinimumHeight, heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int measureWith(int suggestedMinimumWidth, int measureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
Log.d(TAG, "size:" + size);
switch (mode) {
case MeasureSpec.AT_MOST:
defaultWidth = (int) mPaint.measureText(mText) + getPaddingLeft() + getPaddingRight();
break;
case MeasureSpec.EXACTLY:
defaultWidth = size;
break;
case MeasureSpec.UNSPECIFIED:
defaultWidth = Math.max(defaultWidth, size);
break;
}
return defaultWidth;
}
private int measureHeight(int suggestedMinimumHeight, int measureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.AT_MOST:
defaultHeight = (int) (-mPaint.ascent() + mPaint.descent()) + getPaddingTop() + getPaddingBottom();
break;
case MeasureSpec.EXACTLY:
defaultHeight = size;
break;
case MeasureSpec.UNSPECIFIED:
defaultHeight = Math.max(defaultHeight, size);
break;
}
return defaultHeight;
}
}
改变 OnMeasureView 的宽高对比就很清晰了:
图一
图二
图三
知识拓展 ——baseLine 基线
上面的案例中,在onMeasure 注释中提到了,绘制文字的基准线 baseLine,那什么是基线呢?说白了就是一条直线,我们这里理解的是确定它的位置。我们先来看一下基线:
从上图看出:基线等同四线格的第三条线,在Android
中基线的位置定了,那么文字的位置也就定了。
更多自定义view学习,可阅读以下链接:
Android自定义View教程 https://www.gcssloop.com/category/customview
自定义View之绘图篇(三):文字(Text) https://blog.csdn.net/u012551350/article/details/51330308