zoukankan      html  css  js  c++  java
  • Android-实例理解View measure过程

    这里有两个自定义view,HorizontalScrollViewEx(作用类似水平方向的viewpager),CircleView(简单的画一个圆)。

    代码如下:

      1 public class HorizontalScrollViewEx extends ViewGroup {
      2     private static final String TAG = "HorizontalScrollViewEx";
      3 
      4     private int mChildrenSize;
      5     private int mChildWidth;
      6     private int mChildIndex;
      7 
      8     //     分别记录上次滑动的坐标
      9     private int mLastX = 0;
     10     private int mLastY = 0;
     11     // 分别记录上次滑动的坐标(onInterceptTouchEvent)
     12     private int mLastXIntercept = 0;
     13     private int mLastYIntercept = 0;
     14 
     15     private Scroller mScroller;
     16     private VelocityTracker mVelocityTracker;
     17 
     18     public HorizontalScrollViewEx(Context context) {
     19         super(context);
     20         init();
     21     }
     22 
     23     public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
     24         super(context, attrs);
     25         init();
     26     }
     27 
     28     public HorizontalScrollViewEx(Context context, AttributeSet attrs,
     29                                   int defStyle) {
     30         super(context, attrs, defStyle);
     31         init();
     32     }
     33 
     34     private void init() {
     35         mScroller = new Scroller(getContext());
     36         mVelocityTracker = VelocityTracker.obtain();
     37     }
     38 
     39     @Override
     40     public boolean onInterceptTouchEvent(MotionEvent event) {
     41         boolean intercepted = false;
     42         int x = (int) event.getX();
     43         int y = (int) event.getY();
     44 
     45         switch (event.getAction()) {
     46             case MotionEvent.ACTION_DOWN: {
     47                 intercepted = false;
     48                 if (!mScroller.isFinished()) {
     49                     mScroller.abortAnimation();
     50                     intercepted = true;
     51                 }
     52                 break;
     53             }
     54             case MotionEvent.ACTION_MOVE: {
     55                 int deltaX = x - mLastXIntercept;
     56                 int deltaY = y - mLastYIntercept;
     57                 if (Math.abs(deltaX) > Math.abs(deltaY)) {
     58                     intercepted = true;
     59                 } else {
     60                     intercepted = false;
     61                 }
     62                 break;
     63             }
     64             case MotionEvent.ACTION_UP: {
     65                 intercepted = false;
     66                 break;
     67             }
     68             default:
     69                 break;
     70         }
     71 
     72         Log.d(TAG, "intercepted=" + intercepted);
     73         mLastX = x;
     74         mLastY = y;
     75         mLastXIntercept = x;
     76         mLastYIntercept = y;
     77 
     78         return intercepted;
     79     }
     80 
     81     @Override
     82     public boolean onTouchEvent(MotionEvent event) {
     83         mVelocityTracker.addMovement(event);
     84         int x = (int) event.getX();
     85         int y = (int) event.getY();
     86         switch (event.getAction()) {
     87             case MotionEvent.ACTION_DOWN: {
     88                 if (!mScroller.isFinished()) {
     89                     mScroller.abortAnimation();
     90                 }
     91                 break;
     92             }
     93             case MotionEvent.ACTION_MOVE: {
     94                 int deltaX = x - mLastX;
     95                 int deltaY = y - mLastY;
     96                 scrollBy(-deltaX, 0);
     97                 break;
     98             }
     99             case MotionEvent.ACTION_UP: {
    100                 int scrollX = getScrollX();
    101                 int scrollToChildIndex = scrollX / mChildWidth;
    102                 mVelocityTracker.computeCurrentVelocity(1000);
    103                 float xVelocity = mVelocityTracker.getXVelocity();
    104                 if (Math.abs(xVelocity) >= 50) {
    105                     mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
    106                 } else {
    107                     mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
    108                 }
    109                 mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
    110                 int dx = mChildIndex * mChildWidth - scrollX;
    111                 smoothScrollBy(dx, 0);
    112                 mVelocityTracker.clear();
    113                 break;
    114             }
    115             default:
    116                 break;
    117         }
    118 
    119         mLastX = x;
    120         mLastY = y;
    121         return true;
    122     }
    123 
    124     @Override
    125     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    126         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    127         int measuredWidth = 0;
    128         int measuredHeight = 0;
    129         final int childCount = getChildCount();
    130         Log.e(TAG, "" + MeasureSpec.getSize(widthMeasureSpec));
    131         //测量子view的大小
    132         measureChildren(widthMeasureSpec, heightMeasureSpec);
    133 
    134         int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
    135         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    136         int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
    137         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    138         if (childCount == 0) {
    139             setMeasuredDimension(0, 0);
    140         } else if (heightSpecMode == MeasureSpec.AT_MOST) {
    141             Log.e(TAG, "heightMode:"+heightSpecMode);
    142             final View childView = getChildAt(0);
    143 
    144             //TODO layout_height为wrap_content ==> specMode为AT_MOST,将使用childView.getMeasuredHeight()作为其高度
    145 
    146             setMeasuredDimension(300, childView.getMeasuredHeight());
    147         } else if (widthSpecMode == MeasureSpec.AT_MOST) {
    148             Log.e(TAG, "widthMode:"+widthSpecMode);
    149             final View childView = getChildAt(0);
    150             measuredWidth = childView.getMeasuredWidth() * childCount;
    151             setMeasuredDimension(measuredWidth, heightSpaceSize);
    152         } else {
    153             Log.e(TAG, "heightMode:"+heightSpecMode);
    154             Log.e(TAG, "widthMode:"+widthSpecMode);
    155             final View childView = getChildAt(0);
    156             measuredWidth = childView.getMeasuredWidth() * childCount;
    157             measuredHeight = childView.getMeasuredHeight();
    158             setMeasuredDimension(measuredWidth, measuredHeight);
    159         }
    160     }
    161 
    162     @Override
    163     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    164         int childLeft = 0;
    165         final int childCount = getChildCount();
    166         mChildrenSize = childCount;
    167 
    168         for (int i = 0; i < childCount; i++) {
    169             final View childView = getChildAt(i);
    170             if (childView.getVisibility() != View.GONE) {
    171                 final int childWidth = childView.getMeasuredWidth();
    172                 mChildWidth = childWidth;
    173                 childView.layout(childLeft, 0, childLeft + childWidth,
    174                         childView.getMeasuredHeight());
    175                 childLeft += childWidth;
    176             }
    177         }
    178     }
    179 
    180     private void smoothScrollBy(int dx, int dy) {
    181         mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
    182         invalidate();
    183     }
    184 
    185     @Override
    186     public void computeScroll() {
    187         if (mScroller.computeScrollOffset()) {
    188             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
    189             postInvalidate();
    190         }
    191     }
    192 
    193     @Override
    194     protected void onDetachedFromWindow() {
    195         mVelocityTracker.recycle();
    196         super.onDetachedFromWindow();
    197     }
    198 }
     1 public class CircleView extends View {
     2 
     3     private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     4     private final int mCustomWidth = 400;
     5     private final int mCustomHeight = 400;
     6 
     7     public CircleView(Context context) {
     8         super(context);
     9 
    10         mPaint.setColor(Color.RED);
    11     }
    12 
    13     public CircleView(Context context, AttributeSet attrs) {
    14         super(context, attrs);
    15 
    16         mPaint.setColor(Color.RED);
    17     }
    18 
    19     public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
    20         super(context, attrs, defStyleAttr);
    21         mPaint.setColor(Color.RED);
    22     }
    23 
    24     @Override
    25     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    26         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    27 
    28         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    29         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    30         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    31         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    32 
    33         //宽高都是AT_MOST
    34         if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST)
    35         {
    36             setMeasuredDimension(mCustomWidth, mCustomHeight);
    37         }
    38         else if (widthSpecMode == MeasureSpec.AT_MOST)
    39         {
    40             setMeasuredDimension(mCustomWidth, heightSpecSize);
    41         }
    42         else if (heightSpecMode == MeasureSpec.AT_MOST)
    43         {
    44             setMeasuredDimension(widthSpecSize, mCustomHeight);
    45         }
    46 
    47     }
    48 
    49     @Override
    50     protected void onDraw(Canvas canvas) {
    51         super.onDraw(canvas);
    52 
    53         int width = getWidth();
    54         int height = getHeight();
    55 
    56         int radius = Math.min(width, height) / 2;
    57 
    58         canvas.drawCircle(width / 2, height / 2, radius, mPaint);
    59     }
    60 }

    布局文件:activity_main.xml

     1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     2     android:layout_width="match_parent"
     3     android:layout_height="match_parent">
     4 
     5     <com.dhn.customview.HorizontalScrollViewEx
     6         android:layout_width="wrap_content"
     7         android:layout_height="wrap_content">
     8 
     9         <com.dhn.customview.CircleView
    10             android:layout_width="100dp"
    11             android:layout_height="match_parent" />
    12 
    13         <TextView
    14             android:layout_width="100dp"
    15             android:layout_height="match_parent"
    16             android:background="#AAff0000"
    17             android:text="t1" />
    18 
    19         <TextView
    20             android:layout_width="100dp"
    21             android:layout_height="match_parent"
    22             android:background="#AAff00"
    23             android:text="t1" />
    24 
    25     </com.dhn.customview.HorizontalScrollViewEx>
    26 
    27 </RelativeLayout>

    问题:

    布局文件中父View的高为wrap_content,子View为match_parent。这时它们的高度该如何确定呢?

    因为HorizontalScrollViewEx的高设置为wrap_content所以它的高的SpecMode为AT_MOST(它的parent view和自身的LayoutParam在getChildMeasureSpec中计算得出),我们在自定义的onMeasure()方法中将它的高设置为第一个子View的测量高度(第146行),那么第一个子View的childView.getMeasuredHeight()为多少呢。子View的测量在132行measureChildren(widthMeasureSpec, heightMeasureSpec)中进行,进入该方法:

     1    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
     2         final int size = mChildrenCount;
     3         final View[] children = mChildren;
     4         for (int i = 0; i < size; ++i) {
     5             final View child = children[i];
     6             if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
     7                 measureChild(child, widthMeasureSpec, heightMeasureSpec);
     8             }
     9         }
    10     }

    第7行对第一个子View调用measureChild(child, widthMeasureSpec, heightMeasureSpec)

     1     protected void measureChild(View child, int parentWidthMeasureSpec,
     2             int parentHeightMeasureSpec) {
     3         final LayoutParams lp = child.getLayoutParams();
     4 
     5         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
     6                 mPaddingLeft + mPaddingRight, lp.width);
     7         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
     8                 mPaddingTop + mPaddingBottom, lp.height);
     9 
    10         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    11     }

    这里的getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);返回第一个子View的MeasureSpec,该方法如下:

     1 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
     2         int specMode = MeasureSpec.getMode(spec);
     3         int specSize = MeasureSpec.getSize(spec);
     4 
     5         int size = Math.max(0, specSize - padding);
     6 
     7         int resultSize = 0;
     8         int resultMode = 0;
     9 
    10         switch (specMode) {
    11         // Parent has imposed an exact size on us
    12         case MeasureSpec.EXACTLY:
    13             if (childDimension >= 0) {
    14                 resultSize = childDimension;
    15                 resultMode = MeasureSpec.EXACTLY;
    16             } else if (childDimension == LayoutParams.MATCH_PARENT) {
    17                 // Child wants to be our size. So be it.
    18                 resultSize = size;
    19                 resultMode = MeasureSpec.EXACTLY;
    20             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    21                 // Child wants to determine its own size. It can't be
    22                 // bigger than us.
    23                 resultSize = size;
    24                 resultMode = MeasureSpec.AT_MOST;
    25             }
    26             break;
    27 
    28         // Parent has imposed a maximum size on us
    29         case MeasureSpec.AT_MOST:
    30             if (childDimension >= 0) {
    31                 // Child wants a specific size... so be it
    32                 resultSize = childDimension;
    33                 resultMode = MeasureSpec.EXACTLY;
    34             } else if (childDimension == LayoutParams.MATCH_PARENT) {
    35                 // Child wants to be our size, but our size is not fixed.
    36                 // Constrain child to not be bigger than us.
    37                 resultSize = size;
    38                 resultMode = MeasureSpec.AT_MOST;
    39             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    40                 // Child wants to determine its own size. It can't be
    41                 // bigger than us.
    42                 resultSize = size;
    43                 resultMode = MeasureSpec.AT_MOST;
    44             }
    45             break;
    46 
    47         // Parent asked to see how big we want to be
    48         case MeasureSpec.UNSPECIFIED:
    49             if (childDimension >= 0) {
    50                 // Child wants a specific size... let him have it
    51                 resultSize = childDimension;
    52                 resultMode = MeasureSpec.EXACTLY;
    53             } else if (childDimension == LayoutParams.MATCH_PARENT) {
    54                 // Child wants to be our size... find out how big it should
    55                 // be
    56                 resultSize = 0;
    57                 resultMode = MeasureSpec.UNSPECIFIED;
    58             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    59                 // Child wants to determine its own size.... find out how
    60                 // big it should be
    61                 resultSize = 0;
    62                 resultMode = MeasureSpec.UNSPECIFIED;
    63             }
    64             break;
    65         }
    66         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    67     }

    子View的MeasureSpec由父View的MeasureSpec和自身的LayoutParams 共同决定。这里父view高的MeasureSpec.Mode为AT_MOST,子View高的LayoutParams 为math_parent所以子View高的MeasureSpec也为AT_MOST。

    接下来第10行调用第一个子View的measure()方法:

     1 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
     2         boolean optical = isLayoutModeOptical(this);
     3         if (optical != isLayoutModeOptical(mParent)) {
     4             Insets insets = getOpticalInsets();
     5             int oWidth  = insets.left + insets.right;
     6             int oHeight = insets.top  + insets.bottom;
     7             widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
     8             heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
     9         }
    10 
    11         // Suppress sign extension for the low bytes
    12         long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    13         if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
    14 
    15         final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    16         final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
    17                 MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    18         final boolean matchingSize = isExactly &&
    19                 getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&
    20                 getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    21         if (forceLayout || !matchingSize &&
    22                 (widthMeasureSpec != mOldWidthMeasureSpec ||
    23                         heightMeasureSpec != mOldHeightMeasureSpec)) {
    24 
    25             // first clears the measured dimension flag
    26             mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
    27 
    28             resolveRtlPropertiesIfNeeded();
    29 
    30             int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    31             if (cacheIndex < 0 || sIgnoreMeasureCache) {
    32                 // measure ourselves, this should set the measured dimension flag back
    33                 onMeasure(widthMeasureSpec, heightMeasureSpec);
    34                 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    35             } else {
    36                 long value = mMeasureCache.valueAt(cacheIndex);
    37                 // Casting a long to int drops the high 32 bits, no mask needed
    38                 setMeasuredDimensionRaw((int) (value >> 32), (int) value);
    39                 mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    40             }
    41 
    42             // flag not set, setMeasuredDimension() was not invoked, we raise
    43             // an exception to warn the developer
    44             if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
    45                 throw new IllegalStateException("onMeasure() did not set the"
    46                         + " measured dimension by calling"
    47                         + " setMeasuredDimension()");
    48             }
    49 
    50             mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    51         }
    52 
    53         mOldWidthMeasureSpec = widthMeasureSpec;
    54         mOldHeightMeasureSpec = heightMeasureSpec;
    55 
    56         mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
    57                 (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    58     }

    第33行调用onMeasure(widthMeasureSpec, heightMeasureSpec);对于CircleView我们自定义了该方法:

     1     @Override
     2     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     3         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     4 
     5         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
     6         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
     7         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
     8         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
     9 
    10         //宽高都是AT_MOST
    11         if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST)
    12         {
    13             setMeasuredDimension(mCustomWidth, mCustomHeight);
    14         }
    15         else if (widthSpecMode == MeasureSpec.AT_MOST)
    16         {
    17             setMeasuredDimension(mCustomWidth, heightSpecSize);
    18         }
    19         else if (heightSpecMode == MeasureSpec.AT_MOST)
    20         {
    21             setMeasuredDimension(widthSpecSize, mCustomHeight);
    22         }
    23 
    24     }

    在这里为高设置了我们自定义的值mCustomHeight=400,最终circleView的测量高度为400。

    这样HorizontalScrollViewEx的高度也就确定为400。

    结论:

    自定义Veiw是需要在onMeasure方法中处理AT_MOST这种情况。像TextView的onMeasrue实现中,都对AT_MOST进行了处理。这样当出现parent view为wrap_content,child view为math_parent时才可以确定大小。

  • 相关阅读:
    async await异步方法的理解
    前端读取excel
    js如何实现上拉加载更多
    浅谈控制反转与依赖注入
    Java实现二叉树和遍历
    Linux生产故障排查
    排序算法之快速排序
    排序算法之堆排序
    树和二叉树知识整理
    常用的数据结构简单整理
  • 原文地址:https://www.cnblogs.com/gatsbydhn/p/5158951.html
Copyright © 2011-2022 走看看