android學習之路(六)---- 圖片載入庫的優化、封裝
封裝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方法當中加上同步鎖:
@Override
public 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圖片載入-Glide4.0框架封裝AndroidIDE框架封裝
- 載入GIF圖片優化方案優化
- Android OOM 排查與解決——圖片載入優化AndroidOOM優化
- 【前端優化】js圖片懶載入及優化前端優化JS
- iOS效能優化 - 網路圖片載入優化iOS優化
- Flutter圖片載入優化深入探索Flutter優化
- 前端優化之圖片懶載入前端優化
- Glide載入gif圖片優化IDE優化
- 網站優化之路—圖片優化,圖片從模糊到清晰網站優化
- FaceBook推出的Android圖片載入庫FrescoAndroid
- ListView效能優化非同步載入圖片View優化非同步
- 前端效能優化之路——圖片篇。前端優化
- 封裝並實現統一的圖片載入架構封裝架構
- iOS非同步圖片載入優化與常用開源庫分析iOS非同步優化
- 手把手實現圖片懶載入+封裝vue懶載入元件封裝Vue元件
- 圖片無法載入的情況下的優化優化
- Android圖片載入庫Picasso原始碼分析Android原始碼
- Android效能優化——圖片優化(二)Android優化
- 要優雅!Android中這樣載入大圖片和長圖片Android
- Android 圖片載入框架Android框架
- android 載入大量圖片Android
- 原生幻燈片封裝學習封裝
- JS盒子模型、圖片懶載入、DOM庫封裝 — 1、JS盒子模型及常用操作樣式方法的封裝...JS模型封裝
- 專案分享六:圖片的延遲載入
- Yelp app是如何使用Glide優化圖片載入的APPIDE優化
- 解耦圖片載入庫解耦
- Android 載入大圖片,不壓縮圖片Android
- Android 高效安全載入圖片Android
- Android記憶體優化之圖片優化Android記憶體優化
- Android常用圖片載入庫介紹及對比Android
- Android應用開發-圖片載入庫GlideAndroidIDE
- Android 自定義本地圖片載入庫,仿微信相簿Android地圖
- 前端效能優化-圖片懶載入(防抖、節流)前端優化
- Swift 專案總結 08 GIF 圖片載入優化Swift優化
- Android 圖片載入庫Glide知其然知其所以然之載入AndroidIDE
- Android APP 記憶體優化之圖片優化AndroidAPP記憶體優化
- android glide圖片載入框架AndroidIDE框架