Android 视图 View (一)

View 的生命周期

了解生命周期在咱们自定义View的时候发挥着很大的做用java

  • 在Activity启动时获取View的宽高,可是在onCreate、onStart和onResume均没法获取正确的结果。这是由于在Activity的这些方法里,Viewed绘制可能尚未完成,咱们能够在View的生命周期方法里获取,如onSizeChanged()。
  • 在Activity生命周期发生变化时,View也要作响应的处理,典型的有VideoView保存进度和恢复进度。
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
    super.onVisibilityChanged(changedView, visibility);
    //TODO do something if activity lifecycle changed if necessary
    //Activity onResume()
    if(visibility == VISIBLE){
        
    }
    //Activity onPause()
    else {
        
    }
}

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
    super.onWindowFocusChanged(hasWindowFocus);

    //TODO do something if activity lifecycle changed if necessary
    //Activity onResume()
    if (hasWindowFocus) {
    }
    //Activity onPause()
    else {
    }
}
复制代码
  • 释放线程、资源
@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    //TODO release resources, thread, animation
}
复制代码

View 的测量流程

view大小android

View的大小有两对值来表示,getMeasuredWidth()/getMeasureHeight(),这组值表示了他在父View里指望的大小,在measure()方法完成后可得到markdown

View内边距ide

View内边距:View的内边距用padding来表示,它表示View的内容距离View边缘的距离。经过getPaddingXXX()方法获取。须要注意的是咱们在自定义View的时候须要单独处理布局

padding,不然它不会生效.优化

View外边距this

View外边距:View的外边距用margin来表示,它表示View的边缘离它相邻的View的距离。spa

在作测量时,measure() 方法会被父View调用,在measure()中作一些准备和优化工做后,调用onMeasure()来进行实际的自我测量。对于onMeasure(),View和ViewGroup有所区别:线程

  • View:View 在 onMeasure() 中会计算出本身的尺寸而后保存;
  • ViewGroup:ViewGroup在onMeasure()中会调用全部子View的measure()让它们进行自我测量,并根据子View计算出的指望尺寸来计算出它们的实际尺寸和位置而后保存。同时,它也会

根据子View的尺寸和位置来计算出本身的尺寸而后保存.3d

View measure过程 ViewGroup 首先经过getChildMeasureSpec() 获取child的MeasureSpec(childWidthMeasureSpec, childHeightMeasureSpec)

  • getChildMeasureSpec(ViewGroup的spec,ViewGroup padding,view 宽高)
最大size = 父view大小-padding > 0? ..:0
父view mode = exactly
   子 viewsize 为具体>0数值,则子viewsize 肯定 , spec_mode = exactly
   子 View 为 match_parent,则子view大小 = 最大size,spec_mode = exactly
   子 View 为 wrap_content,则子view大小 = 最大size, spec_mode = AT_MOST
父view mode = AT_MOST
   子 viewsize 为具体>0数值,则子viewsize 肯定 , spec_mode = exactly
   子 View 为 match_parent,则子view大小 = 最大size,spec_mode = AT_MOST
   子 View 为 wrap_content,则子view大小 = 最大size, spec_mode = AT_MOST
父view mode = UNSPECIFIED
   子 viewsize 为具体>0数值,则子viewsize 肯定 , spec_mode = exactly
   子 View 为 match_parent,则子view大小 = 0,spec_mode = UNSPECIFIED
   子 View 为 wrap_content,则子view大小 = 0, spec_mode = UNSPECIFIED
复制代码
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    
     public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);
    
            int size = Math.max(0, specSize - padding);
    
            int resultSize = 0;
            int resultMode = 0;
    
            switch (specMode) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size. So be it.
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } 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;
    
            // Parent has imposed a maximum size on us
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } 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;
                } 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;
    
            // Parent asked to see how big we want to be
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = 0;
                    resultMode = 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);
        }
        
}
复制代码

childWidthMeasureSpec,childHeightMeasureSpec的测量参数由个ViewGroup实现类本身实现或者直接集成ViewGroup的测量方法如measureChild 和 measureChildWithMargins指定的测量参数

protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
复制代码

View 调用子View的measure(childWidthMeasureSpec,childHeightMeasureSpec),实际的测量工做是由View的onMeasure()来完成的。咱们来看看 onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法的实现

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    
       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
           setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                   getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
       }

       //设置View宽高的测量值
       protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
       
       //measureSpec指的是View测量后的大小
       public static int getDefaultSize(int size, int measureSpec) {
           int result = size;
           int specMode = MeasureSpec.getMode(measureSpec);
           int specSize =  MeasureSpec.getSize(measureSpec);
   
           switch (specMode) {
           //MeasureSpec.UNSPECIFIED通常用来系统的内部测量流程
           case MeasureSpec.UNSPECIFIED:
               result = size;
               break;
           //咱们主要关注着两种状况,它们返回的是View测量后的大小
           case MeasureSpec.AT_MOST:
           case MeasureSpec.EXACTLY:
               result = specSize;
               break;
           }
           return result;
       }
       
       //若是View没有设置背景,那么返回android:minWidth这个属性的值,这个值能够为0
       //若是View设置了背景,那么返回android:minWidth和背景最小宽度二者中的最大值。
       protected int getSuggestedMinimumHeight() {
           int suggestedMinHeight = mMinHeight;
   
           if (mBGDrawable != null) {
               final int bgMinHeight = mBGDrawable.getMinimumHeight();
               if (suggestedMinHeight < bgMinHeight) {
                   suggestedMinHeight = bgMinHeight;
               }
           }
   
           return suggestedMinHeight;
       }
}
复制代码

若是咱们直接继承View来自定义View时,须要重写onMeasure()方法,并设置wrap_content时的大小。
经过上面的描述咱们知道,当LayoutParams为wrap_content时,SpecMode为AT_MOST,而在 关于getDefaultSize(int size, int measureSpec) 方法须要说明一下,经过上面的描述咱们知道getDefaultSize()方法中AT_MOST与EXACTLY模式下,返回的 都是specSize,这个specSize是父View当前能够使用的大小,若是不处理,那wrap_content就至关于match_parent。

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      Log.d(TAG, "widthMeasureSpec = " + widthMeasureSpec + " heightMeasureSpec = " + heightMeasureSpec);

      //指定一组默认宽高,至于具体的值是多少,这就要看你但愿在wrap_cotent模式下
      //控件的大小应该设置多大了
      int mWidth = 200;
      int mHeight = 200;

      int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
      int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

      int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
      int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

      if (widthSpecMode == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {
          setMeasuredDimension(mWidth, mHeight);
      } else if (widthSpecMode == MeasureSpec.AT_MOST) {
          setMeasuredDimension(mWidth, heightSpecSize);
      } else if (heightSpecMode == MeasureSpec.AT_MOST) {
          setMeasuredDimension(widthSpecSize, mHeight);
      }
  }
复制代码

们再来看看ViewGroup的measure过程。ViewGroup继承于View,是一个抽象类,它并无重写onMeasure()方法,由于不一样布局类型的测量 流程各不相同,所以onMeasure()方法由它的子类来实现。

如下是FrameLayout的onMeasure()实现,顶层的DecorVeiw也是集成了FrameLayout

protected void onMeasurre(in widthMeasureSpec, int height MeasureSpec){
    final int cont = getChildCount()
    int maxHeight = 0;
    int maxWidth = 0;
    
    for( int i=0; i<count; i++){
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
                   measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                   maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
                   maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
               }
    }
    
     // Account for padding too
   maxWidth += mPaddingLeft + mPaddingRight + mForegroundPaddingLeft + mForegroundPaddingRight;
   maxHeight += mPaddingTop + mPaddingBottom + mForegroundPaddingTop + mForegroundPaddingBottom;

   // Check against our minimum height and width
   maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());//min_height属性或者background的minheight属性的最大值
   maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
   
   final Drawable drawable = getForeground();
   if (drawable != null) {
       maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
       maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
   }

   setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
           resolveSize(maxHeight, heightMeasureSpec));
}
public static int resolveSize(int size, int measureSpec) {
   int result = size;
   int specMode = MeasureSpec.getMode(measureSpec);
   int specSize =  MeasureSpec.getSize(measureSpec);
   switch (specMode) {
   case MeasureSpec.UNSPECIFIED:
       result = size;
       break;
   case MeasureSpec.AT_MOST: //Framelayout 宽高为wrap_content 或者match_parent,specSize为父View的大小,因此不能取size
       result = Math.min(size, specSize);
       break;
   case MeasureSpec.EXACTLY:
       result = specSize;
       break;
   }
   return result;
}
复制代码
  1. 调用measureChildWithMargins()去测量每个子View的大小,找到最大高度和宽度保存在maxWidth/maxHeigth中。
  2. 将上一步计算的maxWidth/maxHeigth加上padding值,mPaddingLeft,mPaddingRight,mPaddingTop ,mPaddingBottom表示当前内容区域的左右上下四条边分别到当前视图的左右上下四条边的距离,

mForegroundPaddingLeft ,mForegroundPaddingRight,mForegroundPaddingTop ,mForegroundPaddingBottom表示当前视图的各个子视图所围成的区域的左右上下四条边到当前视图前景区域的 左右上下四条边的距离,通过计算得到最终宽高。 3. 当前视图是否设置有最小宽度和高度。若是设置有的话,而且它们比前面计算获得的宽度maxWidth和高度maxHeight还要大,那么就将它们做为当前视图的宽度和高度值。 4. 当前视图是否设置有前景图。若是设置有的话,而且它们比前面计算获得的宽度maxWidth和高度maxHeight还要大,那么就将它们做为当前视图的宽度和高度值。 5. 通过以上的计算,就获得了正确的宽高,先调用resolveSize()方法,获取MeasureSpec,接着调用父类的setMeasuredDimension()方法将它们做为当前视图的大小。