Android中自定义view的onMeasure()方法详谈 - Go语言中文社区

Android中自定义view的onMeasure()方法详谈


背景

首先关于自定义view的实现过程我这里就不去实现了,因为这部分总体来说是不难的,自己写个类去继承View,然后实现构造方法,重写onMeasure()方法、onLayout()方法(继承ViewGroup的时候需要重写)和onDraw()方法来实现自定义view。
进入正题:很多时候我们需要自己来确定自定义view的大小,比如某种情况下你需要改变自定义view的大小,这时候你就需要重新测量自定义view的宽/高了,而这就与重写的onMeasure()方法有关系了,所以很关键就是onMeasure()方法里的实现方式了。那么今天给大家写一个该方法的通用写法,可以让自定义view根据设置的宽/高类型(wrap_content或者match_parent),来控制显示的大小。下面跟我来一起实现。


理解MeasureSpec

通过源码可以发现,MeasureSpec参与了View的measure过程。MeasureSpec是干什么的呢?确切来说,MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽/高。


MeasureSpec

MeasureSpec代表一个32位int值,高两位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。

SpecMode有三类,每一类都表示特殊的含义,如下表所示

SpecMode 代表的含义
UNSPECFIED 父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态。
EXACTLY 父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。
AT_MOST 父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的warp_content。

情况分析

实际情况中,你在布局文件中引用这个自定义View的时候设置宽和高的时候也就三种情况:

  • 宽/高都设置为了match_parent或者是固定大小
  • 宽/高其中一个设置为了wrap_content,另外一个为match_parent或者是固定大小
  • 宽/高都设置为了wrap_content

结合图例分析

  • 首先不在onMeasure()方法里写任何代码,就用系统默认的测量来试试看当给自定义view设置不同的宽/高类型后,在布局中是怎么显示的。

代码如下(onMeasure()方法里不写代码):

package com.example.demo.myapplication;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by Administrator on 2018/3/16.
 */

public class CustomView extends View{

    Paint paint = null;

    public CustomView(Context context) {
        super(context);
        init();
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 控件初始化,一些初始化设置
     */
    private void init() {
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(Color.BLUE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

//        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
//        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
//
//        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
//
//        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
//            setMeasuredDimension(400, 400);
//        }else if (widthSpecMode == MeasureSpec.AT_MOST){
//            setMeasuredDimension(400, heightSpecSize);
//        }else if (heightSpecMode == MeasureSpec.AT_MOST){
//            setMeasuredDimension(widthSpecSize, 400);
//        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.RED);// 给自定义View的背景设置成红色,直观的显示view的在不同模式下的大小变化情况
    }
}

这里大家可以看到,我把相关代码注释了。这时候View的大小就是系统自己控制的, 我们不做任何的操作了。

在分析各种情况的时候,我这里发现了一个小技巧,不用运行项目就可以直接的将布局中自定义部分的可视界面根据代码变化也相应的动态变化。通俗点说就是代码变化后,不需要运行项目在移动设备上查看界面,只需要在布局中就能看到变化后的界面效果,这个如何做到的呢?如下:

这里写图片描述

这里写图片描述

简单来说,当你的自定义view中onMeasure方法的代码变化后,在布局中可以看到有提示说你的布局构建过时了,这时候就需要重新构建build,点击build就会根据代码的变动做出相对应的修改。而不需要再运行项目重新查看新的界面了,这样的话就方便多了。

  • 当宽/高都是wrap_content的时候,显示如下:

这里写图片描述
这时候自定义View充满了父布局所在空间。

  • 当宽/高都是match_parent的时候,显示如下:

这里写图片描述
这时候自定义View充满了父布局所在的空间。

  • 当宽/高都是固定大小的时候,显示如下:

这里写图片描述
这时候自定义View的大小就是固定的大小了。

很显然上面采用系统默认的测量方式不是我所期望的,我希望的是当自定义View的宽/高为wrap_content的时候可以自己动态的控制view的大小,但是上面的情况下,这时候view是充满父布局的空间的,这显然做不到动态的控制view的显示大小。现在来看第二种情况。

在onMeasure()方法中根据不同的宽/高设置类型来执行对应的操作达到控制view的大小。代码如下:

package com.example.demo.myapplication;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by Administrator on 2018/3/16.
 */

public class CustomView extends View{

    Paint paint = null;

    public CustomView(Context context) {
        super(context);
        init();
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 控件初始化,一些初始化设置
     */
    private void init() {
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(Color.BLUE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 获取view宽的SpecSize和SpecMode
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);

        // 获取view高的SpecSize和SpecMode
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);


        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            // 当view的宽和高都设置为wrap_content时,调用setMeasuredDimension(measuredWidth,measureHeight)方法设置view的宽/高为400px
            setMeasuredDimension(400, 400);
        }else if (widthSpecMode == MeasureSpec.AT_MOST){
            // 当view的宽设置为wrap_content时,设置View的宽为你想要设置的大小(这里我设置400px),高就采用系统获取的heightSpecSize
            setMeasuredDimension(400, heightSpecSize);
        }else if (heightSpecMode == MeasureSpec.AT_MOST){
            // 当view的高设置为wrap_content时,设置View的高为你想要设置的大小(这里我设置400px),宽就采用系统获取的widthSpecSize
            setMeasuredDimension(widthSpecSize, 400);
        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.RED);// 给自定义View的背景设置成红色,直观的显示view的在不同模式下的大小变化情况
    }
}
  • 当宽/高都是wrap_content的时候,显示如下:

这里写图片描述
看这里就是我们自己在代码中设置的400px大小了。

  • 当宽/高都是match_parent的时候,显示如下:

这里写图片描述
这时候也是充满父布局的空间的。

  • 当宽/高都是固定大小的时候,显示如下:

这里写图片描述
这时候就是固定的大小显示,也没问题。

  • 当宽/高有一个是wrap_content的时候,显示如下:

这里写图片描述

这里写图片描述

可以看到设置的wrap_content是根据代码中设置的大小来进行显示,符合我们的要求。


总结

这里大家应该就差不多清楚了不同模式下的view的显示大小是怎么变化的了。onMeasure()方法中的通用写法如下:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 获取view宽的SpecSize和SpecMode
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);

        // 获取view高的SpecSize和SpecMode
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);


        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            // 当view的宽和高都设置为wrap_content时,调用setMeasuredDimension(measuredWidth,measureHeight)方法设置view的宽/高为400px
            setMeasuredDimension(400, 400);
        }else if (widthSpecMode == MeasureSpec.AT_MOST){
            // 当view的宽设置为wrap_content时,设置View的宽为你想要设置的大小(这里我设置400px),高就采用系统获取的heightSpecSize
            setMeasuredDimension(400, heightSpecSize);
        }else if (heightSpecMode == MeasureSpec.AT_MOST){
            // 当view的高设置为wrap_content时,设置View的高为你想要设置的大小(这里我设置400px),宽就采用系统获取的widthSpecSize
            setMeasuredDimension(widthSpecSize, 400);
        }

    }

这里大家想要设置多大的宽和高,自己可以动态的设置,但是需要设置其类型为wrap_content哦

===========================================================================

A little bit of progress every day!Come on!

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/csdnzouqi/article/details/79579562
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-04-18 21:38:31
  • 阅读 ( 759 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢