QclGlide

Project Url: qiushi123/QclGlide
Introduction: Glide 加载 gif 动图,Glide 带加载动画(动画可以自定义)比 imageloader 更好用的安卓图片加载库
More: Author   ReportBugs   
Tags:

Glide 加载 gif 动图,Glide 带加载动画(动画可以自定义)

先看效果图

上面是一个 gif 动图,下面是通过 glide 把图片设成圆形图片

image

一. Android-stduio 引入类库

在 build.gradle 中添加依赖:

compile 'com.github.bumptech.glide:glide:3.7.0'

需要 support-v4 库的支持,如果你的项目没有 support-v4 库(项目默认已经添加了),还需要添加 support-v4 依赖: 如果你用的是 3.0 以后 sdk 下面的 v4 包就不用导入了 compile 'com.android.support:support-v4:23.3.0'

然后配置混淆规则:

-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}

其中第一个混淆规则表明不混淆所有的 GlideModule。

二.Glide 自定义加载动画

1,在 res 下创建 anim 文件夹,在这个文件夹自定义你想要实现的动画,如:slide_in_left.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="-50%p" android:toXDelta="0"
               android:duration="@android:integer/config_mediumAnimTime"/>
    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
           android:duration="@android:integer/config_mediumAnimTime" />
</set>

使用动画效果:

Glide.with(this) .load(imageUrl) .animate(R.anim.slide_in_left) // or R.anim.zoom_in .error(R.mipmap.ic_launcher) .into(image);

三. Glide 的使用

简单使用:

    Glide
       .with(this)
       .load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
       .into(imageView);

Glide.with()
with(Context context). 使用 Application 上下文,Glide 请求将不受 Activity/Fragment 生命周期控制。
with(Activity activity).使用 Activity 作为上下文,Glide 的请求会受到 Activity 生命周期控制。
with(FragmentActivity activity).Glide 的请求会受到 FragmentActivity 生命周期控制。
with(android.app.Fragment fragment).Glide 的请求会受到 Fragment 生命周期控制。
with(android.support.v4.app.Fragment fragment).Glide 的请求会受到 Fragment 生命周期控制。

返回关联了相应上下文的 RequestManager 实例。

requestManager.load()

Glide 基本可以 load 任何可以拿到的媒体资源,如: load SD 卡资源:load("file://"+ Environment.getExternalStorageDirectory().getPath()+"/test.jpg") load assets 资源:load("file:///androidasset/f003.gif") load raw 资源:load("Android.resource://com.frank.glide/raw/raw_1")或 load("android.resource://com.frank.glide/raw/"+R.raw.raw_1) load drawable 资源:load("android.resource://com.frank.glide/drawable/news")或 load("android.resource://com.frank.glide/drawable/"+R.drawable.news) load ContentProvider 资源:load("content://media/external/images/media/139469") load http 资源:load("http://img.my.csdn.net/uploads/201508/05/1438760757_3588.jpg") load https 资源:load("https://img.alicdn.com/tps/TB1uyhoMpXXXXcLXVXXXXXXXXXX-476-538.jpg_240x5000q50.jpg.webp") 当然,load 不限于 String 类型,还可以: load(Uri uri),load(File file),load(Integer resourceId),load(URL url),load(byte[] model), load(T model),loadFromMediaStore(Uri uri)。 load 的资源也可以是本地视频,如果想要 load 网络视频或更高级的操作可以使用 VideoView 等其它控件完成。 而且可以使用自己的 ModelLoader 进行资源加载: using(ModelLoader modelLoader, Class dataClass),using(final StreamModelLoader modelLoader), using(StreamByteArrayLoader modelLoader),using(final FileDescriptorModelLoader modelLoader)。 返回 GenericRequestBuilder 实例。

GenericRequestBuilder

GenericRequestBuilder是最顶层的 Request Builder, 用于处理选项设置和开始一般 resource 类型资源的加载。其中 ModelType 是指代表资源的类型, 如"http://img.my.csdn.net/uploads/201508/05/1438760757_3588.jpg"这个 String 就代表了一张图片资源, 所以这个 ModelType 就是 String。DataType 是指 ModelLoader 提供的,可以被 ResourceDecoder 解码的数据类型。 ResourceType 是指将要加载的 resource 类型。TranscodeType 是指已解码的资源将要被转成的资源类型。

常用设置(比如动画,缓存等)

thumbnail(float sizeMultiplier). 请求给定系数的缩略图。如果缩略图比全尺寸图先加载完,就显示缩略图,
        否则就不显示。系数 sizeMultiplier 必须在(0,1)之间,可以递归调用该方法。
sizeMultiplier(float sizeMultiplier). 在加载资源之前给 Target 大小设置系数。
diskCacheStrategy(DiskCacheStrategy strategy).设置缓存策略。
    DiskCacheStrategy.SOURCE:缓存原始数据,
    DiskCacheStrategy.RESULT:缓存变换后的资源数据,
    DiskCacheStrategy.NONE:什么都不缓存,
    DiskCacheStrategy.ALL:缓存 SOURC 和 RESULT。
    默认采用 DiskCacheStrategy.RESULT 策略,对于 download only 操作要使用 DiskCacheStrategy.SOURCE。
priority(Priority priority). 指定加载的优先级,优先级越高越优先加载,
    但不保证所有图片都按序加载。枚举 Priority.IMMEDIATE,Priority.HIGH,Priority.NORMAL,Priority.LOW。默认为 Priority.NORMAL。
dontAnimate(). 移除所有的动画。
animate(int animationId). 在异步加载资源完成时会执行该动画。
animate(ViewPropertyAnimation.Animator animator). 在异步加载资源完成时会执行该动画。
placeholder(int resourceId). 设置资源加载过程中的占位 Drawable。
placeholder(Drawable drawable). 设置资源加载过程中的占位 Drawable。
fallback(int resourceId). 设置 model 为空时要显示的 Drawable。如果没设置 fallback,model 为空时将显示 error 的 Drawable,
    如果 error 的 Drawable 也没设置,就显示 placeholder 的 Drawable。
fallback(Drawable drawable).设置 model 为空时显示的 Drawable。
error(int resourceId).设置 load 失败时显示的 Drawable。
error(Drawable drawable).设置 load 失败时显示的 Drawable。
listener(RequestListener<? super ModelType, TranscodeType> requestListener). 监听资源加载的请求状态,
    可以使用两个回调:onResourceReady(R resource, T model, Target<R> target, boolean isFromMemoryCache, 
    boolean isFirstResource)和 onException(Exception e, T model, Target&lt;R&gt; target, 
    boolean isFirstResource),但不要每次请求都使用新的监听器,要避免不必要的内存申请,
    可以使用单例进行统一的异常监听和处理。
skipMemoryCache(boolean skip). 设置是否跳过内存缓存,但不保证一定不被缓存(比如请求已经在加载资源且没设置跳过内存缓存,
    这个资源就会被缓存在内存中)。
override(int width, int height). 重新设置 Target 的宽高值(单位为 pixel)。
into(Y target).设置资源将被加载到的 Target。
into(ImageView view). 设置资源将被加载到的 ImageView。取消该 ImageView 之前所有的加载并释放资源。
into(int width, int height). 后台线程加载时要加载资源的宽高值(单位为 pixel)。
preload(int width, int height). 预加载 resource 到缓存中(单位为 pixel)。
asBitmap(). 无论资源是不是 gif 动画,都作为 Bitmap 对待。如果是 gif 动画会停在第一帧。
asGif().把资源作为 GifDrawable 对待。如果资源不是 gif 动画将会失败,会回调.error()。

技巧:

禁止内存缓存:

.skipMemoryCache(true)

清除内存缓存:

// 必须在 UI 线程中调用
Glide.get(context).clearMemory();

禁止磁盘缓存:

.diskCacheStrategy(DiskCacheStrategy.NONE)

清除磁盘缓存:

// 必须在后台线程中调用,建议同时 clearMemory() Glide.get(applicationContext).clearDiskCache();

获取缓存大小:

new GetDiskCacheSizeTask(textView).execute(new File(getCacheDir(), DiskCache.Factory.DEFAULT_DISK_CACHE_DIR));

GetDiskCacheSizeTask 源码

class GetDiskCacheSizeTask extends AsyncTask<File, Long, Long> {
private final TextView resultView;

public GetDiskCacheSizeTask(TextView resultView) {
    this.resultView = resultView;
}

@Override
protected void onPreExecute() {
    resultView.setText("Calculating...");
}

@Override
protected void onProgressUpdate(Long... values) { /* onPostExecute(values[values.length - 1]); */ }

@Override
protected Long doInBackground(File... dirs) {
    try {
        long totalSize = 0;
        for (File dir : dirs) {
            publishProgress(totalSize);
            totalSize += calculateSize(dir);
        }
        return totalSize;
    } catch (RuntimeException ex) {
        final String message = String.format("Cannot get size of %s: %s", Arrays.toString(dirs), ex);
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                resultView.setText("error");
                Toast.makeText(resultView.getContext(), message, Toast.LENGTH_LONG).show();
            }
        });
    }
    return 0L;
}

@Override
protected void onPostExecute(Long size) {
    String sizeText = android.text.format.Formatter.formatFileSize(resultView.getContext(), size);
    resultView.setText(sizeText);
}

private static long calculateSize(File dir) {
    if (dir == null) return 0;
    if (!dir.isDirectory()) return dir.length();
    long result = 0;
    File[] children = dir.listFiles();
    if (children != null)
        for (File child : children)
            result += calculateSize(child);
    return result;
}
}

指定资源的优先加载顺序:

//优先加载
Glide
    .with(context)
    .load(heroImageUrl)
    .priority(Priority.HIGH)
    .into(imageViewHero);
//后加载
Glide
    .with(context)
    .load(itemImageUrl)
    .priority(Priority.LOW)
    .into(imageViewItem);

先显示缩略图,再显示原图:

//用原图的 1/10 作为缩略图
Glide
    .with(this)
    .load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
    .thumbnail(0.1f)
    .into(iv_0);
//用其它图片作为缩略图
DrawableRequestBuilder<Integer> thumbnailRequest = Glide
    .with(this)
    .load(R.drawable.news);
Glide.with(this)
    .load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
    .thumbnail(thumbnailRequest)
    .into(iv_0);

================================================================================================================

Glide 可以对图片进行裁剪、模糊、滤镜等处理:

推荐使用独立的图片处理库:wasabeef/glide-transformations,使用也很简单:
compile 'jp.wasabeef:glide-transformations:2.0.0'

之后我们就可以使用 GenericRequestBuilder 或其子类的 transform()或 bitmapTransform()方法设置图片转换了:

//圆形裁剪
Glide.with(this)
    .load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
    .bitmapTransform(new CropCircleTransformation(this))
    .into(iv_0);
//圆角处理
Glide.with(this)
    .load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
    .bitmapTransform(new RoundedCornersTransformation(this,30,0, RoundedCornersTransformation.CornerType.ALL))
    .into(iv_0);
//灰度处理
Glide.with(this)
    .load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
    .bitmapTransform(new GrayscaleTransformation(this))
    .into(iv_0);
//其它变换...

可根据情况使用 GenericRequestBuilder 子类 DrawableRequestBuilder 的 bitmapTransform(Transformation... bitmapTransformations),transform(BitmapTransformation... transformations),transform(Transformation... transformation),或其子类 BitmapRequestBuilder 的 transform(BitmapTransformation... transformations), transform(Transformation... transformations)方法。

当然如果想自己写 Transformation: 最简单的方式就是继承 BitmapTransformation:

private static class MyTransformation extends BitmapTransformation {

public MyTransformation(Context context) {
    super(context);
}

@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, 
        int outWidth, int outHeight) {
   Bitmap myTransformedBitmap = ... //对 Bitmap 进行各种变换处理。
   return myTransformedBitmap;
}

@Override
public String getId() {
    // 返回代表该变换的唯一 Id,会作为 cache key 的一部分。
    // 注意:最好不要用 getClass().getName(),因为容易受混淆影响。如果变换过程不影响缓存数据,可以返回空字符串。
    return "com.example.myapp.MyTransformation";
}

}

使用时只需使用 transform()或 bitmapTransform()方法即可:

Glide.with(yourFragment)
    .load(yourUrl)
    .asBitmap()
    .transform(new MyTransformation(context))
    .into(yourView);

自定义图片处理时 Glide 会自动计算 View/Target 大小,我们不需要传 View 的宽高,当然你可以使用 override(int, int)去改变这种行为。 自定义图片处理时,为了避免创建大量 Bitmap 以及减少 GC,可以考虑重用 Bitmap,这就需要 BitmapPool,典型地就是, 从 Bitmap 池中拿一个 Bitmap,用这个 Bitmap 生成一个 Canvas, 然后在这个 Canvas 上画初始的 Bitmap 并使用 Matrix、Paint、 或者 Shader 处理这张图片。

为了有效并正确重用 Bitmap 需要遵循以下三条准则:

1,永远不要把 transform()传给你的原始 resource 或原始 Bitmap 给 recycle()了,更不要放回 BitmapPool,因为这些都自动完成了。
    值得注意的是,任何从 BitmapPool 取出的用于自定义图片变换的辅助 Bitmap,
    如果不经过 transform()方法返回,就必须主动放回 BitmapPool 或者调用 recycle()回收。

2,如果你从 BitmapPool 拿出多个 Bitmap 或不使用你从 BitmapPool 拿出的一个 Bitmap,一定要返回 extras 给 BitmapPool。

3,如果你的图片处理没有替换原始 resource(例如由于一张图片已经匹配了你想要的尺寸,你需要提前返回), 
    transform()`方法就返回原始 resource 或原始 Bitmap。 

如: private static class MyTransformation extends BitmapTransformation { public MyTransformation(Context context) { super(context); }

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        Bitmap result = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
        // 如果 BitmapPool 中找不到符合该条件的 Bitmap,get()方法会返回 null,就需要我们自己创建 Bitmap 了
        if (result == null) {
            // 如果想让 Bitmap 支持透明度,就需要使用 ARGB_8888
            result = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888);
        }
        //创建最终 Bitmap 的 Canvas.
        Canvas canvas = new Canvas(result);
        Paint paint = new Paint();
        paint.setAlpha(128);
        // 将原始 Bitmap 处理后画到最终 Bitmap 中
        canvas.drawBitmap(toTransform, 0, 0, paint);
        // 由于我们的图片处理替换了原始 Bitmap,就 return 我们新的 Bitmap 就行。
        // Glide 会自动帮我们回收原始 Bitmap。
        return result;
    }

    @Override
    public String getId() {
        // Return some id that uniquely identifies your transformation.
        return "com.example.myapp.MyTransformation";
    }
}

也可以直接实现 Transformation 接口,进行更灵活的图片处理,如进行简单地圆角处理:

public class RoundedCornersTransformation implements Transformation {

private BitmapPool mBitmapPool;
private int mRadius;

public RoundedCornersTransformation(Context context, int mRadius) {
    this(Glide.get(context).getBitmapPool(), mRadius);
}

public RoundedCornersTransformation(BitmapPool mBitmapPool, int mRadius) {
    this.mBitmapPool = mBitmapPool;
    this.mRadius = mRadius;
}

@Override
public Resource<Bitmap> transform(Resource<Bitmap> resource, int outWidth, int outHeight) {
    //从其包装类中拿出 Bitmap
    Bitmap source = resource.get();
    int width = source.getWidth();
    int height = source.getHeight();
    Bitmap result = mBitmapPool.get(width, height, Bitmap.Config.ARGB_8888);
    if (result == null) {
        result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    }
    Canvas canvas = new Canvas(result);
    //以上已经算是教科书式写法了
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
    canvas.drawRoundRect(new RectF(0, 0, width, height), mRadius, mRadius, paint);
    //返回包装成 Resource 的最终 Bitmap
    return BitmapResource.obtain(result, mBitmapPool);
}

@Override
public String getId() {
    return "RoundedTransformation(radius=" + mRadius + ")";
}

}

对请求状态进行监听:

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";

private ImageView iv_0;
private LoggingListener mCommonRequestListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    iv_0 = (ImageView) findViewById(R.id.iv_0);
    mCommonRequestListener = new LoggingListener<String, GlideDrawable>();
    Glide
            .with(this)
            .load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
            .listener(mCommonRequestListener)
            .into(iv_0);

} }

/**

public class LoggingListener implements RequestListener { private final int level; private final String name; private final RequestListener delegate;

public LoggingListener() { this(""); }

public LoggingListener(@NonNull String name) { this(Log.VERBOSE, name); }

public LoggingListener(int level, @NonNull String name) { this(level, name, null); }

public LoggingListener(RequestListener delegate) { this(Log.VERBOSE, "", delegate); }

public LoggingListener(int level, @NonNull String name, RequestListener delegate) { this.level = level; this.name = name; this.delegate = delegate == null ? NoOpRequestListener.get() : delegate; }

@Override public boolean onException(Exception e, A model, Target target, boolean isFirstResource) { android.util.Log.println(level, "GLIDE", String.format(Locale.ROOT, "%s.onException(%s, %s, %s, %s)\n%s", name, e, model, strip(target), isFirst(isFirstResource), android.util.Log.getStackTraceString(e))); return delegate.onException(e, model, target, isFirstResource); }

@Override public boolean onResourceReady(B resource, A model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { String resourceString = strip(getResourceDescription(resource)); String targetString = strip(getTargetDescription(target)); android.util.Log.println(level, "GLIDE", String.format(Locale.ROOT, "%s.onResourceReady(%s, %s, %s, %s, %s)", name, resourceString, model, targetString, isMem(isFromMemoryCache), isFirst(isFirstResource))); return delegate.onResourceReady(resource, model, target, isFromMemoryCache, isFirstResource); }

private String isMem(boolean isFromMemoryCache) { return isFromMemoryCache ? "sync" : "async"; }

private String isFirst(boolean isFirstResource) { return isFirstResource ? "first" : "not first"; }

private String getTargetDescription(Target target) { String result; if (target instanceof ViewTarget) { View v = ((ViewTarget) target).getView(); LayoutParams p = v.getLayoutParams(); result = String.format(Locale.ROOT, "%s(params=%dx%d->size=%dx%d)", target, p.width, p.height, v.getWidth(), v.getHeight()); } else { result = String.valueOf(target); } return result; }

private String getResourceDescription(B resource) { String result; if (resource instanceof Bitmap) { Bitmap bm = (Bitmap) resource; result = String.format(Locale.ROOT, "%s(%dx%d@%s)", resource, bm.getWidth(), bm.getHeight(), bm.getConfig()); } else if (resource instanceof BitmapDrawable) { Bitmap bm = ((BitmapDrawable) resource).getBitmap(); result = String.format(Locale.ROOT, "%s(%dx%d@%s)", resource, bm.getWidth(), bm.getHeight(), bm.getConfig()); } else if (resource instanceof GlideBitmapDrawable) { Bitmap bm = ((GlideBitmapDrawable) resource).getBitmap(); result = String.format(Locale.ROOT, "%s(%dx%d@%s)", resource, bm.getWidth(), bm.getHeight(), bm.getConfig()); } else if (resource instanceof Drawable) { Drawable d = (Drawable) resource; result = String.format(Locale.ROOT, "%s(%dx%d)", resource, d.getIntrinsicWidth(), d.getIntrinsicHeight()); } else { result = String.valueOf(resource); } return result; }

private static String strip(Object text) { return String.valueOf(text).replaceAll("(com|android|net|org)(\.[a-z]+)+\.", ""); } }

public final class NoOpRequestListener implements RequestListener { private static final RequestListener INSTANCE = new NoOpRequestListener();

@SuppressWarnings("unchecked") public static RequestListener get() { return INSTANCE; }

private NoOpRequestListener() { }

@Override public boolean onException(Exception e, A a, Target target, boolean b) { return false; } @Override public boolean onResourceReady(B b, A a, Target target, boolean b2, boolean b1) { return false; } }

通过 GenericRequestBuilder 的 listener()方法添加一个 RequestListener 实现,但要注意,最好不要用匿名类, 也不要每次都创建新的监听器,要使用单例进行统一监听处理,以避免不必要的内存申请和不必要的引用。 方法最好返回 false,以便 Glide 能继续进行后续处理(如显示 error 占位符)。

对资源的下载进度进行监听: 可以借助 OkHttp 的拦截器进行进度监听。OkHttp 的拦截器官方 Sample 请移步这里。我们可以利用这个拦截器进行监听并处理, 需要自定义 ModelLoader 和 DataFetcher,具体请详见我的 Git:https://github.com/shangmingchao/ProgressGlide,欢迎 Star 啊, 不过没有太大必要告诉用户图片加载的进度(sjudd 和 TWiStErRob 他们说的)。同时也可以看一下 TWiStErRob 大神的实现(自备梯子哈~.~)。

集成网络框架 OkHttp

Glide 的网络请求部分可以使用当前最流行的网络请求框架 Volley 或 OkHttp,也可以通过 Glide 的 ModelLoader 接口自己写网络请求。 Glide 默认使用 HttpUrlConnection 进行网络请求,为了让 APP 保持一致的网络请求形式,可以让 Glide 使用我们指定的网络请求形式请求网络资源, 这里我们选 OkHttp (具有支持 HTTP/2、利用连接池技术减少请求延迟、缓存响应结果等等优点),需要添加一个集成库:

//OkHttp 2.x
//compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
//compile 'com.squareup.okhttp:okhttp:2.7.5'

//OkHttp 3.x
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
compile 'com.squareup.okhttp3:okhttp:3.2.0'

注意:

  1. OkHttp 2.x 和 OkHttp 3.x 需使用不同的集成库。
  2. Gradle 会自动将 OkHttpGlideModule 合并到应用的 manifest 文件中。
  3. 如果你没有对所有的 GlideModule 配置混淆规则(即没有使用-keep public class * implements com.bumptech.glide.module.GlideModule), 则需要把 OkHttp 的 GlideModule 进行防混淆配置:

-keep class com.bumptech.glide.integration.okhttp.OkHttpGlideModule

Apps
About Me
Google+: Trinea trinea
GitHub: Trinea