昨天刚写了个带缓存功能的ImageView(kyimageview),现在把他的实现原理说明下,有兴趣的可以参考下,当然如果有更好的方案也欢迎分享下。
利用图片大小判断图片是否更新
这是这个组件的核心,在下载图片前,先获取图片的大小(到字节),然后与本地缓存的图片比较,不一致则说明有更新。
我们知道,图片的大小不只是与像素数有关,还与透明度、色彩、压缩有关,不同的透明度、色彩 最后压缩出来的图片大小都是不一样的。所以,图片哪怕有稍微的改变,大小都是不一样的。
URL url = new URL(imageUrl);
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();
httpConnection.setRequestProperty("User-Agent", "PacificHttpClient");
httpConnection.setConnectTimeout(10000);
httpConnection.setReadTimeout(20000);
httpConnection.connect();
if (httpConnection.getResponseCode() == 404) {//链接404
finishedHandler.sendEmptyMessage(1);
return ;
}
final long updateTotalSize = httpConnection.getContentLength();//待下载的图片大小
//文件存在
File localFile = new File(cacheFolder, fileName);//本地缓存了的图片,localFile.length()获取本地图片大小
if(localFile.exists() && updateTotalSize == localFile.length()){//比较二者大小,相等则说明没变化
finishedHandler.sendEmptyMessage(2);
return ;
}
File saveFile = new File(cacheFolder, fileName);//图片有变化,则下载图片,并保存到缓存中。这个实际与localFile重了,可以去掉
FileOutputStream output = new FileOutputStream(saveFile);
byte buffer[] = new byte[1024];
int readsize = 0;
final InputStream is = httpConnection.getInputStream();
while((readsize = is.read(buffer)) != -1){
output.write(buffer, 0, readsize);
}
is.close();
output.close();
finishedHandler.sendEmptyMessage(0);如何让图片在画布上居中显示

要想让图片居中显示,首先要计算出图片left、和top。如上图可得出, left=画布的一半 - 图片的一半。top的计算同理。当然如果存在padding,也要考虑到。
如何实现ScaleType
首先来看下ScaleType各个值的含义:
public class ScaleType {
/**
* 不按比例缩放图片,目标是把图片塞满整个View。
* fitXY
*/
public static final int FIT_XY = 1;
/**
* FIT_START, FIT_END在图片缩放效果上与FIT_CENTER一样,只是显示的位置不同,FIT_START是置于顶部,FIT_CENTER居中,FIT_END置于底部。
* fitStart
*/
public static final int FIT_START = 2;
/**
* 把图片按比例扩大/缩小到View的宽度,居中显示
* fitCenter
*/
public static final int FIT_CENTER = 3;
/**
* FIT_START, FIT_END在图片缩放效果上与FIT_CENTER一样,只是显示的位置不同,FIT_START是置于顶部,FIT_CENTER居中,FIT_END置于底部。
* fitEnd
*/
public static final int FIT_END = 4;
/**
* 按图片的原来size居中显示,当图片长/宽超过View的长/宽,则截取图片的居中部分显示
* center
*/
public static final int CENTER = 5;
/**
* 按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长(宽)
* centerCrop
*/
public static final int CENTER_CROP = 6;
/**
* 将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片长/宽等于或小于View的长/宽
* centerInside
*/
public static final int CENTER_INSIDE = 7;
}开始画:
/**
* 根据ScaleType的center模式画
* @return
*/
private void drawByCenter(Canvas canvas, Bitmap bitmap){
int contentWidth = this.getWidth() - this.paddingLeft - this.paddingRight;//计算图片的可用空间,即减去padding后的
int contentHeight = this.getHeight() - this.paddingTop - this.paddingBottom;
int left = this.paddingLeft + (contentWidth/2 - bitmap.getWidth()/2);//计算图片居中显示的left
int top = this.paddingTop + (contentHeight/2 - bitmap.getHeight()/2);//计算图片居中显示的top
canvas.drawBitmap(bitmap, left, top, null);
bitmap.recycle();
}/**
* 根据ScaleType的centerCrop模式画
* @return
*/
private void drawByCenterCrop(Canvas canvas, Bitmap bitmap){
int contentWidth = this.getWidth() - this.paddingLeft - this.paddingRight;
int contentHeight = this.getHeight() - this.paddingTop - this.paddingBottom;
if(bitmap.getWidth() < contentWidth || bitmap.getHeight() < contentHeight){//判断图片是否需要放大
float widthRatio = ((float) contentWidth) / bitmap.getWidth();//计算宽度的放大比例
float heightRatio = ((float) contentHeight) / bitmap.getHeight();//计算高度的放大比例
float ratio = widthRatio;
if(heightRatio > widthRatio)//取二者最大的,这样就能保证宽、高都能大于或等于图片显示区域的大小
ratio = heightRatio;
bitmap = scaleBitmap(bitmap, ratio, ratio);//进行缩放
}
int left = this.paddingLeft + (contentWidth/2 - bitmap.getWidth()/2);
int top = this.paddingTop + (contentHeight/2 - bitmap.getHeight()/2);
canvas.drawBitmap(bitmap, left, top, null);
bitmap.recycle();
}/**
* 根据ScaleType的fitStart模式画
* @return
*/
private void drawByFitStart(Canvas canvas, Bitmap bitmap){
int contentWidth = this.getWidth() - this.paddingLeft - this.paddingRight;
int contentHeight = this.getHeight() - this.paddingTop - this.paddingBottom;
if(bitmap.getWidth() != contentWidth){//将图片宽度缩放到contentWidth的大小
float ratio = ((float) contentWidth) / bitmap.getWidth();
bitmap = scaleBitmap(bitmap, ratio, ratio);
}
int left = this.paddingLeft + (contentWidth/2 - bitmap.getWidth()/2);
int top = this.paddingTop;
canvas.drawBitmap(bitmap, left, top, null);
bitmap.recycle();
}其他的模式与上面的同理,想要更多了解的可以下载源码。
使用线程池
/**
* 获得线程池
*/
private static ExecutorService executorService ;
private ExecutorService getExecutorService(){
if(executorService == null)
executorService = Executors.newFixedThreadPool(threadPoolSize);
return executorService;
}组件使用线程池来异步加载图片,所有KyImageView都共用一个静态的executorService对象。通过threadPoolSize设置线程池最大线程数。
通过消息刷新
线程加载完图片后是通过invalidate()来刷新组件的,为了避免出现混乱或死循环,定义了个ThreadWrapper,可以理解为是个消息:
/**
* 线程wrapper
* @return
*/
private class ThreadWrapper{
private boolean isLoading = false;//是否正在加载远程图片
private boolean isFinished = false;//是否加载完成
}这样在onDraw(Canvas canvas)刷新时,就可以判断当前刷新是否来自线程:
if(!threadWrapper.isLoading){//不是来自线程的刷新
threadWrapper.isLoading = true;
loadRemoteImage();
return ;
}
if(threadWrapper.isFinished){//是来自线程的刷新,同时重置threadWrapper
threadWrapper.isLoading = false;
threadWrapper.isFinished = false;
}完毕。 先不论写得如何,个人是比较喜欢写些自定义组件,希望有同样爱好的可以多多交流。
源码:http://www.see-source.com/androidwidget/list.html