RoundedImageView 处理图片存在毛边的问题

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/android_freshman/article/details/66475760

现在用户的头像都是圆形的,所以大家都会用到圆形imageView 或者用第三方框架 直接处理。我这里是用的RoundedImageView,但是在处理的时候,发现一个问题,就是加载网络图片的时候完全没问题,就是处理默认的图片的时候,会存在毛边,如图:

这里写图片描述

一开始我以为是默认图片的问题,于是查看xml 和默认图片:

这里写图片描述

<!-- android:scaleType="fitXY" 只有设置才不会有毛边,其他都存在缺陷,针对RoundedImageView-->
        <com.xxx.common.widget.roundedimageview.RoundedImageView
            android:id="@+id/avatar"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:contentDescription="@null"
            android:scaleType="centerCrop"
            android:src="@drawable/more_top_avatar_default"
            custom:needShadow="false"
            custom:isCircle="true"
            custom:corner_radius="100dp"
            custom:rounded_border_color="@color/transparent"
            custom:rounded_border_width="0dp"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"/>

这里设置的scaleType=”centerCrop” 是居中多余部分裁剪,原图和xml 都没有问题,很郁闷。于是就查看RoundedImageView的源码进行分析;


1.首先附上解决方案

这里有imageView scaleType 属性的介绍,以前写的

android:scaleType=”fitXY”
只有设置才不会有毛边,针对RoundedImageView

这里写图片描述

当然你也可以设置成
android:scaleType=”center”(保持原图的大小,显示在ImageView的中心。当原图的size大于ImageView的size,超过部分裁剪处理。)
这个被裁剪了很多
这里写图片描述

android:scaleType=”centerCrop”(以填满整个ImageView为目的,将原图的中心对准ImageView的中心,等比例放大原图,直到填满ImageView为止(指的是ImageView的宽和高都要填满),原图超过ImageView的部分作裁剪处理。),
会存在毛边
这里写图片描述

android:scaleType=”centerInside”(以原图完全显示为目的,将图片的内容完整居中显示,通过按比例缩小原图的size宽(高)等于或小于ImageView的宽(高)。如果原图的size本身就小于ImageView的size,则原图的size不作任何处理,居中显示在ImageView。),
会存在如下问题:
这里写图片描述

在我这里 fitXY 是最优方案


2.错误原因分析

下面是imageView 的部分源码:

private void configureBounds() {
        if (mDrawable == null || !mHaveFrame) {
            return;
        }

        int dwidth = mDrawableWidth;
        int dheight = mDrawableHeight;

        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        int vheight = getHeight() - mPaddingTop - mPaddingBottom;

        boolean fits = (dwidth < 0 || vwidth == dwidth) &&
                       (dheight < 0 || vheight == dheight);

        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            /* 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);
            mDrawMatrix = null;
        } else {
            // We need to do the scaling ourself, so have the drawable
            // use its native size.
            mDrawable.setBounds(0, 0, dwidth, dheight);

            if (ScaleType.MATRIX == mScaleType) {
                // Use the specified matrix as-is.
                if (mMatrix.isIdentity()) {
                    mDrawMatrix = null;
                } else {
                    mDrawMatrix = mMatrix;
                }
            } else if (fits) {
                // The bitmap fits exactly, no transform needed.
                mDrawMatrix = null;
            } else if (ScaleType.CENTER == mScaleType) {
                // Center bitmap in view, no scaling.
                mDrawMatrix = mMatrix;
                mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
                                         Math.round((vheight - dheight) * 0.5f));
            } else if (ScaleType.CENTER_CROP == mScaleType) {
                mDrawMatrix = mMatrix;

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) {
                    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 (ScaleType.CENTER_INSIDE == mScaleType) {
                mDrawMatrix = mMatrix;
                float scale;
                float dx;
                float dy;

                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f;
                } else {
                    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);

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(dx, dy);
            } else {
                // Generate the required transform.
                mTempSrc.set(0, 0, dwidth, dheight);
                mTempDst.set(0, 0, vwidth, vheight);

                mDrawMatrix = mMatrix;
                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
            }
        }
    }

注意这一行代码:
mDrawable.setBounds(0, 0, vwidth, vheight);

当为fitxy的时候给mDrawable(也就是我们设置的那张图片)设置了bounds为vwidth,vheight,也就是控件的宽高。所以fitxy时才会铺满整个屏幕的。

其他的放缩模式
// We need to do the scaling ourself, so have the drawable use its native size.


下面看RoundedImageView 的部分源码:

public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundedImageView, defStyle, 0);

        int index = a.getInt(R.styleable.RoundedImageView_android_scaleType, -1);
        if (index >= 0) {
            setScaleType(SCALE_TYPES[index]);
        } else {
            // default scaletype to FIT_CENTER
            setScaleType(ScaleType.FIT_CENTER);
        }

......
....

在构造的时候,会取到xml 中设置的ScaleType

@Override
    public void setScaleType(ScaleType scaleType) {
        assert scaleType != null;

        if (mScaleType != scaleType) {
            mScaleType = scaleType;

            switch (scaleType) {
            case CENTER:
            case CENTER_CROP:
            case CENTER_INSIDE:
            case FIT_CENTER:
            case FIT_START:
            case FIT_END:
            case FIT_XY:
                super.setScaleType(ScaleType.FIT_XY);
                break;
            default:
                super.setScaleType(scaleType);
                break;
            }

            updateDrawableAttrs();
            updateBackgroundDrawableAttrs(false);
            invalidate();
        }
    }

super.setScaleType(scaleType); 去到imageView 的源码中了

/**
     * Controls how the image should be resized or moved to match the size
     * of this ImageView.
     * 
     * @param scaleType The desired scaling mode.
     * 
     * @attr ref android.R.styleable#ImageView_scaleType
     */
    public void setScaleType(ScaleType scaleType) {
        if (scaleType == null) {
            throw new NullPointerException();
        }

        if (mScaleType != scaleType) {
            mScaleType = scaleType;

            setWillNotCacheDrawing(mScaleType == ScaleType.CENTER);            

            requestLayout();
            invalidate();
        }
    }


/**
     * When a View's drawing cache is enabled, drawing is redirected to an
     * offscreen bitmap. Some views, like an ImageView, must be able to
     * bypass this mechanism if they already draw a single bitmap, to avoid
     * unnecessary usage of the memory.
     *
     * @param willNotCacheDrawing true if this view does not cache its
     *        drawing, false otherwise
     */
    public void setWillNotCacheDrawing(boolean willNotCacheDrawing) {
        setFlags(willNotCacheDrawing ? WILL_NOT_CACHE_DRAWING : 0, WILL_NOT_CACHE_DRAWING);
    }

/ **
*当View的绘图缓存启用时,绘图被重定向到屏幕位图。一些视图,像ImageView,必须能够
*绕过这个机制,如果他们已经画了一个位图,以避免不必要的使用内存。
*
* @param willNotCacheDrawing如果此视图不缓存它,则为true绘图,否则为false
* /

setWillNotCacheDrawing 解释

然后就开始刷新了
requestLayout();
invalidate();

requestLayout:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。
特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。

invalidate:View本身调用迫使view重画。

Android View 深度分析requestLayout、invalidate与postInvalidate


下面会看到这里 RoundedImageView updateDrawableAttrs()

private void updateAttrs(Drawable drawable) {
        if (drawable == null) {
            return;
        }

        if (drawable instanceof RoundedDrawable) {
            ((RoundedDrawable) drawable).setScaleType(mScaleType).setCornerRadius(cornerRadius).setBorderWidth(borderWidth)
                    .setBorderColor(borderColor).setOval(isOval);
        } else if (drawable instanceof LayerDrawable) {
            // loop through layers to and set drawable attrs
            LayerDrawable ld = ((LayerDrawable) drawable);
            for (int i = 0, layers = ld.getNumberOfLayers(); i < layers; i++) {
                updateAttrs(ld.getDrawable(i));
            }
        }
    }

setScaleType(mScaleType) 层层点下来会跑到,只看CENTER_CROP 为什么有毛边

private void updateShaderMatrix() {
        float scale;
        float dx;
        float dy;

        switch (mScaleType) {
        .....
        case CENTER_CROP:
            mBorderRect.set(mBounds);
            mBorderRect.inset((mBorderWidth) / 2, (mBorderWidth) / 2);

            mShaderMatrix.set(null);

            dx = 0;
            dy = 0;

            if (mBitmapWidth * mBorderRect.height() > mBorderRect.width()
                    * mBitmapHeight) {
                scale = mBorderRect.height() / (float) mBitmapHeight;
                dx = (mBorderRect.width() - mBitmapWidth * scale) * 0.5f;
            } else {
                scale = mBorderRect.width() / (float) mBitmapWidth;
                dy = (mBorderRect.height() - mBitmapHeight * scale) * 0.5f;
            }

            mShaderMatrix.setScale(scale, scale);
            mShaderMatrix.postTranslate((dx - 2.0f) + mBorderWidth,
                    (dy - 1.0f) + mBorderWidth);
            break;

        .....
        .....

        case FIT_XY:
            mBorderRect.set(mBounds);
            mBorderRect.inset((mBorderWidth) / 2, (mBorderWidth) / 2);
            mShaderMatrix.set(null);
            mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect,
                    Matrix.ScaleToFit.FILL);
            break;
        }

        mDrawableRect.set(mBorderRect);
        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

3.总结

两者的区别 :

CENTER_CROP

....
mShaderMatrix.setScale(scale, scale);

/ **
   *用指定的翻译后缀矩阵。
   * M'= T(dx,dy)* M
* /
mShaderMatrix.postTranslate((dx - 2.0f) + mBorderWidth,(dy - 1.0f) + mBorderWidth);


FIT_XY

/ **
   *将矩阵设置为比例并转换映射源的值
   *矩形到目标矩形,返回true如果结果可以代表。
   * @param src要从中映射的源矩形。
   * @param dst要映射到的目标矩形。
   * @param stf ScaleToFit选项
   *如果矩阵可以用矩形映射表示,则返回true。
* /
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect,Matrix.ScaleToFit.FILL);

可能是放缩后,矩阵再位移的时候存在细微的偏差导致出现毛边,而fitXY 直接绘制未改变原矩行大小。
数学不好,在具体也没有很明白。=。=、、、

相关的参考资料:

1.相关api 说明:
https://developer.xamarin.com/api/member/Android.Views.View.SetWillNotCacheDrawing/p/System.Boolean/

2.Android项目中遇到的坑之(Android圆角圆形图 二)

3.Android图片圆角转换 RoundedImageView开源项目 小记

猜你喜欢

转载自blog.csdn.net/android_freshman/article/details/66475760