自定義View——Layout

  1. 主要思路
    ViewGroup的遍歷子節(jié)點(diǎn),通過setFrame存儲位置信息
  2. 主體函數(shù)
    View.layout()View.onLayout(),View.setFrame()
  3. layout(int l, int t, int r, int b)
    • 作用:為自身及其子View分配大小與位置
    • 如何開始:ViewRootImplperformTravesals中調(diào)用DecorView.layout()
    • 相關(guān)源碼:
      android/view/ViewRootImpl.java
      private void performTraversals() {
          performLayout(lp, mWidth, mHeight);
      }
      private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
              int desiredWindowHeight) {
          final View host = mView;
          //執(zhí)行DecorView的layout方法,傳入的是measuredWidth,measuredHeight 因此得先measure,后layout
          host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
      }
      
      android/view/View.java
      public void layout(int l, int t, int r, int b) {
          //判斷是否需要重新測量
          if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
              onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
              mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
          }
          //存儲之前的l t r b信息
          int oldL = mLeft;
         int oldT = mTop;
         int oldB = mBottom;
         int oldR = mRight;
          //確定布局是否發(fā)生了變化,并存儲新的l t r b
         boolean changed = isLayoutModeOptical(mParent) ?
                 setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
             if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
              //執(zhí)行布局相應(yīng)的onLayout()
                 onLayout(changed, l, t, r, b);
              ListenerInfo li = mListenerInfo;
             if (li != null && li.mOnLayoutChangeListeners != null) {
                 ArrayList<OnLayoutChangeListener> listenersCopy =
                      (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                 int numListeners = listenersCopy.size();
                 for (int i = 0; i < numListeners; ++i) {
                     //當(dāng)布局發(fā)生變化時,通知觀察者布局變化通知
                     listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                 }
             }
          }
      }
      
  4. setFrame(int left, int top, int right, int bottom)
    • 作用:存儲傳入的left,topright,bottom信息 并確定布局是否發(fā)生變化,如果發(fā)生變化,返回true
    • 相關(guān)源碼:
      android/view/View.java
      protected boolean setFrame(int left, int top, int right, int bottom) {
          boolean changed = false;
          //如果傳入的位置信息跟之前的位置信息不同
          if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
              //則說明布局發(fā)生改變
             changed = true;
              //重新存儲位置信息
              //這里注意一點(diǎn),mLeft,mTop,mRight,mBottom都是相對位置,不是坐標(biāo)系的絕對位置
              //是相對于父view的位置大小
              mLeft = left;
             mTop = top;
             mRight = right;
             mBottom = bottom;
          }
          //返回布局是否發(fā)生改變
          return changed;
      }
      
  5. onLayout(boolean changed, int left, int top, int right, int bottom)
    • 作用:確定子View的位置與大小,所以,如果是View,onLayout是空實(shí)現(xiàn),而如果是ViewGrouponLayout方法是抽象,必須有具體的子類根據(jù)不同策略來實(shí)現(xiàn)。
    • 相關(guān)源碼:
      android/view/View.java
      //空實(shí)現(xiàn)
      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
      }
      
      android/view/ViewGroup.java
      //抽象方法
      protected abstract void onLayout(boolean changed,
                  int l, int t, int r, int b);
      
  6. getWidth()/getHeight()
    • 作用:setFrame()存儲布局傳入的信息,View的寬高,也是最終的寬高。
    • 相關(guān)源碼:
      public final int getWidth() {
          //根據(jù)布局傳入的right - left得到寬
          return mRight - mLeft;
      }
      public final int getHeight() {
          //根據(jù)布局傳入的bottom - top得到高
          return mBottom - mTop;
      }
      
  7. 例子
    • LinearLayout來舉例onLayoutViewGroup中是如何實(shí)現(xiàn)的
    • 相關(guān)源碼:
      android/widget/LinearLayout.java
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          //根據(jù)LinearLayout的方向來決定子view的布局
          if (mOrientation == VERTICAL) {
              layoutVertical(l, t, r, b);
          } else {
              layoutHorizontal(l, t, r, b);
          }
      }
      
      void layoutVertical(int left, int top, int right, int bottom) {
          //獲取子view個數(shù)
          final int count = getVirtualChildCount();
          //遍歷子view
          for (int i = 0; i < count; i++) {
             //這里我們不關(guān)注子view的left top right bottom是如何計(jì)算的 主要關(guān)注整體布局流程
             setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                 childWidth, childHeight);
          }
      }
      
      private void setChildFrame(View child, int left, int top, int width, int height) {
          //執(zhí)行child.layout,繼續(xù)完成子類的布局設(shè)置
          child.layout(left, top, left + width, top + height);
      }
      
  8. 簡要流程
  9. 其他要點(diǎn)
    • getWidth/getHeight是在setFrame()中得到的,也就是在layout流程后才能獲取到,也是View的最終寬高。
    • layout流程中,用到的left,topright,bottom都是相對于父View的位置,不是坐標(biāo)系的絕對位置。
  10. 系列文章
    1. View的背景知識
    2. View的測量流程
    3. View的布局流程
    4. View的繪制背景知識
    5. View的繪制流程
    6. View的三大繪制流程總結(jié)
    7. View的事件分發(fā)機(jī)制
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容