preface

ImageView is one of the most basic Android controls. Through ImageView, we can display all kinds of pictures. The research on its principle helps us to use it better. Through this article, you will learn:

AdjustViewBounds (adjustViewBounds) 2, adjustViewBounds (adjustViewBounds) 3, adjustViewBounds (adjustViewBounds) 3, adjustViewBounds (adjustViewBounds

ImageView size determination

The ImageView inherits from the View, and we know that the size of the View is ultimately determined in the onMeasure method. See if the ImageView does anything to this method:

Protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// omit int w; int h; if (mDrawable == null) { mDrawableWidth = -1; mDrawableHeight = -1; w = h = 0; } else {//mDrawableWidth = mDrawableWidth; h = mDrawableHeight; if (w <= 0) w = 1; if (h <= 0) h = 1; } the if (resizeWidth | | resizeHeight) {/ / about adjustViewBounds processing / / omitted} else {w + = pleft + pright; h += ptop + pbottom; // Find a large value and make sure you can accommodate w = math.max (w, getstedminimumWidth ()); h = Math.max(h, getSuggestedMinimumHeight()); / / widthMeasureSpec, heightMeasureSpec is the size of the parent for the ImageView distribution, h/w content / / this method is combined with the size of the parent to the size of the size and content, finally calculate the View really need / / the size of the specific rules are as follows: // If the parent controls the measurement mode is: EXACTLY, then the ImageView will take the value of the spec. WidthSize = resolveSizeAndState(w, widthMeasureSpec, 0); widthMeasureSpec = resolveSizeAndState(w, widthMeasureSpec, 0); heightSize = resolveSizeAndState(h, heightMeasureSpec, 0); } / / in the end, after calculating the size of the save to mMeasuredWidth/mMeasuredHeight, ImageView is finished setMeasuredDimension (widthSize heightSize); }Copy the code

ImageView overrides the onMeasure method. As you can see above, the size of the ImageView depends on the size of the content and the size of the parent control.

Small example

First, select an image test2.jpg and place it in the Drawable/nodpi directory. (Read the image’s original size without compression. The image is 592 x 258 pixels in length and width.

<ImageView Android :id="@+id/iv" Android :background="@color/colorAccent" android:src="@drawable/test2" android:layout_width="wrap_content" android:layout_height="wrap_content"> </ImageView> // EXACTLY the same size as the parent control <ImageView Android :id="@+id/iv" Android :background="@color/colorAccent" Android: SRC ="@drawable/test2" android:layout_width="100dp" android:layout_height="wrap_content"> </ImageView>Copy the code

Both have a background that makes it easy to visualize the size of the ImageView (a more accurate comparison is to print the size of the ImageView). Look at the results:



When ImageView uses “wrap_content”, its size depends on the size of the content

ImageView scaleType attribute

How to determine the size of the ImageView, but if you set a fixed width and height for the ImageView, and the image size is different, how do you determine how the image will appear in the ImageView? ImageView provides a “scaleType” attribute to customize different presentation styles.

    public enum ScaleType {
        MATRIX      (0),

        FIT_XY      (1),

        FIT_START   (2),

        FIT_CENTER  (3),

        FIT_END     (4),

        CENTER      (5),

        CENTER_CROP (6),

        CENTER_INSIDE (7);
    }
Copy the code

Let’s see what happens when you set different ScaleTypes.

Image size: 182 * 538(PX) ImageView size: 100 * 100 (DP), test device density is 2.75, converted to pixels: 275x275px For dp and PX please refer to :Android screen resolution adaptation in order to more intuitive to see the size of the control and image size, the control size added a red background. You can see that the width of the picture is smaller than the width of the control, the picture is taller than the height of the control. Take a look at the presentation in each mode. Each control has a corresponding scaleType on it.

1. The original picture is displayed at the bottom of the screen without zooming. 2, Matrix: The picture is not zoomed, according to the normal layout (matrix if the transformation is set, there may be zooming, panning and other operations), display from the upper left corner, beyond the part is not displayed. 3, fitXY: the picture is not equal scale, the width and height of the picture is limited within the control and full of the control around. 4. FitStart: Scale the picture in proportion to limit the width and height of the picture within the control. Scaling rules: Both sides need to be scaled into the control, and at least one side is flush with a side of the control. Zoom in and show it from the top left corner. 5. FitCenter: The scaling rule is the same as fitStart, except that after scaling, the picture is displayed in the center. 6. FitEnd: The scaling rule is the same as fitStart, except that after scaling, it will be displayed from the lower right corner. 7, Center: no zoom, the picture center display. 8, centerCrop: equal scale scaling, scaling rules: the length and width of the image should be greater than or equal to the length and width of the control. Center display. 9, centerInside: equal scale scaling, scaling rules, the length and width of the picture is larger than the size of the control, then scaling, both sides need to be scaled into the control. If the image is smaller than the control size, it is not scaled. Center the display regardless of whether it is scaled or not.

Most articles on scaleType explanation stop here, read easy to forget, the main principle is not expanded will be told, next we from the source point of view of in-depth analysis, let the memory more profound.

ImageView ScaleType source code implementation

ScaleType is implemented in the ImageView configureBounds method, which takes effect after layout.

private void configureBounds() { if (mDrawable == null || ! mHaveFrame) { return; } final int dwidth = mDrawableWidth; final int dheight = mDrawableHeight; Final int vwidth = getWidth() -mpaddingLeft - mPaddingRight; final int vheight = getHeight() - mPaddingTop - mPaddingBottom; final boolean fits = (dwidth < 0 || vwidth == dwidth) && (dheight < 0 || vheight == dheight); If (dwidth < = 0 | | dheight < = 0 | | ImageView. ScaleType. FIT_XY = = mScaleType) {/ / set the drawable size is consistent with the controls MDrawable. SetBounds (0, 0, vWidth, vheight); mDrawMatrix = null; MDrawable. SetBounds (0, 0, dwidth, dheight);} else {// Draw the bounds to the same size as the picture. If (ImageView) ScaleType) MATRIX = = mScaleType) {/ / using MATRIX if (mMatrix. IsIdentity ()) {/ / unit MATRIX, do not affect transform mDrawMatrix = null; } else { mDrawMatrix = mMatrix; }} else if (fits) {mDrawMatrix = null; } else if (ImageView. ScaleType. CENTER = = mScaleType) {/ / translation, making the image CENTER shows / / the target container is a control that is picture mDrawMatrix = mMatrix; Mdrawmatrix. setTranslate(math. round((vwidth-dwidth) * 0.f), math. round((vheight-dheight) * 0.f)); mdrawmatrix. setTranslate(math. round((vwidth-dwidth) * 0.f)); } else if (ImageView.ScaleType.CENTER_CROP == mScaleType) { mDrawMatrix = mMatrix; float scale; float dx = 0, dy = 0; If (dwidth * vheight > vwidth * dheight) { //dwidth * vheight > dwidth * dheight //dwidth /vwidth > dheight //vwidth/dheight <= vheight/dheight // So it is judged that: If control/picture width ratio is less than the proportion of high / / so high, the proportion of the scaling is the proportion of larger value / /, for example, high image width would be higher than controls, and control high and image high proportion bigger, zoom at this time, before the picture of high / / scaling to control is high, the image width is not wide, and scaling to control. Scale = (float) vheight/(float) dheight; Dx = (vwidth - dwidth * scale) * 0.5f; } else { scale = (float) vwidth / (float) dwidth; Dy = (vheight - dheight * scale) * 0.5f; } mDrawMatrix.setScale(scale, scale); mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy)); } else if (ImageView.ScaleType.CENTER_INSIDE == mScaleType) { mDrawMatrix = mMatrix; float scale; float dx; float dy; If (dwidth <= vwidth && dheight <= vheight) {scale = 1.0f; } else {// In contrast to the centerCrop pattern, the judgment here is that if the control/image width ratio is less than its height // then the scale takes the width ratio, that is, the scale of the smaller value. // Scale = math.min ((float) vwidth/(float) dwidth, (float) vheight/(float) dheight); } dx = math. round((vwidth - dwidth * scale) * 0.5f); Dy = Math. Round ((vheight-dheight * scale) * 0.5f); // scale first, along the default point (0,0) mdrawmatrix.setscale (scale, scale); / / translation again, makes the picture center mDrawMatrix. The postTranslate (dx, dy); } else {//fitStart //fitEnd //fitCenter mtempsrc.set (0, 0, dwidth, dheight); mTempDst.set(0, 0, vwidth, vheight); mDrawMatrix = mMatrix; / / the focus is on the mDrawMatrix. SetRectToRect (mTempSrc mTempDst, scaleTypeToScaleToFit (mScaleType)); }}}Copy the code

The above code has a comment, should be more detailed. Then see mDrawMatrix. SetRectToRect (mTempSrc mTempDst, scaleTypeToScaleToFit (mScaleType)); Methods:

Enum ScaleToFit {kFill_ScaleToFit, kStart_ScaleToFit, Fit_Center kEnd_ScaleToFit, fit_End}; bool SkMatrix::setRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit align) { if (src.isEmpty()) { this->reset(); return false; } if (dst.isEmpty()) { sk_bzero(fMat, 8 * sizeof(SkScalar)); fMat[kMPersp2] = 1; this->setTypeMask(kScale_Mask | kRectStaysRect_Mask); } else {SkScalar TX, sx = dst.width()/src.width(); // sx = dst.width()/src.width(); SkScalar ty, sy = dst.height() / src.height(); bool xLarger = false; // fit if (align! If (sx > sy) {xLarger = true; // If (sx > sy) {xLarger = true; sx = sy; } else { sy = sx; } } tx = dst.fLeft - src.fLeft * sx; ty = dst.fTop - src.fTop * sy; if (align == kCenter_ScaleToFit || align == kEnd_ScaleToFit) { SkScalar diff; Diff = dst.width() -src.width () * sy; } else { diff = dst.height() - src.height() * sy; } if (align == kCenter_ScaleToFit) {// If fit_center = fit_center SkScalarHalf=diff/2 diff = SkScalarHalf(diff); } if (xLarger) {// If the scale is high, the x direction will be shifted // If fit_center mode, the diff is already bismuted // If fit_end mode, the diff will be shifted to the lower right of the flush tx += diff;  } else { ty += diff; This ->setScaleTranslate(sx, sy, tx, ty); this->setScaleTranslate(sx, sy, tx, ty); } return true; }Copy the code

1, fit_start, FIT_center, FIT_end zoom rules are similar to center_inside, but center_inside image width, one of the height is larger than the width of the control. 2. The scaling of the image will eventually be implemented into the matrix transformation. ImageView scaleType is also the embodiment of the classic application of Matrix. It should be noted that the Matrix scale here is based on the upper-left corner (0,0). 3. ScaleType only changes the display mode of pictures, but does not reduce or increase the memory occupation of pictures. 4, scaleType work is actually how to make content on the control to do different display, here the idea of using more, such as video player, how to make video fill high or wide, and center playback.

ScaleType default values

    private void initImageView() {
        mMatrix = new Matrix();
        mScaleType = ScaleType.FIT_CENTER;

        if (!sCompatDone) {
            final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
            sCompatAdjustViewBounds = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
            sCompatUseCorrectStreamDensity = targetSdkVersion > Build.VERSION_CODES.M;
            sCompatDrawableVisibilityDispatch = targetSdkVersion < Build.VERSION_CODES.N;
            sCompatDone = true;
        }
    }
Copy the code

You can see that the default scaleType is FIT_CENTER mode.

How to use ImageView “adjustViewBounds”

ScaleType is the control size invariable, the image fits the size of the control. Can the size of the control change with the size of the image? The answer is yes, through adjustViewBounds, as the name suggests. We all know that the size of the control is determined in the onMeasure method. We have omitted some code when analyzing the ImageView onMeasure method:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mDrawable == null) { // If no drawable, its intrinsic size is 0. mDrawableWidth = -1; mDrawableHeight = -1; w = h = 0; } else {AdjustViewBounds () {adjustbounds (mAdjustViewBounds) { Control size is fixed, there is no need to recalculate resizeWidth = widthSpecMode! = MeasureSpec.EXACTLY; resizeHeight = heightSpecMode ! = MeasureSpec.EXACTLY; DesiredAspect = (float) w/(float) h; }} the if (resizeWidth | | resizeHeight) {/ / calculation control wide widthSize = resolveAdjustedSize (w + pleft + pright, mMaxWidth, widthMeasureSpec); ResolveAdjustedSize = resolveAdjustedSize(h + pTOP + pbottom, mMaxHeight, heightMeasureSpec); if (desiredAspect ! Final Float actualAspect = (float)(widthSize - pleft-pright)/(heightSize - ptop-pbottom); If (math. abs(actualAspect - desiredAspect) > 0.0000001) {Boolean done = false; If (resizeWidth) {// recalculate width In terms of actual picture scale, Int newWidth = (int)(desiredAspect * (heightSize - ptop-pbottom)) + pleft + pright; if (! resizeHeight && ! sCompatAdjustViewBounds) { widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec); } if (newWidth <= widthSize) {// If (newWidth <= widthSize) {// If (newWidth <= widthSize) {// If (newWidth <= widthSize) {// If (newWidth <= widthSize); done = true; } } // Try adjusting height to be proportional to width if (! done && resizeHeight) { int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom; // Allow the height to outgrow its original estimate if width is fixed. if (! resizeWidth && ! sCompatAdjustViewBounds) { heightSize = resolveAdjustedSize(newHeight, mMaxHeight, heightMeasureSpec); } if (newHeight <= heightSize) { heightSize = newHeight; }}}}} else {// omit} setMeasuredDimension(widthSize, heightSize); }Copy the code

Use this attribute in XML

        <ImageView
            android:adjustViewBounds="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/test_small"></ImageView>
Copy the code

No matter how big the picture is, the control will be large, following the size of the picture. The picture does not zoom in or pan. AdjustViewBounds property and scaleType property are two different functions. AdjustViewBounds property is used to control the size of controls, while scaleType property is used to control the size of pictures.

ImageView and Drawable

When demonstrating the scaleType property, the ImageView references the BitmapDrawable, which holds the Bitmap object and ultimately displays the Bitmap on the view. The general process is as follows:

ImageView->onDraw()->Drawable->draw()->canvas.drawXXX();

We know that the View needs to be displayed on the screen, and we end up calling the Canvas set of methods in the onDraw() method, for example:

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(bitmap, null, new Rect(0, 0, 100, 258), paint);
    }
Copy the code

If the drawing in onDraw has a general part to show, it can be raised, such as:

private void draw(Canvas canvas) { canvas.drawBitmap(bitmap, null, new Rect(0, 0, 100, 258), paint); canvas.drawXX(); }}Copy the code

So in the View’s onDraw() method just call the common draw() method to achieve different effects. In fact, this is how Drawable is used. We encapsulate some generic effects into different drawables, holding the Drawable object in the View, and eventually calling Drawable’s draw() method in the View’s onDraw(). Drawable requires two elements

1. Set the drawable size by setBounds(). 2. Override the draw() method, which uses drawable limits

If you have any questions, please leave a comment.