View的工作原理和事件體系

View的基礎(chǔ)知識(shí)

View是所有控件的基類错负,ViewGroup繼承了View, ViewGroup表示一個(gè)控件組喧兄,內(nèi)部可以包含多個(gè)控件, 例如LineraLayout就是繼承的ViewGroup,它里面可以包含多個(gè)子控件赫粥。即View可以是單個(gè)控件也可以是多個(gè)控件組成的控件組棠众。

  • View的位置參數(shù)
    View在平移過程中荞怒,left耻讽、top察纯、right、bottom這幾個(gè)值是不會(huì)改變的针肥,改變的是x饼记、y、translationX慰枕、
    translationY這幾個(gè)值

    參數(shù)名 解釋
    left View的左邊界距父容器左邊界的距離
    top View的上邊界距父容器上邊界的距離
    right View的右邊界距父容器左邊界的距離
    bottom View的底邊界距父容器上邊界的距離
    x left+translationX
    y top+translationY
    translationX View的左上角X方向相對(duì)于父容器的偏移量
    translationY View的左上角Y方向相對(duì)于父容器的偏移量
  • MotionEvent和TouchSlop

    • 典型的MotionEvent事件
    名稱 解釋
    ACTION_DOWN 手指剛接觸屏幕
    ACTION_MOVE 手指在屏幕上滑動(dòng)
    ACTION_UP 手指從屏幕上松開的一瞬間
    • 獲取點(diǎn)擊位置的坐標(biāo)
      getX()/getY() 獲取的是相對(duì)于當(dāng)前View左上角的坐標(biāo)
      getRawX()/getRawY() 獲取的是相對(duì)于手機(jī)屏幕左上角的坐標(biāo)
    • TouchSlop
      系統(tǒng)識(shí)別認(rèn)為是滑動(dòng)的最小距離
         /**
          * Distance a touch can wander before we think the user is scrolling in dips.
          * Note that this value defined here is only used as a fallback by legacy/misbehaving
          * applications that do not provide a Context for determining densit   configuration-dependent
          * values.
          *
          * To alter this value, see the configuration resourc config_viewConfigurationTouchSlop
          * in frameworks/base/core/res/res/values/config.xml or the appropriate devic resource   overlay.
          * It may be appropriate to tweak this on a device-specific basis in an overla based on
          * the characteristics of the touch panel and firmware.
          */
         private static final int TOUCH_SLOP = 8;
      
  • VelocityTracker具则、 GestureDetector、 Scroller

    • VelocityTracket
         //獲取實(shí)例
         private var velocityTracker: VelocityTracker = VelocityTracker.obtain()
        
          //添加事件
          /**
           * Add a user's movement to the tracker.  You should call this for the
           * initial {@link MotionEvent#ACTION_DOWN}, the following
           * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
           * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
           * for whichever events you desire.
           * 
           * @param event The MotionEvent you received and would like to track.
           */
         velocityTracker.addMovement(event)
      
         //在MotionEvent#ACTION_UP的時(shí)候計(jì)算速率
         /**
          *@param units 單位為毫秒(millisecond),表示速率的單位時(shí)間
          *       如值是1000,則在1000毫秒內(nèi)滑過100像素(px)具帮,速率就是100
          *       如值是100乡洼,如果在100毫秒內(nèi)同樣滑過100像素,速率也是100
          *       速率 = 終點(diǎn)位置(ACTION_DOWN)- 初始位置(ACTION_DOWN)/(經(jīng)過的時(shí)間/units)
          */
         velocityTracker.computeCurrentVelocity(1000)
         // 獲取x匕坯、y方向上的速率
         val xVelocity = velocityTracker.xVelocity
         val yVelocity = velocityTracker.yVelocity  
      
         //將實(shí)例重置為初始狀態(tài)
         velocityTracker.clear()
         //回收內(nèi)存
         velocityTracker.recycle()
      
    • GestureDetector
      Android手勢(shì)介紹
    • Scroller
      實(shí)現(xiàn)View的彈性滑動(dòng),詳情見下文

View的滑動(dòng)

實(shí)現(xiàn)滑動(dòng)的方式及彈性滑動(dòng)

Android動(dòng)畫介紹

  • 滑動(dòng)
實(shí)現(xiàn)滑動(dòng)的方式
scrollTo()/scrollBy()
View動(dòng)畫
屬性動(dòng)畫
LayoutParams
  1. scrollTo()/scrollBy()
     // 只是移動(dòng)View里面的內(nèi)容拔稳,對(duì)于像ImageView之類的單個(gè)控件來說葛峻,內(nèi)容就是里面的圖片
     // 對(duì)于ViewGroup這種控件組,內(nèi)容就是其中的子View.
     // scrollBy()是相對(duì)目前的scrollX, scrollY進(jìn)行移動(dòng)
     // scrollTo()則是覆蓋之前的scrollX, scrollY進(jìn)行移動(dòng)
     // scrollX == View的左邊坐標(biāo) - View內(nèi)容的左邊坐標(biāo)巴比,即scrollX為負(fù)值時(shí)术奖,View內(nèi)容向右移
     // scrollY == View的頂部坐標(biāo) - View內(nèi)容的頂部坐標(biāo)礁遵, scrollY為負(fù)值時(shí),View內(nèi)容向下移動(dòng)
     SLIDE_MODE_SCROLL -> {
         scrollTo(-event.x.toInt(), 0)
     }

  1. View動(dòng)畫
    //移動(dòng)后采记,點(diǎn)擊響應(yīng)的位置還在原來的區(qū)域
    // left,top,right,bottom屬性不變
    // x, y屬性不變
    SLIDE_MODE_ANIMATION -> {
        val translateTime = (Math.abs(event.x - x) / 100 * 1000).toLong()
        val translateAnimation = TranslateAnimation(x, event.x, y, y).apply {
            duration = translateTime
            fillAfter = true
        }
        this.startAnimation(translateAnimation)
    }

  1. 屬性動(dòng)畫
    //移動(dòng)后佣耐,點(diǎn)擊響應(yīng)的位置在移動(dòng)后的區(qū)域
    // left,top,right,bottom屬性不變
    // translationX,translationY改變 導(dǎo)致x, y屬性改變
    SLIDE_MODE_ANIMATOR -> {
        val translateTime = (Math.abs(event.x - x) / 100 * 100).toLong()
        ObjectAnimator.ofFloat(this, "translationX", x, event.x).setDuratio(translateTime).start()
    }

  1. LayoutParams
    //移動(dòng)后,點(diǎn)擊響應(yīng)的位置在移動(dòng)后的區(qū)域
    // left,top,right,bottom屬性根據(jù)情況改變
    // left,top改變 導(dǎo)致x, y屬性改變
    SLIDE_MODE_LAYOUT_PARAMS -> {
                  (this.layoutParams    ViewGroup.MarginLayoutParams)?.apply {
                      leftMargin = event.x.toInt()
                      this@SampleViewGroup.requestLayout()
                  }
              }
   
  • 彈性滑動(dòng)
  1. 彈性滑動(dòng)實(shí)際上是將一次滑動(dòng)“微分”成一次次小滑動(dòng)唧龄,并在一個(gè)合理的時(shí)間段內(nèi)完成兼砖,而不像普通的滑動(dòng)一次滑動(dòng)就完成所有工作
  2. 實(shí)現(xiàn)彈性滑動(dòng)一般結(jié)合scrollTo()來實(shí)現(xiàn)
實(shí)現(xiàn)彈性滑動(dòng)的方式
使用Scroller
使用屬性動(dòng)畫
延時(shí)策略(Handler#sendMessageDelay())
  1. Scroller
   //初始化時(shí)創(chuàng)建實(shí)例
   private var mScroller: Scroller = Scroller(context)

   //重寫computeScroll實(shí)現(xiàn)
   SLIDE_MODE_SMOOTH_SCROLL -> smoothScrollTo(-event.x.toInt())

   private fun smoothScrollTo(destX: Int) {
       val deltaX = destX - scrollX
       val time = Math.abs(deltaX) / 100 * 1000
       mScroller.startScroll(scrollX, y.toInt(), deltaX, y.toInt(), time)
       //重繪
       invalidate()
  }

   //會(huì)在draw的時(shí)候調(diào)用computeScroll()
   override fun computeScroll() {
      //計(jì)算當(dāng)前的滑動(dòng)偏移
      //滑動(dòng)未結(jié)束則返回true
       if (mScroller.computeScrollOffset()) {
           scrollTo(mScroller.currX, mScroller.currY)
           //scrollTo()之后重繪,達(dá)成彈性滑動(dòng)的效果
           postInvalidate()
       }
   }

  1. 屬性動(dòng)畫
   //動(dòng)畫實(shí)現(xiàn)彈性滑動(dòng)
   SLIDE_MODE_ANIMATOR_SCROLL -> {
       lastScrollX = scrollX
       mScrollX = -event.x.toInt() - scrollX
       ValueAnimator.ofInt(0, 1).apply {
           duration = Math.abs(mScrollX) / 100 * 1000.toLong()
           addUpdateListener {
               it.animatedFraction
               this@SampleViewGroup.scrollTo((it.animatedFraction * mScrollX).toInt() + lastScrollX, 0)
           }
           start()
       }
   }

  1. 延時(shí)策略(Handler#sendMessageDelay())

    //通過Handler#sendMessageDelay實(shí)現(xiàn)彈性滑動(dòng)
     SLIDE_MODE_HANDLER_SMOOTH -> {
         lastScrollX = scrollX
         mScrollX = -event.x.toInt() - scrollX
         intervalX = if (mScrollX < 0) -10 else 10
         mHandler.sendMessage(Message.obtain(nul SLIDE_MODE_HANDLER_SMOOTH, intervalX, 0 ))
     }


     //Handler
     private class SlideHandler(private val weakReference: WeakReference<SampleViewGroup>) : Handler() {
         private var scrollerX = 0
         override fun handleMessage(msg: Message?) {
             if (msg?.what == SLIDE_MODE_HANDLER_SMOOTH) {
                 weakReference.get()?.run {
                     scrollerX += msg.arg1
                     scrollTo(scrollerX + lastScrollX, 0)
                     mScrollX -= intervalX
                     if (Math.abs(mScrollX) >= Math.abs(intervalX)) {
                         mHandler.sendMessageDelayed(Message.obtain(null,     SLIDE_MODE_HANDLER_SMOOTH, intervalX, 0), 100)
                     } else {
                         scrollerX = 0
                     }
                 }
             }
         }
     }

滑動(dòng)沖突

滑動(dòng)沖突場景 處理原則
外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向不一致 根據(jù)x方向滑動(dòng)距離和y方向的滑動(dòng)距里的差值 或 根據(jù)二者的夾角來判斷滑動(dòng)方向既棺,如ViewPager的解決方式
外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向一致 根據(jù)具體需求讽挟,如可以根據(jù)滑動(dòng)的距離,滑動(dòng)起始位置來判斷具體是哪個(gè)部分滑動(dòng)
以上兩者的嵌套
  • 滑動(dòng)沖突處理方式

    滑動(dòng)沖突處理方式
    外部攔截法丸冕,父容器需要就攔截耽梅,不需要就不攔截。重寫父容器的onInterceptTouchEvent
    內(nèi)部攔截法胖烛,父容器也需要配合修改眼姐,子元素需要就直接消耗,重寫dispatchTouchEvent且要配合requestDisallowTouchEvent,參考ViewPager
  1. 外部攔截法-簡單示例
   override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
       var intercepted = false
       when(ev?.action){
           //如果攔截了ACTION_DOWN,后續(xù)的事件都會(huì)在當(dāng)前View中處理
           //子View中的onClick(在ACTION_UP的時(shí)候)之類的事件也不會(huì)觸發(fā)
           MotionEvent.ACTION_DOWN ->{
               intercepted =false
           }
           MotionEvent.ACTION_MOVE ->{
               //是否需要事件
               intercepted = needMotionEvent()
           }
           // 如果在這之前已經(jīng)攔截了佩番,返回true和false 相差不大
           // 如果之前沒有攔截众旗,此處返回了true,那么子View中設(shè)置的onClick(ACTION_UP的時(shí)候)就會(huì)無效
           MotionEvent.ACTION_UP ->{
               intercepted = false
           }
       }
       return intercepted
    }

  1. 內(nèi)部攔截法(可參考ViewPager)-簡單示例
   //子元素
    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
       when(event?.action){
           //ACTION_DOWN在父元素中默認(rèn)不攔截答捕,如果攔截了ACTION_DOWN的話之后的事件都不會(huì)傳遞
           MotionEvent.ACTION_DOWN ->{
               //父元素不攔截事件
               parent.requestDisallowInterceptTouchEvent(true)
           }
           MotionEvent.ACTION_MOVE ->{
               if (parentNeedEvent()){
                   parent.requestDisallowInterceptTouchEvent(false)
               }
           }
       }
       return super.dispatchTouchEvent(event)
   }
   //父元素
   override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
       return ev?.action != MotionEvent.ACTION_DOWN
   }

View的事件分發(fā)

View的事件分發(fā)

Note:
1. 事件的傳遞從Activity開始到View結(jié)束逝钥,如果被消費(fèi)了就不再繼續(xù)傳遞ACTION_DOWN是事件開始的標(biāo)識(shí)拱镐,不會(huì)被攔截艘款。例如重寫了Activity的onTouchEvent方法并返回true,則ACTION_DOWN之后的事件都不會(huì)向下一級(jí)分發(fā)。
2. 事件的消費(fèi)從View開始到Activity結(jié)束沃琅,只能消費(fèi)一次例如一個(gè)自定義View,重寫了onTouchEvent方法并返回true,則表示要消費(fèi)此次事件哗咆,這個(gè)自定義View會(huì)接收ACTION_DOWN之后的事件,而在它的消費(fèi)鏈下一級(jí)(ViewGroup、 Activity)一些用來消費(fèi)事件的方法不會(huì)被調(diào)用(onTouchEvent益眉、一些點(diǎn)擊事件)
3. onTouchListener的執(zhí)行順序在onTouchEvent之前晌柬,如果onTouchListener返回true消費(fèi)了事件則onTouchEvent不會(huì)調(diào)用,一些點(diǎn)擊事件是在onTouchEvent中處理的。
4. 像onClick(ACTION_UP后執(zhí)行),onLongClick(ACTION_DOWN一般延時(shí)500ms后還是press后執(zhí)行)都是在onTouchEvent中執(zhí)行的郭脂,設(shè)置了這些點(diǎn)擊事件則onTouchEvent返回的是true.
5. onLongClick中返回的bool值年碘,true表示消費(fèi)結(jié)束了,其它點(diǎn)擊事件不再響應(yīng)展鸡,false則其他點(diǎn)擊事件還可以響應(yīng).

View的工作原理

基本概念

  • ViewRoot屿衅、DecorView、Window
    1. 在ActivityThread#performLaunchActivity()中執(zhí)行Activity#attach , Activity#attach中創(chuàng)建了PhoneWindow
       mWindow = new PhoneWindow(this, window, activityConfigCallback);
    
    1. 在ActivityThread#handleResumeActivity中調(diào)用了Activity#makeVisiable
     void makeVisible() {
         if (!mWindowAdded) {
             ViewManager wm = getWindowManager();
             wm.addView(mDecor, getWindow().getAttributes());
             mWindowAdded = true;
         }
         mDecor.setVisibility(View.VISIBLE);
     }
    
    1. 在Activity#makeVisible中調(diào)用了WindowManagerImpl#addView
    2. WindowManagerImpl#addView中調(diào)用了WindowManagerGlobal#addView莹弊,在WindowManagerGlobal#addView中創(chuàng)建了ViewRootImpl,
      通過ViewRootImpl#setView將Window相關(guān)屬性和DecorView關(guān)聯(lián)了起來涤久。
       ... 
    
       root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
    
      ...
    
    
    1. ActivityThread#performResumeActivity最終會(huì)執(zhí)行到Activity#onResume涡尘,ActivityThread#handleResumeActivity在performResumeActivity之后。也就是說ViewRootImpl的創(chuàng)建在Activity#onResume回調(diào)執(zhí)行之后
  • 繪制流程

    1. 在ViewRootImpl中
        final class TraversalRunnable implements Runnable {
           @Override
           public void run() {
               doTraversal();
           }
       }
       final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    
        void unscheduleTraversals() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
                mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            }
        }
    
        void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
                if (mProfile) {
                    Debug.startMethodTracing("ViewAncestor");
                }
    
                performTraversals();
    
                if (mProfile) {
                    Debug.stopMethodTracing();
                    mProfile = false;
                }
            }
        }
    
    1. 在ViewRootImpl#perforTranversals中按順序調(diào)用:
      測量:performMeasure -> View#measure -> View#onMeasure
      布局:performLayout -> View#layout -> View#onLayout
      繪制:performDraw -> View#draw -> View#onDraw
    2. 測量中MeasureSpec的獲取
      ViewRootImpl#dispatchResized(將傳進(jìn)來的數(shù)據(jù)包括屏幕信息用Message包裝,用ViewRootHandler發(fā)送) ->
      ViewRootHandler(mWinFrame.set((Rect) args.arg1);) ->
      getRootMeasureSpec(mWinFrame.width/mWinFrame.height,LayoutParams)(根據(jù)寬高及Laoutparams屬性組裝MeasureSpec) ->
      performMeasure(widhMeasureSpec,heightMeasureSpec)

    NOTE:

    1. Android在子線程中更新UI的時(shí)候會(huì)拋出異常响迂,這個(gè)是在ViewRootImpl#checkThread()中處理的
      void checkThread() {
          if (mThread != Thread.currentThread()) {
              throw new CalledFromWrongThreadException(
                      "Only the original thread that created a view hierarchy can touch its views.");
          }
      }
    
    1. ViewRootImpl的創(chuàng)建是在Activity#onResume之后才創(chuàng)建的,所以如果是在ViewRootImpl創(chuàng)建之前在子線程中更新UI是不會(huì)拋出異常的

    2. 原因: 因?yàn)锳ndroid的View控件是非線程安全的考抄,所以要進(jìn)行checkThread(),如果加入線程同步的話會(huì)出現(xiàn)兩個(gè)問題:1. 使邏輯變得復(fù)雜蔗彤;2.鎖機(jī)制會(huì)降低UI訪問效率川梅,因?yàn)樵诙鄠€(gè)線程的情況下會(huì)阻塞一些線程的運(yùn)行

  • MeasureSpec

    1. MeasureSpec是個(gè)32位的int值,高2位表示SpecMode,低30位表示SpecSize
    
       private static final int MODE_SHIFT = 30;
       private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    
       /**
        * Measure specification mode: The parent has not imposed any constraint
        * on the child. It can be whatever size it wants.
        */
       public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
       /**
        * 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.
        */
       public static final int EXACTLY     = 1 << MODE_SHIFT;
    
       /**
        * Measure specification mode: The child can be as large as it wants up
        * to the specified size.
        */
       public static final int AT_MOST     = 2 << MODE_SHIFT;
    
       public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) { // Android版本<=17時(shí)才為true
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
    
    
    UNSPECIFIED 父容器不對(duì)View有任何限制幕与,要多大給多大挑势,對(duì)應(yīng)于系統(tǒng)內(nèi)部的測量狀態(tài)
    EXACTLY 父容器已經(jīng)測量出View所需的精確大小,這時(shí)候View的最終大小就是SpecSize所指定的值啦鸣。對(duì)應(yīng)于LayoutParams中的match_parent和具體數(shù)值的模式
    AT_MOST 父容器指定了一個(gè)可用大小SpecSize潮饱,View的大小不能大于這個(gè)值,最終多大看具體情況诫给,對(duì)應(yīng)于LayoutParams的wrap_content
  • MeasureSpec和LayoutParams的對(duì)應(yīng)關(guān)系
    可查看ViewGroup#measureChild
parentLayoutParams parentSpecMode childLayoutParams childSpecMode childSpecSize
match_parent EXACTLY match_parent EXACTLY availableSize
match_parent EXACTLY wrap_content AT_MOST availableSize
match_parent EXACTLY dp EXACTLY childSize
wrap_parent AT_MOST wrap_content AT_MOST availableSize
wrap_parent AT_MOST match_parent AT_MOST availableSize
wrap_parent AT_MOST dp EXACTLY childSize
dp EXACTLY match_parent EXACTLY availableSize
dp EXACTLY dp EXACTLY childSize
dp EXACTLY wrap_content AT_MOST availableSize
UNSPECIFIED match_parent UNSPECIFIED UNSPECIFIED
UNSPECIFIED wrap_content UNSPECIFIED UNSPECIFIED
UNSPECIFIED dp EXACTLY childSize
1. 如果View是采用固定寬高香拉,不管父容器是什么模式,View都是EXACTLY    
2. 如果父容器是AT_MOST模式中狂,View不是采用固定寬高凫碌,則View也是AT_MOST模式  
3. 如果View是AT_MOST模式,默認(rèn)情況下會(huì)鋪滿剩余的所有空間胃榕,**這樣的話就會(huì)于match_parent是一樣的效果**盛险,所以自定義View的時(shí)候最好對(duì)AT_MOST作自定義處理.

measure

  • measure的流程

    1. View#onMeasure()#getDefaultSize()
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          //setMeasureDimension(),設(shè)置View的測量寬高值
          setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                  getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
      }
    
      public static int getDefaultSize(int size, int measureSpec) {
          int result = size;
          int specMode = MeasureSpec.getMode(measureSpec);
          int specSize = MeasureSpec.getSize(measureSpec);
    
          //根據(jù)測量模式勋又,返回對(duì)應(yīng)的Size
          switch (specMode) {
          case MeasureSpec.UNSPECIFIED:
              result = size;
              break;
          case MeasureSpec.AT_MOST:
          case MeasureSpec.EXACTLY:
              result = specSize;
              break;
          }
          return result;
      }
    
      //mMinWidth是設(shè)置的布局屬性“minWidth"苦掘,默認(rèn)是0
       case R.styleable.View_minWidth:
                  mMinWidth = a.getDimensionPixelSize(attr, 0);
                  break;
      // 如果背景是null的話,取mMinWidth
      // 如果背景不為null的話楔壤,取mMinWidth鹤啡、背景寬度中的最大值  
      // ShapeDrawable沒有原始寬度,BitmapDrawable有原始寬度(圖片尺寸)          
      protected int getSuggestedMinimumWidth() {
          return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
      }
    
    1. ViewGroup#measureChild()蹲嚣、ViewGroup#onMeasure()
      1. ViewGroup中沒有重寫View中的onMeasure而是交給具體的ViewGroup去根據(jù)各自特性實(shí)現(xiàn)
      2. 定義了measureChild(),用于得到childView的MeasureSpec及執(zhí)行childView.measure(),在具體的ViewGroup#onMeasure中調(diào)用递瑰。
         protected void measureChild(View child, int parentWidthMeasureSpec,
                   int parentHeightMeasureSpec) {
               final LayoutParams lp = child.getLayoutParams();
               //根據(jù)父類的MeasureSpec,Padding隙畜,子類的LayoutParams得到傳給子類的MeasureSpec
               final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                       mPaddingLeft + mPaddingRight, lp.width);
               final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                       mPaddingTop + mPaddingBottom, lp.height);
       
               child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
           }
      
      
    • 如何在View的測量執(zhí)行結(jié)束后抖部,獲取View的寬/高
      由于View的measure和Activity的生命周期不是同步的,所以并不能確定在某個(gè)Activity的生命周期內(nèi)议惰,View的measure已經(jīng)完成了慎颗。有以下幾種方式能確切的獲取View的寬高
      1. 重寫Activity#onWindowFocusChanged()
        onWindowFocusChanged()在窗口焦點(diǎn)改變的時(shí)候調(diào)用,調(diào)用這個(gè)方法時(shí)表示View已經(jīng)準(zhǔn)備好了,寬高已經(jīng)能夠準(zhǔn)確獲取哗总。但是會(huì)調(diào)用多次(頻繁地Activity#onResume獲取焦點(diǎn),Activity#onPause失去焦點(diǎn))
  2. view.post(runnable)  
     通過View#post()將一個(gè)**Runnable**添加到消息隊(duì)列的末尾,等到  Looper調(diào)用到此Runnable時(shí)倍试,View已經(jīng)準(zhǔn)備完畢了讯屈。
     
  3.ViewTreeObserver#onGlobalLayoutListener  
    **View樹的狀態(tài)改變,此方法會(huì)被回調(diào)多次**
    ```kotlin
       view.viewTreeObserver().addOnClobalLayoutListener{
           //method body
       }
    ```   
  4.view.measure(widthMeasureSpec,heightMeasureSpec)    
   **View的尺寸是30位二進(jìn)制县习,故(1 << 30) -1)**
  |              |      |
  |:-------------|:-----|
  | match_parent |如果是childView的話涮母,需要知道parentView中的剩余空間,如果是parentView躁愿,則可以作為具體數(shù)值的方式處理(屏幕的寬高)|
  | 具體數(shù)值(50dp)|` widthMeasureSpec = MeasureSpec.makeMeasureSpec(50, MeasureSpec.EXACTLY)`  `heightMeasureSpec = MeasureSpec.makeMeasureSpec(50, MeasureSpec.EXACTLY)`  `view.measure(widthMeasureSpec,heightMeasureSpec)`|
  | wrap_content |` widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<< 30)-1, MeasureSpec.AT_MOST)`  `heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<< 30)-1, MeasureSpec.AT_MOST)`  `view.measure(widthMeasureSpec,heightMeasureSpec)`|

layout

1. layout()中**setFrame**確定元素四個(gè)頂點(diǎn)的位置叛本,調(diào)用onLayout確定子元素的位置
2. 測量寬高默認(rèn)情況下等于最終的寬高源武,但有些特殊情況
   ```java
      //重寫layout方法在塔,改變了四個(gè)頂點(diǎn)的值
      public void layout(int l, int t, int r, int b){
          super.layout(l+50, t, r, b);
      }
   ```  
   **還有就是多次measure的情況,在前幾次的measure中測量寬高可能和最終寬高不同**

draw

   /**
     * If this view doesn't do any drawing on its own, set this flag to
     * allow further optimizations. By default, this flag is not set on
     * View, but could be set on some View subclasses such as ViewGroup.
     *
     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
     * you should clear this flag.
     *
     * @param willNotDraw whether or not this View draw on its own
     */
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

當(dāng)確切知道需要繪制內(nèi)容時(shí)左敌,關(guān)閉WILL_NOT_DRAW逸雹。
ViewGroup中默認(rèn)啟用WILL_NOT_DRAW营搅,View中默認(rèn)關(guān)閉WILL_NOT_DRAW。

  1. 繪制背景
       // Step 1, draw the background, if needed
       if (!dirtyOpaque) {
            drawBackground(canvas);
        }
    
  2. 繪制自身內(nèi)容
       // Step 3, draw the content
       if (!dirtyOpaque) onDraw(canvas);
    
  3. 繪制children
       // Step 4, draw the children
       dispatchDraw(canvas);
    
  4. 繪制裝飾
       // Step 6, draw decorations (foreground, scrollbars)
       onDrawForeground(canvas);
    

自定義View

自定義View

資源

Android開發(fā)藝術(shù)探索 - 任玉剛

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梆砸,一起剝皮案震驚了整個(gè)濱河市转质,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帖世,老刑警劉巖休蟹,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異日矫,居然都是意外死亡赂弓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門搬男,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拣展,“玉大人,你說我怎么就攤上這事缔逛”赴#” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵褐奴,是天一觀的道長按脚。 經(jīng)常有香客問我,道長敦冬,這世上最難降的妖魔是什么辅搬? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上堪遂,老公的妹妹穿的比我還像新娘介蛉。我一直安慰自己,他們只是感情好溶褪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布币旧。 她就那樣靜靜地躺著,像睡著了一般猿妈。 火紅的嫁衣襯著肌膚如雪吹菱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天彭则,我揣著相機(jī)與錄音鳍刷,去河邊找鬼。 笑死俯抖,一個(gè)胖子當(dāng)著我的面吹牛输瓜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚌成,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼前痘,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了担忧?” 一聲冷哼從身側(cè)響起芹缔,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瓶盛,沒想到半個(gè)月后最欠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惩猫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年芝硬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轧房。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拌阴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奶镶,到底是詐尸還是另有隱情迟赃,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布厂镇,位于F島的核電站纤壁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏捺信。R本人自食惡果不足惜酌媒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秒咨,春花似錦喇辽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舅世,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奇徒,已是汗流浹背雏亚。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摩钙,地道東北人罢低。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像胖笛,于是被迫代替她去往敵國和親网持。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容