现在用户的头像都是圆形的,所以大家都会用到圆形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/