Android MeasureSpec的理解和源码的解析

Android  MeasureSpec的理解和源码的解析

MeasureSpec的创建规则:

实例详解:

package cc.ww; 

import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.LinearLayout; 

/**
 * @author http://blog.csdn.net/lfdfhl
 *
 * 文档描述:
 * 关于MeasureSpec的理解
 *
 * (1) MeasureSpec基础知识
 *   MeasureSpec通常翻译为"测量规格",它是一个32位的int数据.
 *   其中高2位代表SpecMode即某种测量模式,低32位为SpecSize代表在该模式下的规格大小.
 *   可以通过:
 *   int specMode = MeasureSpec.getMode(measureSpec) 获取specMode
    int specSize = MeasureSpec.getSize(measureSpec) 获取SpecSize 

      常用的SpecMode有三种: 

    MeasureSpec.EXACTLY
      官方文档
    Measure specification mode: The parent has determined an exact size
    for the child. The child is going to be given those bounds regardless of how big it wants to be.
         父容器已经检测出子View所需要的精确大小.该子View最终的测量大小即为SpecSize.
    (1) 当子View的LayoutParams的宽(高)采用具体的值(如100px)时且父容器的MeasureSpec为 MeasureSpec.EXACTLY或者
    MeasureSpec.AT_MOST或者MeasureSpec.UNSPECIFIED时:
         系统返回给该子View的specMode就为 MeasureSpec.EXACTLY
         系统返回给该子View的specSize就为子View自己指定的大小(childSize)
         通俗地理解:
         子View的LayoutParams的宽(高)采用具体的值(如100px)时,那么说明该子View的大小是非常明确的,明确到已经用具体px值
         指定的地步了.那么此时不管父容器的specMode是什么,系统返回给该子View的specMode总是MeasureSpec.EXACTLY,并且
         系统返回给该子View的specSize就为子View自己指定的大小(childSize). 

    (2) 当子View的LayoutParams的宽(高)采用match_parent时并且父容器的MeasureSpec为 MeasureSpec.EXACTLY时:
         系统返回给该子View的specMode就为 MeasureSpec.EXACTLY
         系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
         通俗地理解:
      子View的LayoutParams的宽(高)采用match_parent时并且父容器的MeasureSpec为 MeasureSpec.EXACTLY.
      这时候说明子View的大小还是挺明确的:就是要和父容器一样大,更加直白地说就是父容器要怎样子View就要怎样.
      所以,如果父容器MeasureSpec为 MeasureSpec.EXACTLY那么:
      系统返回给该子View的specMode就为 MeasureSpec.EXACTLY,和父容器一样.
         系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize),就是父容器的剩余大小.
         同样的道理如果此时,MeasureSpec为 MeasureSpec.AT_MOST呢?
         系统返回给该子View的specMode也为 MeasureSpec.AT_MOST,和父容器一样.
         系统返回给该子View的specSize也为该父容器剩余空间的大小(parentLeftSize),就是父容器的剩余大小. 

    MeasureSpec.AT_MOST
      官方文档
    The child can be as large as it wants up to the specified size.
      父容器指定了一个可用大小即specSize,子View的大小不能超过该值.
    (1) 当子View的LayoutParams的宽(高)采用match_parent时并且父容器的MeasureSpec为 MeasureSpec.AT_MOST时:
         系统返回给该子View的specMode就为 MeasureSpec.AT_MOST
         系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
         这种情况已经在上面介绍 MeasureSpec.EXACTLY时已经讨论过了.
   (2) 当子View的LayoutParams的宽(高)采用wrap_content时并且父容器的MeasureSpec为 MeasureSpec.EXACTLY时:
         系统返回给该子View的specMode就为 MeasureSpec.AT_MOST
         系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
         通俗地理解:
         子View的LayoutParams的宽(高)采用wrap_content时说明这个子View的宽高不明确,要视content而定.
         这个时候如果父容器的MeasureSpec为 MeasureSpec.EXACTLY即父容器是一个精确模式;这个时候简单地说
         子View是不确定的,父容器是确定的,那么
         系统返回给该子View的specMode也就是不确定的即为 MeasureSpec.AT_MOST
         系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
    (3) 当子View的LayoutParams的宽(高)采用wrap_content时并且父容器的MeasureSpec为 MeasureSpec.AT_MOST时:
         系统返回给该子View的specMode就为 MeasureSpec.AT_MOST
         系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)
         通俗地理解:
         子View的LayoutParams的宽(高)采用wrap_content时说明这个子View的宽高不明确,要视content而定.
         这个时候如果父容器的MeasureSpec为 MeasureSpec.AT_MOST这个时候简单地说
         子View是不确定的,父容器也是不确定的,那么
         系统返回给该子View的specMode也就是不确定的即为 MeasureSpec.AT_MOST
         系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize) 

    MeasureSpec.UNSPECIFIED
      官方文档
    The parent has not imposed any constraint on the child. It can be whatever size it wants.
      父容器不对子View的大小做限制.
      一般用作Android系统内部,或者ListView和ScrollView.在此不做讨论. 

      关于这个三种测量规格下面的源码分析中体现得很明显,也可参考以下附图. 

 * (2) 在onMeasure()时子View的MeasureSpec的形成过程分析
 *   关于该技术点的讨论,请看下面的源码分析.
 *
 */
public class UnderstandMeasureSpec { 

  /**
   * 第一步:
   * 在ViewGroup测量子View时会调用到measureChildWithMargins()方法,或者与之类似的方法.
   * 请注意方法的参数:
   * @param child
   * 子View
   * @param parentWidthMeasureSpec
   * 父容器(比如LinearLayout)的宽的MeasureSpec
   * @param widthUsed
   * 父容器(比如LinearLayout)在水平方向已经占用的空间大小
   * @param parentHeightMeasureSpec
   * 父容器(比如LinearLayout)的高的MeasureSpec
   * @param heightUsed
   * 父容器(比如LinearLayout)在垂直方向已经占用的空间大小
   *
   * 在该方法中主要有四步操作,其中很重要的是调用了getChildMeasureSpec()方法来确定
   * 子View的MeasureSpec.详情参见代码分析
   */
  protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,
                           int parentHeightMeasureSpec, int heightUsed) {
    //1 得到子View的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //2 得到子View的宽的MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec
    (parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
    //3 得到子View的高的MeasureSpec
    final int childHeightMeasureSpec = getChildMeasureSpec
    (parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
    //4 测量子View
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  } 

  /**
   * getChildMeasureSpec()方法确定子View的MeasureSpec
   * 请注意方法的参数:
   * @param spec
   * 父容器(比如LinearLayout)的宽或高的MeasureSpec
   * @param padding
   * 父容器(比如LinearLayout)在垂直方向或者水平方向已被占用的空间.
   * 在measureChildWithMargins()方法里调用getChildMeasureSpec()时注意第二个参数的构成:
   * 比如:mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
   * 其中:
   * mPaddingLeft和mPaddingRight表示父容器左右两内侧的padding
   * lp.leftMargin和lp.rightMargin表示子View左右两外侧的margin
   * 这四部分都不可以再利用起来布局子View.所以说这些值的和表示:
   * 父容器在水平方向已经被占用的空间
   * 同理:
   * mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
   * 表示:
   * 父容器(比如LinearLayout)在垂直方向已被占用的空间.
   * @param childDimension
   * 通过子View的LayoutParams获取到的子View的宽或高
   *
   *
   * 经过以上分析可从getChildMeasureSpec()方法的第一个参数和第二个参数可以得出一个结论:
   * 父容器(如LinearLayout)的MeasureSpec和子View的LayoutParams共同决定了子View的MeasureSpec!!!
   *
   *
   *
   */
   public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
      /**
       * 第一步:得到父容器的specMode和specSize
       */
      int specMode = MeasureSpec.getMode(spec);
      int specSize = MeasureSpec.getSize(spec);
      /**
       * 第二步:得到父容器在水平方向或垂直方向可用的最大空间值.
       *    关于padding参见上面的分析
       */
      int size = Math.max(0, specSize - padding); 

      int resultSize = 0;
      int resultMode = 0; 

      /**
       * 第三步:确定子View的specMode和specSize.
       *    在此分为三种情况进行.
       */
      switch (specMode) {
      /**
       * 第一种情况:
       * 父容器的测量模式为EXACTLY
       *
       * 请注意两个系统常量:
       * LayoutParams.MATCH_PARENT=-1
       * LayoutParams.WRAP_CONTENT=-2
       * 所以在此处的代码:
       * childDimension >= 0 表示子View的宽或高不是MATCH_PARENT和WRAP_CONTENT
       */
      case MeasureSpec.EXACTLY:
        /**
         * 当父容器的测量模式为EXACTLY时如果:
         * 子View的宽或高是一个精确的值,比如100px;
         * 那么:
         * 子View的size就是childDimension
         * 子View的mode也为MeasureSpec.EXACTLY
         */
        if (childDimension >= 0) {
          resultSize = childDimension;
          resultMode = MeasureSpec.EXACTLY;
        /**
         * 当父容器的测量模式为EXACTLY时如果:
         * 子View的宽或高是LayoutParams.MATCH_PARENT
         * 那么:
         * 子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size
         * 子View的mode也为MeasureSpec.EXACTLY
         */
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
          // Child wants to be our size. So be it.
          resultSize = size;
          resultMode = MeasureSpec.EXACTLY;
        /**
         * 当父容器的测量模式为EXACTLY时如果:
         * 子View的宽或高是LayoutParams.WRAP_CONTENT
         * 那么:
         * 子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size
         * 子View的mode为MeasureSpec.AT_MOST
         */
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
          // Child wants to determine its own size. It can't be bigger than us.
          resultSize = size;
          resultMode = MeasureSpec.AT_MOST;
        }
        break; 

      /**
       * 第二种情况:
       * 父容器的测量模式为AT_MOST
       *
       * 请注意两个系统常量:pp
       * LayoutParams.MATCH_PARENT=-1
       * LayoutParams.WRAP_CONTENT=-2
       * 所以在此处的代码:
       * childDimension >= 0 表示子View的宽或高不是MATCH_PARENT和WRAP_CONTENT
       */
      case MeasureSpec.AT_MOST:
        /**
         * 当父容器的测量模式为AT_MOST时如果:
         * 子View的宽或高是一个精确的值,比如100px;
         * 那么:
         * 子View的size就是childDimension
         * 子View的mode也为MeasureSpec.EXACTLY
         */
        if (childDimension >= 0) {
          // Child wants a specific size... so be it
          resultSize = childDimension;
          resultMode = MeasureSpec.EXACTLY;
        /**
         * 当父容器的测量模式为AT_MOST时如果:
         * 子View的宽或高为LayoutParams.MATCH_PARENT
         * 那么:
         * 子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size
         * 子View的mode也为MeasureSpec.AT_MOST
         */
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
          // Child wants to be our size, but our size is not fixed.
          // Constrain child to not be bigger than us.
          resultSize = size;
          resultMode = MeasureSpec.AT_MOST;
         /**
         * 当父容器的测量模式为AT_MOST时如果:
         * 子View的宽或高为LayoutParams.WRAP_CONTENT
         * 那么:
         * 子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size
         * 子View的mode也为MeasureSpec.AT_MOST
         */
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
          // Child wants to determine its own size. It can't be
          // bigger than us.
          resultSize = size;
          resultMode = MeasureSpec.AT_MOST;
        }
        break; 

      /**
       * 第三种情况:
       * 父容器的测量模式为UNSPECIFIED
       *
       * 请注意两个系统常量:
       * LayoutParams.MATCH_PARENT=-1
       * LayoutParams.WRAP_CONTENT=-2
       * 所以在此处的代码:
       * childDimension >= 0 表示子View的宽或高不是MATCH_PARENT和WRAP_CONTENT
       */
      case MeasureSpec.UNSPECIFIED:
        /**
         * 当父容器的测量模式为UNSPECIFIED时如果:
         * 子View的宽或高是一个精确的值,比如100px;
         * 那么:
         * 子View的size就是childDimension
         * 子View的mode也为MeasureSpec.EXACTLY
         */
        if (childDimension >= 0) {
          // Child wants a specific size... let him have it
          resultSize = childDimension;
          resultMode = MeasureSpec.EXACTLY;
        /**
         * 当父容器的测量模式为UNSPECIFIED时如果:
         * 子View的宽或高为LayoutParams.MATCH_PARENT
         * 那么:
         * 子View的size为0
         * 子View的mode也为MeasureSpec.UNSPECIFIED
         */
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
          // Child wants to be our size... find out how big it should be
          resultSize = 0;
          resultMode = MeasureSpec.UNSPECIFIED;
        /**
         * 当父容器的测量模式为UNSPECIFIED时如果:
         * 子View的宽或高为LayoutParams.WRAP_CONTENT
         * 那么:
         * 子View的size为0
         * 子View的mode也为MeasureSpec.UNSPECIFIED
         */
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
          // Child wants to determine its own size.... find out how big it should be
          resultSize = 0;
          resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
      }
      return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    } 

}

如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • Android中Activity和Fragment传递数据的两种方式

    1.第一种方式,也是最常用的方式,就是使用Bundle来传递参数 MyFragment myFragment = new MyFragment(); Bundle bundle = new Bundle(); bundle.putString("DATA",values);//这里的values就是我们要传的值 myFragment.setArguments(bundle); 然后在Fragment中的onCreatView方法中,通过getArgments()方法,获取到bundle

  • 微信Android热更新Tinker使用详解(星空武哥)

    Tinker是什么 Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码.So库以及资源,让应用能够在不需要重新安装的情况下实现更新.当然,你也可以使用Tinker来更新你的插件. 它主要包括以下几个部分: gradle编译插件: tinker-patch-gradle-plugin 核心sdk库: tinker-android-lib 非gradle编译用户的命令行版本: tinker-patch-cli.jar 为什么使用Tinker 当前市面的热补丁方案有很多,其中比较

  • Android 开发之Dialog中隐藏键盘的正确使用方法

    Android 开发之Dialog中隐藏键盘的正确使用方法 场景:弹出一个Dialog,里面有一个EditText,用来输入内容,因为输入时,需要弹出键盘,所以当Dialog消失时,键盘要一起隐藏. 现在我们做一个自定义的Dialog MyDialog extends Dialog 一开始认为这个功能很容易实现,于是写了下面的代码 //Dialog的构造函数中写 this.setOnDismissListener(new OnDismissListener() { @Override publi

  • Android ListView之setEmptyView正确使用方法

    Android ListView之setEmptyView正确使用方法 我们知道ListView组件提供了一个空数据是的视图设置方法setEmptyView,该方法存在一个诟病,就是空视图和listview组件要在一个Parent中,这个就不在此细说,下面说另一个问题,原因其实和前面那个问题同出一辙. 假如emptyView和listview在布局中,已经属于同一个parent,设置EmptyView的代码如下 private void setEmptyView(){ emptyTv.setTe

  • Android LocationManager获取经度与纬度等地理信息

    Android LocationManager获取经度与纬度等地理信息 利用LocationManager实现定位功能 1 实时更新经度,纬度 2 根据经度和纬度获取地理信息(比如:国家,街道等)(略过) MainActivity如下: package cc.bb; import java.util.Iterator; import java.util.List; import android.location.Location; import android.location.Location

  • Android 软键盘状态并隐藏输入法的实例

    Android 软键盘状态并隐藏输入法的实例 1 软键盘状态的切换 2 强制隐藏输入法键盘 MainActivity如下: package cc.c; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.view.inputmethod.InputMethodManager; import android.widget.Button

  • Android Intent调用 Uri的方法总结

    Android Intent调用 Uri的方法总结 //调用浏览器 Uri uri = Uri.parse(""); Intent it = new Intent(Intent.ACTION_VIEW,uri); startActivity(it); //显示某个坐标在地图上 Uri uri = Uri.parse("geo:38.899533,-77.036476"); Intent it = new Intent(Intent.Action_VIEW,uri);

  • Android MeasureSpec的理解和源码的解析

    Android  MeasureSpec的理解和源码的解析 MeasureSpec的创建规则: 实例详解: package cc.ww; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.L

  • Android  MeasureSpec的理解和源码的解析

    Android  MeasureSpec的理解和源码的解析 MeasureSpec的创建规则: 实例详解: package cc.ww; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.L

  • 深入Android HandlerThread 使用及其源码完全解析

    关联篇:深入Android的消息机制源码详解-Handler,MessageQueue与Looper关系 关联篇:Handler内存泄漏及其解决方案 本篇我们将来给大家介绍HandlerThread这个类,以前我们在使用线程执行一个耗时任务时总会new一个Thread的线程去跑,当任务执行完后,线程就会自动被销毁掉,如果又由新的任务,我们又得新建线程.....我们假设这样的一个情景,我们通过listview去加载图文列表,当我们往下滑动时,这时需要不断去请求网络资源,也就是需要不断开线程去加载

  • 深入理解react-router@4.0 使用和源码解析

    如果你已经是一个正在开发中的react应用,想要引入更好的管理路由功能.那么,react-router是你最好的选择~ react-router版本现今已经到4.0.0了,而上一个稳定版本还是2.8.1.相信我,如果你的项目中已经在使用react-router之前的版本,那一定要慎重的更新,因为新的版本是一次非常大的改动,如果你要更新,工作量并不小. 这篇文章不讨论版本的变化,只是讨论一下React-router4.0的用法和源码. 源码在这里:https://github.com/ReactT

  • Android Jetpack 组件LiveData源码解析

    目录 前言 基本使用 疑问 源码分析 Observer ObserverWrapper LifecycleBoundObserver MutableLiveData postValue setValue 问题答疑 LiveData 特性引出的问题 问题解决 最后 前言 本文来分析下 LiveData 的源码,以及其在实际开发中的一些问题. 基本使用 一般来说 LiveData 都会配合 ViewModel 使用,篇幅原因关于 ViewModel 的内容将在后续博客中分析,目前可以将 ViewMo

  • Android消息循环机制源码深入理解

    Android消息循环机制源码 前言: 搞Android的不懂Handler消息循环机制,都不好意思说自己是Android工程师.面试的时候一般也都会问这个知识点,但是我相信大多数码农肯定是没有看过相关源码的,顶多也就是网上搜搜,看看别人的文章介绍.学姐不想把那个万能的关系图拿出来讨论. 近来找了一些关于android线程间通信的资料,整理学习了一下,并制作了一个简单的例子. andriod提供了 Handler 和 Looper 来满足线程间的通信.例如一个子线程从网络上下载了一副图片,当它下

  • Android编程实现二维码的生成与解析

    本文实例讲述了Android编程实现二维码的生成与解析.分享给大家供大家参考,具体如下: 直接上代码,代码上面有具体的解析,并且提供jar供下载:二维码Jar包.rar. 根据文本生成对应的二维码: // 生成QR图 private void createImage() { try { // 需要引入core包 QRCodeWriter writer = new QRCodeWriter(); String text = qr_text.getText().toString(); Log.i(T

  • 解析Android框架之OkHttp3源码

    OkHttp流程图 OkHttp基本使用 gradle依赖 implementation 'com.squareup.okhttp3:okhttp:3.11.0' implementation 'com.squareup.okio:okio:1.15.0' /** *这里拿get请求来 * 异步的get请求 */ public void okhttpAsyn() { //设置超时的时间 OkHttpClient.Builder builder = new OkHttpClient.Builder

  • 解析Android框架之Volley源码

    Volley简单使用 我这里是以依赖架包的形式 ,大家也可以以gradle的形式进行依赖. 好了,接下来上代码了..... //获取volley的请求对象 RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext()); StringRequest stringRequest = new StringRequest(StringRequest.Method.GET, "http://www.baidu.com

  • Android文件存储SharedPreferences源码解析

    1.我们都知道SharedPreferences 是android可以用来存放key value的的文件. SharedPreferences sp = getSharedPreferences("fileName", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("key","value"); editor.commit(

随机推荐