一起Talk Android吧(第一百四十三回:Android自定义View之Measure七)

各位看官们,大家好,上一回中咱们说的是Android中自定义View之Measure的例子,这一回咱们继续说该例子。闲话休提,言归正转。让我们一起Talk Android吧!

看官们,我们在前面章回中介绍了View的Measure流程,以及通过分析源代码介绍了其中的细节。在本章回中我们将介绍ViewGroup的Measure流程以及具体细节。同时我们将介绍ViewGroup在Measure过程中如何使用MeasureSpec。

Measure的流程在View和ViewGroup中完全一样,不一样的是Meausre的细节。因此我们直接看ViewGroup类中Measure()方法,可是怎么也找不到,那么找一下onMeasure()方法,结果仍然是找不到。看来ViewGroup类没有重写父类的这两个方法。那么它是如何实现Measure的呢?我们只能到它的子类中去找,它的子类比较多,我们以FrameLayout类为例来分析。在该类中只重写了父类的onMeasure()方法,下面是该方法的源代码:

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      int count = getChildCount();

      final boolean measureMatchParentChildren =
              MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
              MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
      mMatchParentChildren.clear();

      int maxHeight = 0;
      int maxWidth = 0;
      int childState = 0;

      for (int i = 0; i < count; i++) {
          final View child = getChildAt(i);
          if (mMeasureAllChildren || child.getVisibility() != GONE) {
              measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
              final LayoutParams lp = (LayoutParams) child.getLayoutParams();
              maxWidth = Math.max(maxWidth,
                      child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
              maxHeight = Math.max(maxHeight,
                      child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
              childState = combineMeasuredStates(childState, child.getMeasuredState());
              if (measureMatchParentChildren) {
                  if (lp.width == LayoutParams.MATCH_PARENT ||
                          lp.height == LayoutParams.MATCH_PARENT) {
                      mMatchParentChildren.add(child);
                  }
              }
          }
      }

      // Account for padding too
      maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
      maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

      // Check against our minimum height and width
      maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
      maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

      // Check against our foreground's minimum height and width
      final Drawable drawable = getForeground();
      if (drawable != null) {
          maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
          maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());

      }

      setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
              resolveSizeAndState(maxHeight, heightMeasureSpec,
                      childState << MEASURED_HEIGHT_STATE_SHIFT));

      count = mMatchParentChildren.size();
      if (count > 1) {
          for (int i = 0; i < count; i++) {
              final View child = mMatchParentChildren.get(i);
              final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

              final int childWidthMeasureSpec;
              if (lp.width == LayoutParams.MATCH_PARENT) {
                  final int width = Math.max(0, getMeasuredWidth()
                          - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                          - lp.leftMargin - lp.rightMargin);
                  childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                          width, MeasureSpec.EXACTLY);
              } else {
                  childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                          getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                          lp.leftMargin + lp.rightMargin,
                          lp.width);
              }

              final int childHeightMeasureSpec;
              if (lp.height == LayoutParams.MATCH_PARENT) {
                  final int height = Math.max(0, getMeasuredHeight()
                          - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                          - lp.topMargin - lp.bottomMargin);
                  childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                          height, MeasureSpec.EXACTLY);
              } else {
                  childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                          getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                          lp.topMargin + lp.bottomMargin,
                          lp.height);
              }

              child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
          }
      }
  }

从上面的代码中可以发现它会依次遍历所有的子View,在遍历时会调用父类的measureChildWithMargins()方法去测量,测量完成后还是使用setMeasuredDimension()方法设置ViewGroup的长宽属性(这点和View的onMeasure方法一样)。

分析完onMeasure()方法的源代码后,我们回头去看看父类(ViewGroup)的measureChildWithMargins()方法,下面是它的源代码:

 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);
 }

从上面的源代码中可以看出,该方法首先通过getChildMeasureSpec()方法获取到子View的测量标准值,然后调用子View的measure()方法完成测量。

好了测量的功能就介绍完了,为了让大家对测量过程有清晰的认识,我们总结了ViewGroup的Measure流程:

doTraversal()->performTraversals()->performMeasurea()->measure()->onMeasure()->measureChildWithMargins()->measure()->onMeasure().

该流程中前三个函数都在ViewRootImpl.java文件中,第四个函数(Measure)在View.java文件中,而第五个函数(onMeasure)在ViewGroup的子类中(比如FragmentLayoutLinearLayout等),第六函数在ViewGroup.java类中,最后两个函数在哪里取决于它们的类,如果类是某个具体的控件,那么就是该控件所属的类中,如果类仍然是ViewGroup或者它的子类,那么流程递归执行,直到布局中没有嵌套的布局为止,或者说遇到控件为止。

这里再补充一点,就是流程中的第六个函数也可能是measureChild()方法,至于使用哪个方法与具体的子类有关,不过大部分子类还是使用流程中提到的方法。

我们把ViewGroup的Measure流程和View的对比一下就是会发现,View是直接实现了Measure相关的方法,而ViewGroup则没有实现,它把Measure的操作交给了它的子类去实现,它之所以这样做是因为不同的子类逻辑不同,大家可以自己去看看LinearLayout,FragmentLayout等ViewGroup的直接子类,整体的思路和我们本章回介绍的类似,只是具体细节不同而已。

各位看官,关于Androd中自定义View之Measure的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!

发布了520 篇原创文章 · 获赞 131 · 访问量 62万+

猜你喜欢

转载自blog.csdn.net/talk_8/article/details/99766765