ImageView的測量與繪製機制解析

  1. mAdjustViewBounds屬性

    • True when ImageView is adjusting its bounds to preserve the aspect ratio of its drawable
    • 如果允許adjustViewBounds,ScaleType會強行設置爲FIT_CENTER
  2. MaxWidth/MaxHeight機制: 要生效的話,必須setAdjustViewBounds設置爲true:

    • E.g, 要設置一個imageView最大爲100*100, 3 steps:
      1. set adjustViewBounds to true
      2. set maxWidth and maxHeight to 100
      3. set the height and width layout params to WRAP_CONTENT
  3. setImageResource(int resId):

    • This does Bitmap reading and decoding on the UI thread, which can cause a latency hiccup(Bitmap的讀取和解碼都是在UI線程完成的,可能會有小卡頓)
  4. ScaleType:

    1. MATRIX, 使用通過setImageMatrix(Matrix)設置的Matrix來**繪製(嚴格意義上講,Image本身沒有被Matrix改變,改變的是Canvas)**Image
    2. FIT_XY: 對應Matrix.ScaleToFit#FILL:
      • Scale in X and Y independently, so that src matches dst exactly. This may change the aspect ratio of the src.(非等比例放大/縮小Image來完全填充整個View,因此Image的縱橫比可能會被改變)
    3. FIT_START: 對應Matrix.ScaleToFit#START:
      • Compute a scale that will maintain the original src aspect ratio, but will also ensure that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. START aligns the result to the left and top edges of dst(等比例放大/縮小Image,同時確保Image不會超出View, 並且至少在一個維度上Image和View的尺寸是相等的, START在這裏的意思是經過處理的Image會在左上和View對齊).
    4. FIT_CENTER: 對應Matrix.ScaleToFit#CENTER:
      • 基本同FIT_START,唯一區別是Image在View中是居中的
    5. FIT_END: 對應Matrix.ScaleToFit#END:
      • 基本同FIT_END,唯一區別是Image在右下與View對齊
    6. CENTER: Center the image in the view, but perform no scaling
    7. CENTER_CROP: Scale the image uniformly (maintain the image’s aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding). The image is then centered in the view.(非等比例放大Image,確保在每個維度上Image都>=View的尺寸, 居中放置)
    8. CENTER_INSIDE:Scale the image uniformly (maintain the image’s aspect ratio) so that both dimensions (width and height) of the image will be equal to or less than the corresponding dimension of the view (minus padding). The image is then centered in the view.(非等比例放大Image,確保在每個維度上Image都<=View的尺寸, 居中放置)
  5. resolveUri():

    • 解析獲取Drawable的優先級如下:

      1. 如果設置了resourceId, 那麼通過resourceId來獲取。
      2. 如果設置了mUri:

        1. uri的scheme是ContentResolver.SCHEME_ANDROID_RESOURCE(android.resource)

          ContentResolver.OpenResourceIdResult r =
          mContext.getContentResolver().getResourceId(mUri);
          d = r.r.getDrawable(r.id);
          
        2. ContentResolver.SCHEME_CONTENT/ContentResolver.SCHEME_FILE
          stream = mContext.getContentResolver().openInputStream(mUri);
          d = Drawable.createFromStream(stream, null);
        3. 都未滿足: Drawable.createFromPath(mUri.toString());
  6. updateDrawable(Drawable d):

    • 釋放原來的Drawable:
      • mDrawable.setCallback(null)
      • unscheduleDrawable(mDrawable)
    • 設置新的Drawable:

      • d.setCallback(this) ,爲新的Drawable掛載callback
      • 傳遞state和level等狀態信息給新的Drawable。ImageView是Drawable的info hodler。

      • 根據新的Drawable更新本地信息:

      • mDrawableWidth = d.getIntrinsicWidth()
      • mDrawableHeight = d.getIntrinsicHeight()
      • applyColorMod() (根據ImageView保存的Colorfilter/XferMode等信息來設置新的Drawable)
      • configureBounds() (根據當前ImageView的限制來配置新Drawable的bounds)
      • 如果新的Drawable是null,那麼mDrawableWidth = mDrawableHeight = -1
  7. onMeasure:

    • desiredAspect(Desired aspect ratio of the view’s contents (not including padding)
    • boolean resizeWidth = false;(We are allowed to change the view’s width
    • boolean resizeHeight = false;(We are allowed to change the view’s height
    • 若mDrawable == null:
      • mDrawableWidth = -1
      • mDrawableHeight = -1
      • w = h = 0
    • 若mDrawable != null
      • w = mDrawableWidth;
      • h = mDrawableHeight;
      • if (w <= 0) w = 1;
      • if (h <= 0) h = 1;
    • 如果可以mAdjustViewBounds:
      • 如果parentView在width/height給的SpecMode**不是EXACTLY**,那麼相應的resizeWidth/resizeHeight就是true,代表parentView並沒有限定ImageView的尺寸,可以進行尺寸的調整變化
      • desiredAspect = (float) w / (float) h;
    • 如果可以resizeWidth || resizeHeight
      • widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec); (Get the max possible width given our constraints)
      • heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec); (Get the max possible height given our constraints)
      • 如果desiredAspect != 0.0f:
        • actualAspect = (float)(widthSize - pleft - pright) / (heightSize - ptop - pbottom); 得到實際情況下的ratio。
        • 如果實際ratio和desiredAspect不等(Math.abs(actualAspect - desiredAspect) > 0.0000001):
          • 如果可以resizeWidth(調整寬度)
            • 根據desiredAspect和之前resolve出的heightSize算出一個可以滿足desiredAspect的newWidth(理想的寬度,在height不變的情況下)
            • 如果不能resizeHeight並且不能mAdjustViewBoundsCompat, 那麼widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec)(以上一步得到的理想的寬度結合當前的實際寬度限制條件來獲得一個折中的寬度)
            • 如果newWidth <= widthSize,說明當前是折中的寬度是過寬了(那麼有調整寬度的餘地),直接減一下就行: widthSize = newWidth, 能到這一步則視爲按照desireAspect調整到位,而如果newWidth > widthSize, 折中的寬度比理想的要窄,這就沒有辦法了,因爲沒有調整的餘地,只能寄希望於Height做調整了
          • 如果上面沒有調整到位並且可以resizeHeight(既然寬度調節沒有到位,那麼就只能調節高度了)高度調節的邏輯基本和寬度調節的邏輯一致:
            • 同樣根據之前resolve出的widthSize和desiredAspect算出一個理想的newHeight
            • 如果不能resizeWidth並且不能mAdjustViewBoundsCompat,那麼heightSize = resolveAdjustedSize(newHeight, mMaxHeight, heightMeasureSpec)
            • 如果newHeight <= heightSize,說明過高,直接減一下,heightSize = newHeight
      • 由此可見,按照desireAspect來調整width/height**並非一定能完美達到,只能力所能及, 現實總是殘酷的**
    • 如果Width和Height**都不能resize(高寬都不能自行調整,那隻好接受現實了)**:
      • w和h先附加上對應維度的padding。
      • w和h進一步考慮getSuggestedMinimumWidth()/Height(),取大者。
      • resolveSizeAndState(w/h, width/HeightMeasureSpec, 0)來獲得合適的尺寸。
    • 最後setMeasuredDimension(widthSize, heightSize)
  8. resolveAdjustedSize(int desiredSize, int maxSize, int measureSpec)

    1. MeasureSpec.UNSPECIFIED: result = Math.min(desiredSize, maxSize), parent沒有限定尺寸,那麼只要考慮maxSize的限制即可,不能超過maxSize
    2. MeasureSpec.AT_MOST: result = Math.min(Math.min(desiredSize, specSize), maxSize), parent附加了最大值的限制(specSize), 還要同時考慮maxSize這個限制
    3. MeasureSpec.EXACTLY: result = specSize(parent已經強制了具體尺寸, 沒什麼好說的,只能接受)
  9. setFrame(int l, int t, int r, int b):

    • configureBounds(), 到這一步,View的尺寸和位置已經確定下來了,可以設置Drawable的bound了, 進而決定了Image到底會繪製在哪裏
  10. configureBounds()決定了Image(mDrawble)要在哪裏繪製,ScaleType會在這一步被參考,使其生效:

    • vwidthvheight代表去掉Padding後的View尺寸(現實世界, 留給Image進行顯示的區域):
      • vwidth = getWidth() - mPaddingLeft - mPaddingRight
      • vheight = getHeight() - mPaddingTop - mPaddingBottom
    • 如果mDrawableWidth/Height <=0 || ScaleType是ScaleType.FIT_XY
      • If the drawable has no intrinsic size, or we’re told to scaletofit, then we just fill our entire view.
      • mDrawable.setBounds(0, 0, vwidth, vheight)(FIT_XY對應FILL,即填滿整個View,因此直接使用vwidth和vheight來使自己可以填滿整個View)
    • 否則
      • We need to do the scaling ourself, so have the drawable use its native size.(很關鍵的描述,mDrawable的bounds會設置爲其本身的尺寸,(dwidth/dheight),至於裁剪等ScaleType效果則是通過對Canvas執行Matrix運算來實現的
      • mDrawable.setBounds(0, 0, dwidth, dheight)(mDrawable的bounds設爲和mDrawable本身的size一樣)
      • 接下的操作是根據各種條件來得到一個合適的Matrix用於繪製mDrawable**對Canvas進行運算。**
        • 如果View的尺寸就和mDrawable是一樣的,那麼顯然不需要進行Matrix變換
        • ScaleType.CENTER:
          • mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f), (int) ((vheight - dheight) * 0.5f + 0.5f))
          • 不需要Scale,因此只需要Translate一定的距離來確保Image在View中是居中的
        • ScaleType.CENTER_CROP:
          • 如果dwidth * vheight > vwidth * dheight:
            • Image相對View偏寬, 因爲CENTER_CROP不改變Image的縱橫比,那麼爲了實現其描述的效果(Image的width/height >= View的width/height, 並且至少在一個維度上是相等的, 那麼需要以height爲基準進行scale,如果以width爲基準,Image的height就會小於View的Height。
          • 否則:
            • Image相對View偏窄,和上面相反,需要以Width爲基準進行scale。
          • 不管過寬還是過窄,還需要進行Translate來確保Center
        • ScaleType.CENTER_INSIDE:
          • 和CENTER_CROP類似,區別只是在保證Image縱橫比的同時, Image的Width/Height要<=View的Width/Height(Image在View內),因此也需要進行Scale和Translate。
        • 其他ScaleType:
          • 調用mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType))根據View(mTempDst)和Drawable(mTempSrc)的尺寸區域以及ScaleToFit的具體子類型(START/CENTER/END)來得到相應的變化矩陣, 運算在Native層完成。
        • 上面得到的Matrix會保存在mDrawMatrix中,在draw時會被apply到Canvas上, 進而實現ScaleType的效果
  11. onDraw(Canvas canvas), 複雜的工作基本都在configureBounds()中完成了(成果就是mDrawMatrix),真正繪製時的邏輯相對就簡單:

    • 沒有mDrawMatrix(== null)的情況(不需要進行Transform,或者通過設置Drawable的bounds就達到了效果),直接mDrawable.draw(canvas)即可。
    • 有mDrawMatrix:
      • canvas.concat(mDrawMatrix), Matrix(Transformation)在Canvas上生效
      • mDrawable.draw(canvas)(Transform通過Canvas實現了,mDrawable就只需要簡單的draw即可)
  12. 針對不同的ScaleType, ImageView可能改變Drawable也可能改變Canvas

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章