欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

如何实现带有图片缓存功能的ImageView,,利用图片大小判断图片是否

来源: 开发者 投稿于  被查看 15337 次 评论:74

如何实现带有图片缓存功能的ImageView,,利用图片大小判断图片是否


昨天刚写了个带缓存功能的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);


如何让图片在画布上居中显示

aaaa.png

要想让图片居中显示,首先要计算出图片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

用户评论