android学习之路(六)---- 图片加载库的优化、封装
来源:互联网 发布:365抽奖软件破解版 编辑:程序博客网 时间:2024/04/30 05:57
封装Image-Loader
一、背景
universal-image-loader是一项伟大的开源项目,作者在其中运用到的软件工程解决办法让人印象深刻,在本篇文章的开篇,首先向universal-image-loader的作者致以敬意,详细地址:https://github.com/nostra13/Android-Universal-Image-Loader ,(源码详解可以参考:http://a.codekk.com/detail/Android/huxian99/Android%20Universal%20Image%20
Loader%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90),相对于一个轻量级的app而言,universal-image-loader完全能够承担相关开发工作,但越到后来,对于体量相对较大的app而言,universal-image-loader的缺点逐渐显现出来(以下内容用u代表universal-image-loader):
1.u的下载和保存在同一子线程进行,这就造成了下载到显示的过程有时间的浪费
2.u的线程池高达三个,虽然每个线程池的管理方式不一样,且可自定义,但是如果一次性加载大量图片,(比如照片流),会消耗大量内存,本人在实验的过程当中,一次性加载60张图片,小米4上面还是能够加载出来,但是很卡,当加载图片的数量增加到70张的时候,不仅是app挂了,手机也挂了!如果用u来加载相册,会感受得比较明显
3.u 封装的效果有限,在日常开发工作当中,显示的图片效果可能默认的效果,也可能是经过特殊裁剪和设计的效果,比如圆角矩形、圆形、圆形+环形、高斯模糊、LOMO效果等特效等等,所以需要进行扩展
二、结果
进行相关改造和封装之后,基本解决了上面的问题,代码详见: https://github.com/pinguo-fandong/Fan-Image-Loader, 整个项目只有一个线程池,负责加载网络数据,而对于图片数据的缓存,用了一个Thread+Queue的方式,这样一来,整个项目的CPU消耗就只有一个线程池+一个子线程,性能提高不少,相较u而言,提高了图片的加载速度,减少了资源消耗
三、详细的解决办法
1. 准备工作
首先看了u的所有源码,通过别人的分析和自己的理解,基本明白了整个流程,为了增加程序的可扩展性,采用builder模式进行封装。
2.修改缓存过程
2.1 在接口DiskCache.java当中增加两个方法
/** * 从memorycache当中拿到bitmap,然后保存到sd卡上面 * * @param cacheKey 图片对应的内存缓存的key和sd卡上面的缓存key * @return 是否保存成功 * @throws IOException */boolean save(String cacheKey) throws IOException;/** * 通过生成的cache Key保存图片到缓存路径 * @param cacheKey 缓存key(缓存的文件名称) * @param bitmap 图片 * @return * @throws IOException */boolean saveByCacheKey(String cacheKey, Bitmap bitmap) throws IOException;
2.2 自定义本地缓存策略
/** * time: 15/11/17 * description: 自定义的本地缓存机制 * * @author fandong */public class CustomDiskCache extends BaseDiskCache { private ConcurrentLinkedQueue<String> mQueue; //标识是否正在轮循 private boolean mIsPoll; //标识是否销毁 private boolean mIsDestroy; public CustomDiskCache(File cacheDir, File reserveCacheDir) { super(cacheDir, reserveCacheDir); this.mQueue = new ConcurrentLinkedQueue<String>(); } @Override public boolean save(String cacheKey) throws IOException { if (!mQueue.contains(cacheKey)) { mQueue.add(cacheKey); } if (!mIsPoll) { mIsPoll = true; Thread thread = new Thread(getCacheTask()); thread.start(); } return true; } public Runnable getCacheTask() { return new Runnable() { @Override public void run() { try { String cacheKey = mQueue.poll(); do { //0.如果销毁就跳出线程 if (mIsDestroy) { break; } boolean savedSuccessfully = false; //1.从内存当中拿出缓存 Bitmap bitmap = FanImageLoader.getMemoryCache(cacheKey); if (bitmap == null || bitmap.isRecycled()) { continue; } //2.保存到sd卡上面 File imageFile = getFileByCacheKey(cacheKey); FileOutputStream os = new FileOutputStream(imageFile); try { savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os); } finally { IoUtils.closeSilently(os); if (!savedSuccessfully) { if (imageFile.exists()) { imageFile.delete(); } } } } while ((cacheKey = mQueue.poll()) != null); } catch (Exception e) { e.printStackTrace(); } finally { mIsPoll = false; } } }; } @Override public void close() { this.mIsDestroy = true; if (mQueue != null) { mQueue.clear(); } }}
这就是将图片缓存到本地的核心部分了,由于从网络上面下载好图片之后,经过相应的图片处理(裁剪、加特效)等,会将bitmap
以key-value
的形式缓存在内存当中,当需要缓存到sd卡上面的时候,只需要从内存缓存当中拿到bitmap
就可以了,那么怎样能够拿到内存当中的缓存bitmap
呢?在FanImageLoader
当中设计了这样的方法:
public static Bitmap getMemoryCache(String memoryKey) { return mImageLoader.getMemoryCache().get(memoryKey);}
2.3 本地缓存时机
在u当中,加载图片是首先会从内存当中读取数据,如果没有缓存,会到sd卡上面去读取缓存文件,如果没有缓存文件,会到网络或者其他源获取数据,获取成功之后会首先放在内存当中,然后同步放入sd卡,然后显示在界面上面,整个加载本地缓存和缓存网络图片到sd卡上面,都在LoadAndDisplayImageTask.java
里面,在tryLoadBitmap()
方法当中,做如下修改:
private Bitmap tryLoadBitmap() throws TaskCancelledException { Bitmap bitmap = null; try { //2.从sd卡上面得到带宽高的缓存文件 File imageFile = configuration.diskCache.getFileByCacheKey(memoryCacheKey); //3.如果没有带宽高的缓存文件,那么 if (imageFile != null && imageFile.exists() && imageFile.length() > 0) { loadedFrom = LoadedFrom.DISC_CACHE; checkTaskNotActual(); bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath())); } if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { loadedFrom = LoadedFrom.NETWORK; if (options.isCacheOnDisk()) { bitmap = tryCacheImageOnDisk(); } if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { fireFailEvent(FailType.DECODING_ERROR, null); } } } catch (IllegalStateException e) { fireFailEvent(FailType.NETWORK_DENIED, null); } catch (TaskCancelledException e) { throw e; } catch (OutOfMemoryError e) { L.e(e); fireFailEvent(FailType.OUT_OF_MEMORY, e); } catch (Throwable e) { L.e(e); fireFailEvent(FailType.UNKNOWN, e); } return bitmap;}
从上面的过程可以看到,到sd卡上面读取缓存,如果没有就会去加载远程的图片资源,这个过程在tryCacheImageOnDisk()
方法当中完成,下载的具体过程由 downloadImage()
方法完成,做如下修改:
private Bitmap downloadImage() throws IOException { InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader()); if (is == null) { L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey); return null; } else { try { Bitmap bitmap = null; //String url = uri; int width = targetSize.getWidth(); int height = targetSize.getHeight(); if (width > 0 || height > 0) { bitmap = BitmapUtils.createScaledBitmap(is, width, height); } if (bitmap == null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; options.inSampleSize = 1; bitmap = BitmapFactory.decodeStream(is, null, options); } if (bitmap != null) { //1.存放在内存当中 if (options.isCacheInMemory()) { configuration.memoryCache.put(memoryCacheKey, bitmap); } //2.存放到sd卡上面 if (uri.startsWith("content") || uri.startsWith("http")) { configuration.diskCache.save(memoryCacheKey); } } return bitmap; } finally { IoUtils.closeSilently(is); } }}
需要说明的是,在封装的过程当中,存放到memory
里面的key和存放到sd卡上面的key采用统一的生成方式,从上面的过程不难看出,存放数据到sd卡的过程是一个异步的过程,下载得到bitmap
之后,会将memoryCacheKey
传递给 CustomDiskCache
,这样,CustomDiskCache
就可以根据memoryCacheKey
取出bitmap
,然后进行存放。
从上面的方法中不难看出,得到网络bitmap之后,程序对bitmap进行了裁剪,就是这句代码:
int width = targetSize.getWidth();int height = targetSize.getHeight();if (width > 0 || height > 0) { bitmap = BitmapUtils.createScaledBitmap(is, width, height);}
targetSize
就是我们需要显示图片的ImageView
或者ImageSwitcher
,它的确定可以通过FanImageLoader.create("http://a.jpg").setShowSize(100,100)
来确定,也可以通过给ImageView
或者ImageSwitcher
设置宽高,或者MaxWidth/MaxHeight
实现。
2.4 缓存key的生成
缓存key默认是url+尺寸信息生成的md5码形成的,这样一来,同一个url会根据不同的size进行存储,大大加快了图片的加载速度,也是软件工程当中“以空间换时间”的概念,具体的实现方式如下:
public class NameGeneratorUtil { private static FileNameGenerator mFileNameGenerator; static { mFileNameGenerator = new Md5FileNameGenerator(); } /** * 生成缓存的key,包括内存缓存和sd卡缓存 * * @param imageURI 原始的imageUrl * @param width 视图的宽度 * @param height 视图的高度 * @return */ public synchronized static String generateCacheKey(String imageURI, int width, int height) { imageURI = encodeURL(imageURI, width, height); return mFileNameGenerator.generate(imageURI); } /** * 生成缓存的key,包括内存缓存和sd卡缓存 * * @param imageURI 原始的imageUrl * @param imageSize 视图的尺寸 * @return */ public synchronized static String generateCacheKey(String imageURI, ImageSize imageSize) { imageURI = encodeURL(imageURI, imageSize.getWidth(), imageSize.getHeight()); return mFileNameGenerator.generate(imageURI); } /** * 生成缓存的key,包括内存缓存和sd卡缓存 * * @param imageURI 原始的imageUrl * @return */ public synchronized static String generateCacheKey(String imageURI) { return mFileNameGenerator.generate(imageURI); } /** * 根据宽高信息将原来的url转变成?width=1080&height=1920 * * @param url 原来的url * @param width 缓存的宽度 * @param height 缓存的高度 * @return 添加宽高信息的url */ public static String encodeURL(String url, int width, int height) { if (TextUtils.isEmpty(url)) { return ""; } if (width <= 0 && height <= 0) { return url; } StringBuilder builder = new StringBuilder(url); if (!builder.toString().contains("?")) { builder.append("?"); } url = builder.toString(); if (!url.endsWith("&") && !url.endsWith("?")) { builder.append("&"); } builder.append("width=") .append(width) .append("&") .append("height=") .append(height); return builder.toString(); }}
2.5 得到sd卡缓存
在FanImageloader当中提供了三个得到本地缓存数据的方法,分别是:
/** * 得到缓存数据 * * @param url * @return */public static String getDiskCachePath(String url) { return getDiskCachePath(url, 0, 0);}/** * 得到url对应的硬盘缓存数据 * * @param url 原始的url * @param width 指定宽度 * @param height 指定高度 * @return */public static String getDiskCachePath(String url, int width, int height) { String cacheKey; if (width <= 0 || height <= 0) { cacheKey = NameGeneratorUtil.generateCacheKey(url, getMaxImageSize()); } else { cacheKey = NameGeneratorUtil.generateCacheKey(url, width, height); } DiskCache diskCache = mImageLoader.getDiskCache(); File imageFile = diskCache.getFileByCacheKey(cacheKey); if (imageFile != null) { return imageFile.getAbsolutePath(); } return null;}/** * 得到url对应的硬盘缓存数据(url没有加七牛的信息) * * @param url 原始的url * @param view 原始的url显示的控件,这个控件是用来计算宽高用的 * @return */public static String getDiskCachePath(String url, View view) { ImageAware aware; if (view instanceof ImageView) { aware = new ImageViewAware((ImageView) view); } else if (view instanceof ImageSwitcher) { aware = new ImageSwitcherAware(view); } else { aware = new SimpleViewAware(view); } return getDiskCachePath(url, aware.getWidth(), aware.getHeight());}private static ImageSize getMaxImageSize() { DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); return new ImageSize(displayMetrics.widthPixels, displayMetrics.heightPixels);}
四、封装图片处理效果
1.实现背景淡出,前景淡入的效果
在很多场景下,如果我们通过背景淡出,前景淡入的方式显示图片,整个过程显得很柔和,很优雅,那么如何实现呢,如果是ImageView
,进行动画显示的是整个控件,能够实现淡入,但不能实现淡出的效果,所以这里我们采用ImageSwitcher
的方式进行了实现,首先我们知道,在u当中,并没有直接将一个下载的bitmap
设置给控件显示,而是通过封装一层aware来进行显示,这里我们自定义封装ImageSwitcherAware
.如下所示:
public class ImageSwitcherAware extends ViewAware { public ImageSwitcherAware(View view) { super(view); } public ImageSwitcherAware(View view, boolean checkActualViewSize) { super(view, checkActualViewSize); } protected void setImageDrawableInto(Drawable drawable, View view) { ((ImageSwitcher) view).setImageDrawable(drawable); } protected void setImageBitmapInto(Bitmap bitmap, View view) { ((ImageSwitcher) view).setImageDrawable(new BitmapDrawable(view.getResources(), bitmap)); } @Override public int getHeight() { View view = viewRef.get(); if (view != null) { final ViewGroup.LayoutParams params = view.getLayoutParams(); int height = 0; if (view instanceof FanImageView) { FanImageView iv = (FanImageView) view; height = iv.getShowHeight(); } if (height <= 0 && checkActualViewSize && params != null && params.height != ViewGroup.LayoutParams.WRAP_CONTENT) { height = view.getHeight(); // Get actual image height } if (height <= 0 && params != null) height = params.height; // Get layout height parameter return height; } return 0; } @Override public int getWidth() { View view = viewRef.get(); if (view != null) { final ViewGroup.LayoutParams params = view.getLayoutParams(); int width = 0; if (view instanceof FanImageView) { FanImageView iv = (FanImageView) view; width = iv.getShowWidth(); } if (width <= 0 && checkActualViewSize && params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) { width = view.getWidth(); // Get actual image width } if (width <= 0 && params != null) width = params.width; // Get layout width parameter return width; } return 0; }}
那么,我们要用ImageSwitcherAware
实现淡入淡出的效果,需要封装ImageSwitcher
的动画执行方法,于是这里进行了对ImageSwitcher
的封装: FanImageView
:
package com.fans.loader.view;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.animation.AlphaAnimation;import android.view.animation.LinearInterpolator;import android.widget.ImageSwitcher;import android.widget.ImageView;import android.widget.ViewSwitcher.ViewFactory;/** * time: 15/11/11 * description:封装了淡入淡出的ImageSwitcher * * @author fandong */public class FanImageView extends ImageSwitcher implements ViewFactory { private int showWidth; private int showHeight; public FanImageView(Context context) { super(context); initView(); } public FanImageView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } private void initView() { AlphaAnimation in = new AlphaAnimation(0.0f, 1.0f); in.setInterpolator(new LinearInterpolator()); in.setDuration(800); AlphaAnimation out = new AlphaAnimation(1.0f, 0.0f); in.setInterpolator(new LinearInterpolator()); out.setDuration(800); setInAnimation(in); setOutAnimation(out); setFactory(this); } @SuppressWarnings("deprecation") @Override public View makeView() { ImageView view = new ImageView(getContext()); view.setBackgroundColor(0x00000000); view.setScaleType(ImageView.ScaleType.CENTER_CROP); view.setLayoutParams(new LayoutParams( android.widget.Gallery.LayoutParams.MATCH_PARENT, android.widget.Gallery.LayoutParams.MATCH_PARENT)); return view; } public int getShowWidth() { return showWidth; } public void setShowWidth(int showWidth) { this.showWidth = showWidth; } public int getShowHeight() { return showHeight; } public void setShowHeight(int showHeight) { this.showHeight = showHeight; }}
我们要使用,那么就需要在display
方法当中进行判断,当前需要显示的控件,是ImageView
还是ImageSwitcher
,在FanImageLoader.java
当中,有这样的方法:
private synchronized static void display(String url, View view, DisplayImageOptions displayImageOptions,ImageLoadingListener imageLoadingListener, ImageLoadingProgressListener imageLoadingProgressListener) { try { if (view instanceof ImageView) { mImageLoader.displayImage(url, new ImageViewAware((ImageView) view), displayImageOptions, imageLoadingListener, imageLoadingProgressListener); } else if (view instanceof ImageSwitcher) { mImageLoader.displayImage(url, new ImageSwitcherAware(view), displayImageOptions, imageLoadingListener, imageLoadingProgressListener); } else { mImageLoader.displayImage(url, new SimpleViewAware(view), displayImageOptions, imageLoadingListener, imageLoadingProgressListener); } } catch (OutOfMemoryError e1) { e1.printStackTrace(); } catch (Exception e2) { e2.printStackTrace(); }}
2.实现圆角矩形的显示
在源码包com.fans.loader.core.display
包下面自定了各种displayer,其中的RoundedBitmapDisplayer
就是实现圆角矩形显示的控制器,核心方法:
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { imageAware.setImageDrawable(new RoundedDrawable(bitmap, this.cornerRadius, this.margin));}
这里可以看出,我们将处理之后的bitmap
转换成了一个圆角矩形的drawable
,那么这个RoundedDrawable
是如何生成的呢?
public static class RoundedDrawable extends Drawable { protected final float cornerRadius; protected final int margin; protected final RectF mRect = new RectF(); protected final Rect mBitmapRect; protected final BitmapShader bitmapShader; protected final Paint paint; public RoundedDrawable(Bitmap bitmap, int cornerRadius, int margin) { this.cornerRadius = (float) cornerRadius; this.margin = margin; this.bitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP); this.mBitmapRect = new Rect(margin, margin, bitmap.getWidth() - margin, bitmap.getHeight() - margin); this.paint = new Paint(); this.paint.setAntiAlias(true); this.paint.setShader(this.bitmapShader); } protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); this.mRect.set((float) this.margin, (float) this.margin, (float) (bounds.width() - this.margin), (float) (bounds.height() - this.margin)); Matrix shaderMatrix = new Matrix(); float dx = 0.0F; float dy = 0.0F; int dwidth = this.mBitmapRect.width(); int dheight = this.mBitmapRect.height(); int vwidth = bounds.width() - this.margin; int vheight = bounds.height() - this.margin; float scale; if (dwidth * vheight > vwidth * dheight) { scale = (float) vheight / (float) dheight; dx = ((float) vwidth - (float) dwidth * scale) * 0.5F; } else { scale = (float) vwidth / (float) dwidth; dy = ((float) vheight - (float) dheight * scale) * 0.5F; } shaderMatrix.setScale(scale, scale); shaderMatrix.postTranslate((float) ((int) (dx + 0.5F)), (float) ((int) (dy + 0.5F))); this.bitmapShader.setLocalMatrix(shaderMatrix); } public void draw(Canvas canvas) { canvas.drawRoundRect(this.mRect, this.cornerRadius, this.cornerRadius, this.paint); } public int getOpacity() { return -3; } public void setAlpha(int alpha) { this.paint.setAlpha(alpha); } public void setColorFilter(ColorFilter cf) { this.paint.setColorFilter(cf); }}
从上面可以看出,我们是通过shader的方式实现了圆角drawable的生成。
3.高斯模糊的实现
这里的高斯模糊采用了github上面的开源库,StackBlur,当然也可以通过renderscript来实现,
/** * time: 15/11/11 * description:显示高斯模糊的图片 * * @author fandong */public class BlurBitmapDisplayer implements BitmapDisplayer { private final int depth; public BlurBitmapDisplayer(int depth) { this.depth = depth; } public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { GaussianBlur blurProcess = new GaussianBlur(); Bitmap blurBitmap = blurProcess.blur(bitmap, (float) this.depth); if (blurBitmap != null && !blurBitmap.isRecycled()) { imageAware.setImageBitmap(blurBitmap); } }}
GaussianBlur.java处于com.fans.loader.core.util
包下面
4.如何使用
上面我们定义了各种displayer
现在是时候运用在我们的FanImageLoader
上面了,观察FanImageLoader
内部类Builder
,提供的方法build()
里面,会根据传递进来的效果类型,生成对应的displayer
,并传入到DisplayImageOptions.Builder
里面去,关键代码如下:
DisplayImageOptions.Builder builder = new DisplayImageOptions.Builder() .showImageOnFail(this.mFailDrawable) .showImageForEmptyUri(this.mEmptyDrawable) .showImageOnLoading(this.mDefaultDrawable) .showImageOnFail(this.mFailRes) .showImageForEmptyUri(this.mEmptyRes) .showImageOnLoading(this.mDefaultRes) .imageScaleType(this.mImageScaleType) .cacheInMemory(true) .cacheOnDisk(true) .decodingOptions(this.decodingOptions) .considerExifParams(true);DisplayImageOptions displayImageOptions = null;switch (this.mDisplayType) { case DISPLAY_DEFAULT:// 简单 default: displayImageOptions = builder.displayer(new SimpleBitmapDisplayer()).build(); break; case DISPLAY_FADE_IN:// 淡入 displayImageOptions = builder.displayer(new FadeInBitmapDisplayer(this.mFadeInTime)).build(); break; case DISPLAY_ROUND:// 圆角矩形 displayImageOptions = builder.displayer(new RoundedBitmapDisplayer(this.mRoundRadius)).build(); break; case DISPLAY_ROUND_FADE_IN:// 圆角矩形淡入 displayImageOptions = builder.displayer( new RoundedFadeInBitmapDisplayer(this.mRoundRadius, this.mFadeInTime)).build(); break; case DISPLAY_ROUND_VIGNETTE:// 圆角阴影(LOMO) displayImageOptions = builder.displayer(new RoundedLomoBitmapDisplayer(this.mRoundRadius)).build(); break; case DISPLAY_ROUND_VIGNETTE_FADE_IN:// 圆角阴影淡入 displayImageOptions = builder.displayer( new RoundedLomoFadeInBitmapDisplayer(this.mRoundRadius, this.mFadeInTime)).build(); break; case DISPLAY_CIRCLE:// 圆形 displayImageOptions = builder.displayer(new CircleBitmapDisplayer()).build(); break; case DISPLAY_CIRCLE_FADE_IN:// 圆形淡入 displayImageOptions = builder.displayer(new CircleFadeInBitmapDisplayer(this.mFadeInTime)).build(); break; case DISPLAY_CIRCLE_RING:// 圆形带环 displayImageOptions = builder.displayer( new CircleRingBitmapDisplayer().setStrokeWidth(mStrokeWidth).setColor(mRingColor) .setRingPadding(mRingPadding)).build(); break; case DISPLAY_BLUR:// 高斯模糊 displayImageOptions = builder.displayer(new BlurBitmapDisplayer(this.mBlurDepth)).build(); break; case DISPLAY_BLUR_FADE_IN:// 高斯模糊淡入 displayImageOptions = builder.displayer( new BlurFadeInBitmapDisplayer(this.mBlurDepth, this.mFadeInTime)).build(); break; case DISPLAY_ROUND_BLUR:// 圆角高斯模糊 displayImageOptions = builder.displayer( new RoundedBlurBitmapDisplayer(this.mRoundRadius, this.mBlurDepth)).build(); break; case DISPLAY_ROUND_BLUR_VIGNETTE:// 圆角高斯模糊的LOMO displayImageOptions = builder.displayer( new RoundedLomoBlurBitmapDisplayer(this.mRoundRadius, this.mBlurDepth)).build(); break; case DISPLAY_CIRCLE_BLUR:// 圆形高斯模糊 displayImageOptions = builder.displayer(new CircleBlurBitmapDisplayer(this.mBlurDepth)).build();}
五、滑动优化
当我们使用ListView
或者RecyclerView
进行图片显示的时候,通常会让ListView/RecyclerView
在滑动的过程当中停止图片加载,universal-image-loader
的处理方式有bug,采用的同步锁根本不能锁住,所以第一步,我们在加载图片的LoadAndDisplayImageTask.java
的run方法当中加上同步锁:
@Overridepublic void run() { //1.处理在滑动的时候不加载图片,只有在idle状态之下才会加载图片 AtomicBoolean pause = engine.getPause(); if (pause.get()) { synchronized (engine.pauseLock) { try { engine.pauseLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } Bitmap bmp; try { checkTaskNotActual();……}
第二步、自定义OnScrollListener,用于ListView的OnScrollListener:
/** * time: 15/6/11 * description:当控件(ListView)在滑动过程当中时暂停图片的加载,停止后恢复加载 * * @author fandong */public class AbsListPauseOnScrollListener implements OnScrollListener { private final boolean pauseOnScroll; private final boolean pauseOnFling; private final OnScrollListener externalListener; public AbsListPauseOnScrollListener(boolean pauseOnScroll, boolean pauseOnFling) { this(pauseOnScroll, pauseOnFling, null); } public AbsListPauseOnScrollListener(boolean pauseOnScroll, boolean pauseOnFling, OnScrollListener customListener) { this.pauseOnScroll = pauseOnScroll; this.pauseOnFling = pauseOnFling; this.externalListener = customListener; } public void onScrollStateChanged(AbsListView view, int scrollState) { switch (scrollState) { case SCROLL_STATE_IDLE: FanImageLoader.resume(); break; case SCROLL_STATE_TOUCH_SCROLL: if (this.pauseOnScroll) { FanImageLoader.pause(); } break; case SCROLL_STATE_FLING: if (this.pauseOnFling) { FanImageLoader.pause(); } } if (this.externalListener != null) { this.externalListener.onScrollStateChanged(view, scrollState); } } public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (this.externalListener != null) { this.externalListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } }}
使用方式也很简单:
mListView.setOnScrollListener(new AbsListPauseOnScrollListener(true, true, mOnScrollListener));
第三步、自定义recyclerView对应的OnScrollListener
/** * time: 15/11/11 * description: RecyclerView滑动时候是否加载图片 * * @author fandong */public class RecyclerPauseOnScrollListener extends RecyclerView.OnScrollListener { private final boolean pauseOnScroll; private final boolean pauseOnFling; public RecyclerPauseOnScrollListener(boolean pauseOnScroll, boolean pauseOnFling) { this.pauseOnScroll = pauseOnScroll; this.pauseOnFling = pauseOnFling; } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { switch (newState) { case RecyclerView.SCROLL_STATE_IDLE: FanImageLoader.resume(); break; case RecyclerView.SCROLL_STATE_DRAGGING: if (this.pauseOnScroll) { FanImageLoader.pause(); } break; case RecyclerView.SCROLL_STATE_SETTLING: if (this.pauseOnFling) { FanImageLoader.pause(); } } }}
六.好了,以上就是改写的大部分了,当然了,还有其他一些改写内容,相信大家在看源码的过程当中就会领会清楚,比如load()方法等,整个Fan-Image-Loader的使用方法如下所示:
第一步、在Application或者SplashActivity当中初始化
FanImageLoader.init(context.getApplicationContext(), FileUtil.getPathByType(FileUtil.DIR_TYPE_CACHE));L.writeDebugLogs(DebugUtil.isDebug());
第二步、在需要显示图片的地方调用(详见下面的示例)
第三步、(可选)在程序退出的时候,调用FanImageLoader.destroy();
//示例0 、背景淡出,图片淡入 FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setDisplayType(FanImageLoader.DISPLAY_DEFAULT) .into(mIs); /* 示例一、普通加载图片*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setDisplayType(FanImageLoader.DISPLAY_DEFAULT) .into(mIv1); FanImageLoader.create("assets://xiada01.jpg") .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setDisplayType(FanImageLoader.DISPLAY_DEFAULT) .into(mIv2); /* 示例二、渐变显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setDisplayType(FanImageLoader.DISPLAY_FADE_IN) .setFadeInTime(1000) .into(mIv2); /* 示例三、圆角矩形显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setRoundRadius(30) .setDisplayType(FanImageLoader.DISPLAY_ROUND) .into(mIv3); /* 示例四、圆角矩形淡入显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setRoundRadius(30) .setDisplayType(FanImageLoader.DISPLAY_ROUND_FADE_IN) .setFadeInTime(1000) .into(mIv4); /* 示例五、圆角矩形LOMO显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setRoundRadius(30) .setDisplayType(FanImageLoader.DISPLAY_ROUND_VIGNETTE) .into(mIv5); /* 示例六、圆角矩形LOMO淡入显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setRoundRadius(30) .setDisplayType(FanImageLoader.DISPLAY_ROUND_VIGNETTE_FADE_IN) .setFadeInTime(1000) .into(mIv6); /* 示例七、圆形显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setDisplayType(FanImageLoader.DISPLAY_CIRCLE) .into(mIv7); /* 示例八、圆形淡入显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setShowSize(100,100) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setDisplayType(FanImageLoader.DISPLAY_CIRCLE_FADE_IN) .setFadeInTime(1000) .into(mIv8); /* 示例九、带环的圆形图片*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setStrokeWidth(5.f) .setRingColor(0xff00ff00) .setRingPadding(3.f) .setDisplayType(FanImageLoader.DISPLAY_CIRCLE_RING) .into(mIv9); /* 示例十、模糊图片显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setBlurDepth(20) .setDisplayType(FanImageLoader.DISPLAY_BLUR) .into(mIv10); /* 示例十一、模糊图片显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setBlurDepth(20) .setFadeInTime(1000) .setDisplayType(FanImageLoader.DISPLAY_BLUR_FADE_IN) .into(mIv11); /* 示例十二、模糊图片显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setBlurDepth(20) .setRoundRadius(20) .setDisplayType(FanImageLoader.DISPLAY_ROUND_BLUR) .into(mIv12); /* 示例十三、模糊图片显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setBlurDepth(20) .setRoundRadius(20) .setDisplayType(FanImageLoader.DISPLAY_ROUND_BLUR_VIGNETTE) .into(mIv13); /* 示例十四、模糊图片显示*/ FanImageLoader.create(url) .setImageScaleType(ImageScaleType.EXACTLY) .setDefaultRes(R.drawable.ic_launcher) .setFailRes(R.drawable.ic_launcher) .setEmptyRes(R.drawable.ic_launcher) .setBlurDepth(20) .setDisplayType(FanImageLoader.DISPLAY_CIRCLE_BLUR) .into(mIv14);
- android学习之路(六)---- 图片加载库的优化、封装
- Android图片加载库的封装实战
- Android图片加载库的封装实战
- 图片加载库的封装
- Android学习之图片加载库Fresco
- Android学习之图片加载库Glide
- Android异步加载学习笔记之四:利用缓存优化网络加载图片及ListView加载优化
- Android学习笔记之六-图片的修饰技术
- Android ListView&异步加载的学习(三)——AsyncTask加载图片&运用Lru算法优化图片加载
- Android 图片加载优化
- Android 加载图片优化
- Android图片加载优化
- Android-图片加载优化
- Android图片加载优化
- 封装Android加载网络图片
- Hibernate的学习之路六(加载配置)
- android内存优化之二加载图片内存优化
- Android学习笔记之网络图片加载
- nginx日志简单认识
- 织梦 channelartlist 按指定的typeid排序[推荐]
- 浏览器渲染原理
- 求传递闭包Warshall算法代码实现
- JavaScript 面向对象与原型、继承
- android学习之路(六)---- 图片加载库的优化、封装
- Activity生命周期分析
- 各种音视频编解码学习详解
- 集合问题
- 6、ZigZag Conversion
- jQuery插件开发模式
- EXTJS表格编辑控件验证
- fourcc的涵义以及在C++编程中的实现
- Timus Online Judge 1577 E-mail(字符串DP)