ImageLoader是款非常好的开源图片加载控件,android中加载图片是一个非常头疼的事,图片列表卡,内存溢出等,项目中自从用了ImageLoder之后这种问题省心多了,尽管给链接给控件图片就加载出来,项目中用了两年了控件了,只知道传两个参数就可以显示出图片,但对具体的实现原理一知半解,花了一些时间能Imageloader的源码进行了研究,发现好多知识都不曾知道。
Imageloder中用提供了四中图片的显示式,圆形,淡入(动画),圆角,修饰,项目中显示圆角,圆形的图片是做了一个白色的中间透明的图片覆盖到原图的上面形成的圆形的效果,很Low,用ImageLoader这些完全不用。
在控件的核心包中有下面的display包里面就是用来显示图片代码
这六个类的关系简单说一下
BitmapDisplayer是一个接口,
CircleBitmapDislayer、RoundedBitmapDisplayer、SimpleBitmapDisplayer、FadeInbitmapDisplayer实现了BitmapDisplayer接口,
RoundedVignetteBitmapDispler继承了RoundedBitmapDisplayer.
BitmapDisplayer接口
public interface BitmapDisplayer {
void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom);
}
中只有一个方法display,
第二个参数IamgeAware是控件中对Imageview的封装,可以理解为IamgeView,
第三方参数
LoadedFrom是一个枚举类,图片是从那加载的,有四种:
NETWORK,从网络加载
DISC_CACHE, 从磁盘缓存中加载
MEMORY_CACHE 从内存缓存中加载
SimpleBitmapDisplayer是直接显示图片对图片没有进行任何处理
public final class SimpleBitmapDisplayer implements BitmapDisplayer {
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
}
}
CircleBitmapDislayer分析
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
if (!(imageAware instanceof ImageViewAware)) {
throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected.");
}
imageAware.setImageDrawable(new CircleDrawable(bitmap, strokeColor, strokeWidth));
}
实现了接口BitmapDispalyer接口中的方法display,实现比较简单先判断了用于显示图片的是不是IamgeView,如果不是抛出异常,后面的那一句相当于ImageView中的SetImageDrawable(Drawable图片);
CircleDrawable是一个内部类,继承Drawable.
public static class CircleDrawable extends Drawable {
protected float radius;
protected final RectF mRect = new RectF();
protected final RectF mBitmapRect;
protected final BitmapShader bitmapShader;
protected final Paint paint;
protected final Paint strokePaint;
protected final float strokeWidth;
protected float strokeRadius;
public CircleDrawable(Bitmap bitmap, Integer strokeColor, float strokeWidth) {
bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);调
mBitmapRect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(bitmapShader);
paint.setFilterBitmap(true);
paint.setDither(true);
if (strokeColor == null) {
strokePaint = null;
} else {
strokePaint = new Paint();
strokePaint.setStyle(Paint.Style.STROKE);
strokePaint.setColor(strokeColor);
strokePaint.setStrokeWidth(strokeWidth);
strokePaint.setAntiAlias(true);
}
this.strokeWidth = strokeWidth;
strokeRadius = radius - strokeWidth / 2;
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mRect.set(0, 0, bounds.width(), bounds.height());
radius = Math.min(bounds.width(), bounds.height()) / 2;
strokeRadius = radius - strokeWidth / 2;
// Resize the original bitmap to fit the new bound
Matrix shaderMatrix = new Matrix();
shaderMatrix.setRectToRect(mBitmapRect, mRect, Matrix.ScaleToFit.FILL);
bitmapShader.setLocalMatrix(shaderMatrix);
}
@Override
public void draw(Canvas canvas) {
canvas.drawCircle(radius, radius, radius, paint);
if (strokePaint != null) {
canvas.drawCircle(radius, radius, strokeRadius, strokePaint);
}
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
}
}
这个内部的作用就是用一个矩形的Bitmap显示成一个圆形的Drawable
radius = Math.min(bitmap.getWidth(), bitmap.getHeight()) / 2;
长和宽中最小的一半做为圆图的半径
bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
-
这个方法来产生一个画有一个位图的渲染器(Shader)
- TileMode:(一共有三种)CLAMP :如果渲染器超出原始边界范围,会复制范围内边缘染色。REPEAT :横向和纵向的重复渲染器图片,平铺。MIRROR :横向和纵向的重复渲染器图片,这个和REPEAT 重复方式不一样,他是以镜像方式平铺。
onBoundsChange
@Override
protected void onBoundsChange(Rect bounds) {super.onBoundsChange(bounds);mRect.set(0, 0, bounds.width(), bounds.height());radius = Math.min(bounds.width(), bounds.height()) / 2;strokeRadius = radius - strokeWidth / 2;// Resize the original bitmap to fit the new boundMatrix shaderMatrix = new Matrix();shaderMatrix.setRectToRect(mBitmapRect, mRect, Matrix.ScaleToFit.FILL);bitmapShader.setLocalMatrix(shaderMatrix);}
shaderMatrix
.setRectToRect(
mBitmapRect
,
mRect
, Matrix.ScaleToFit.
FILL
);
mBitmapRect
坐标变换前的矩形
mRect
坐标变换后的矩形
Matrix
.
ScaleToFit
.
FILL
矩形缩放选项
由于提供坐标变换前后的参数可为任意矩形,这样的话,变换前后矩形的长宽比不一定一样,提供指定Matrix.ScaleToFit选项来确定缩放选项。Matrix.ScaleToFit定义了四种选项:
CENTER: 保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。
END:保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。END提供右下对齐。
FILL: 可能会变换矩形的长宽比,保证变换和目标矩阵长宽一致。
START:保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。START提供左上对齐。
@Override
public void draw(Canvas canvas) {
canvas.drawCircle(radius, radius, radius, paint);
if (strokePaint != null) {
canvas.drawCircle(radius, radius, strokeRadius, strokePaint);
}
}
在onDraw方法中
画出一个半径为
radius原图,圆心在原来图片的中心
,再画出一个以坐标x=radius,y=radius为圆心,宽度strokeRadius的圆环。
RoundedBitmapDisplayer圆角图片的实现原理和圆形的基本一样,只是在内部类中把Birmap构建一个圆角的图片。看一下RoundedBitmapDisplayer的onDraw方法
@Override
public void draw(Canvas canvas) {
canvas.drawRoundRect(mRect, cornerRadius, cornerRadius, paint);
}
参数说明
mRect:矩形对象。
cornerRadius:x方向上的圆角半径。
cornerRadius:y方向上的圆角半径。
paint:绘制时所使用的画笔。
RoundedVignetteBitmapDispler继承了RoundedBitmapDisplayer. 是在圆角的基础上对图片进行了修饰。
内部类继承了
RoundedBitmapDisplayer中
RoundedDrawable类,
用了父类的构造方法,
RoundedVignetteDrawable(Bitmap bitmap, int cornerRadius, int margin) {
super(bitmap, cornerRadius, margin);
}
所以实际执行了
RoundedBitmapDisplayer类中 的内部方法,
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
RadialGradient vignette = new RadialGradient(
mRect.centerX(), mRect.centerY() * 1.0f / 0.7f, mRect.centerX() * 1.3f,
new int[]{0, 0, 0x7f000000}, new float[]{0.0f, 0.7f, 1.0f},
Shader.TileMode.CLAMP);
Matrix oval = new Matrix();
oval.setScale(1.0f, 0.7f);
vignette.setLocalMatrix(oval);
paint.setShader(new ComposeShader(bitmapShader, vignette, PorterDuff.Mode.SRC_OVER));
}
圆形渲染器
RadialGradient vignette = new RadialGradient(
mRect.centerX(), mRect.centerY() * 1.0f / 0.7f, mRect.centerX() * 1.3f,
new int[]{0, 0, 0x7f000000}, new float[]{0.0f, 0.7f, 1.0f},
Shader.TileMode.CLAMP);
mRect.centerX(): 圆心X坐标
mRect.centerY() * 1.0f / 0.7f: 圆心Y坐标
mRect.centerX() * 1.3f: 半径
new
int
[]{0, 0, 0x7f000000}
: 渲染颜色数组
new
float
[]{0.0f, 0.7f, 1.0f}
: 相对位置数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布
Shader.TileMode.
CLAMP
:渲染器平铺模式
后面的代码和
RoundedBitmapDisplayer中内部类的代码一样。
FadeInbitmapDisplayer类是一个淡入的效果,原理是在图片显示的时候加入了下个淡入的动画。
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
if ((animateFromNetwork && loadedFrom == LoadedFrom.NETWORK) ||
(animateFromDisk && loadedFrom == LoadedFrom.DISC_CACHE) ||
(animateFromMemory && loadedFrom == LoadedFrom.MEMORY_CACHE)) {
animate(imageAware.getWrappedView(), durationMillis);
}
}
在图片显示的时候调用了了animate方法
public static void animate(View imageView, int durationMillis) {
if (imageView != null) {
AlphaAnimation fadeImage = new AlphaAnimation(0, 1);
fadeImage.setDuration(durationMillis);
fadeImage.setInterpolator(new DecelerateInterpolator());
imageView.startAnimation(fadeImage);
}
}
animate方法执行了一个动画。
了解了这几个显示方式的原理,我们可以继承这几个类实现自己的图片显示形式。
接下来我看来看一下如何使用。
圆形渲染器
RadialGradient vignette = new RadialGradient(
mRect.centerX(), mRect.centerY() * 1.0f / 0.7f, mRect.centerX() * 1.3f,
new int[]{0, 0, 0x7f000000}, new float[]{0.0f, 0.7f, 1.0f},
Shader.TileMode.CLAMP);
new
int
[]{0, 0, 0x7f000000}
: 渲染颜色数组
new
float
[]{0.0f, 0.7f, 1.0f}
: 相对位置数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布
Shader.TileMode.
CLAMP
:渲染器平铺模式
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
if ((animateFromNetwork && loadedFrom == LoadedFrom.NETWORK) ||
(animateFromDisk && loadedFrom == LoadedFrom.DISC_CACHE) ||
(animateFromMemory && loadedFrom == LoadedFrom.MEMORY_CACHE)) {
animate(imageAware.getWrappedView(), durationMillis);
}
}
在图片显示的时候调用了了animate方法
public static void animate(View imageView, int durationMillis) {
if (imageView != null) {
AlphaAnimation fadeImage = new AlphaAnimation(0, 1);
fadeImage.setDuration(durationMillis);
fadeImage.setInterpolator(new DecelerateInterpolator());
imageView.startAnimation(fadeImage);
}
}
animate方法执行了一个动画。
了解了这几个显示方式的原理,我们可以继承这几个类实现自己的图片显示形式。
接下来我看来看一下如何使用。