Android框架之路——Glide加载图片(结合RecyclerView、CardView)

Android框架之路——Glide加载图片

一、简介:

在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide 的图片加载库,作者是bumptech。这个库被广泛的运用在google的开源项目中,包括2014年google I/O大会上发布的官方app。

  • 使用简单
  • 可配置度高,自适应程度高
  • 支持常见图片格式 Jpg png gif webp
  • 支持多种数据源 网络、本地、资源、Assets 等
  • 高效缓存策略 支持Memory和Disk图片缓存 默认Bitmap格式采用RGB_565内存使用至少减少一半
  • 生命周期集成 根据Activity/Fragment生命周期自动管理请求
  • 高效处理Bitmap 使用Bitmap Pool使Bitmap复用,主动调用recycle回收需要回收的Bitmap,减小系统回收压力.

然后呢,这篇文章的重点不完全放在了Glide上,重要的是完成了一个demo——RecyclerView+CardView+Glide加载图片实现瀑布流,写过瀑布流的都知道,这中间有个bug,滑动的时候item位置会变动,这效果就很难受了。本文参考了一位前辈的方法,将其移植到我们的demo中来,算是基本解决了这个问题。

二、添加依赖:

去github上查看最新添加依赖:https://github.com/bumptech/glide

repositories {
  mavenCentral() // jcenter() works as well because it pulls from Maven Central
}

dependencies {
  compile 'com.github.bumptech.glide:glide:3.7.0'
  compile 'com.android.support:support-v4:19.1.0'
}

如果你需要各种变换效果,你可以继续添加:

compile 'jp.wasabeef:glide-transformations:2.0.1'
compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0'   

三、解锁姿势:

  1. 最简单的使用:

    Glide.with(this)
         .load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png")
         .into(imageView);
  2. 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生命周期控制。
  3. load()的使用:
    Glide基本可以load任何可以拿到的媒体资源,load的参数也不局限于String类型。
    可以拿到的资源:

    • SD卡资源:load(“file://”+ Environment.getExternalStorageDirectory().getPath()+”/test.jpg”)
    • assets资源:load(“file:///android_asset/f003.gif”)
    • raw资源:load(“Android.resource://com.frank.glide/raw/raw_1”)或load(“android.resource://com.frank.glide/raw/”+R.raw.raw_1)
    • drawable资源:load(“android.resource://com.frank.glide/drawable/news”)或load(“android.resource://com.frank.glide/drawable/”+R.drawable.news)
    • ContentProvider资源:load(“content://media/external/images/media/139469”)
    • http资源:load(“https://img-my.csdn.net/uploads/201508/05/1438760757_3588.jpg“)
    • https资源:load(“https://img.alicdn.com/tps/TB1uyhoMpXXXXcLXVXXXXXXXXXX-476-538.jpg_240x5000q50.jpg_.webp“)

    load()的参数类型:

    • load(Uri uri),
    • load(File file),
    • load(Integer resourceId),
    • load(URL url),
    • load(byte[] model),
    • load(T model),
    • loadFromMediaStore(Uri uri)
  4. 重要功能:
    • .skipMemoryCache(true) 禁止内存缓存
    • .get(context).clearMemory() 清除内存缓存,必须在UI线程中调用
    • .diskCacheStrategy(DiskCacheStrategy.NONE) 禁止磁盘缓存
    • .get(applicationContext).clearDiskCache() 清除磁盘缓存,必须在后台线程中调用,建议同时clearMemory()
    • new GetDiskCacheSizeTask(textView).execute(new File(getCacheDir(),DiskCache.Factory.DEFAULT_DISK_CACHE_DIR)) 获取缓存大小
    • .priority(Priority.HIGH/LOW) 指定资源的优先加载顺序
    • .thumbnail(0.1f) 先显示缩略图,再显示原图
    • 对图片进行裁剪、模糊、滤镜等处理
    • 对请求状态进行监听
    • 对资源的下载进度进行监听
  5. Api方法说明:
    • thumbnail(float sizeMultiplier)——请求给定系数的缩略图。如果缩略图比全尺寸图先加载完,就显示缩略图,否则就不显示。系数sizeMultiplier必须在(0,1)之间,可以递归调用该方法。
    • sizeMultiplier(float sizeMultiplier)——在加载资源之前给Target大小设置系数。
    • diskCacheStrategy(DiskCacheStrategy strategy)——设置缓存策略。默认采用DiskCacheStrategy.RESULT策略,对于download only操作要使用DiskCacheStrategy.SOURCE。
      • DiskCacheStrategy.SOURCE:缓存原始数据,
      • DiskCacheStrategy.RESULT:缓存变换(如缩放、裁剪等)后的资源数据,
      • DiskCacheStrategy.NONE:什么都不缓存,
      • DiskCacheStrategy.ALL:缓存SOURC和RESULT。
    • 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

四、来个Demo:

  1. 这里我们会用到ButterKnife,不会使用的可以看一下Android框架之路——ButterKnife的使用;
  2. 看一下Demo效果:

  3. Glide基本使用的demo:

    private void initData() {
            mTvGlide1.setText("加载网络图片");
            Glide.with(this).load("http://7xi8d6.com1.z0.glb.clouddn.com/2017-04-28-18094719_120129648541065_8356500748640452608_n.jpg").into(mIvGlide1);
    
            mTvGlide2.setText("加载资源图片");
            Glide.with(this).load(R.mipmap.ic_launcher).into(mIvGlide2);
    
            mTvGlide3.setText("加载手机SD卡图片");
            String path = Environment.getExternalStorageDirectory() + "/back.jpg";
            File file = new File(path);
            Uri uri = Uri.fromFile(file);
            Glide.with(this).load(uri).into(mIvGlide3);
    
            mTvGlide4.setText("加载网络gif");
            String gifUrl = "http://b.hiphotos.baidu.com/zhidao/pic/item/faedab64034f78f066abccc57b310a55b3191c67.jpg";
            Glide.with(this).load(gifUrl).into(mIvGlide4);
    
            mTvGlide5.setText("加载资源gif");
            Glide.with(this).load(R.drawable.loading).into(mIvGlide5);
    
            mTvGlide6.setText("加载本地gif");
            String path1 = Environment.getExternalStorageDirectory() + "/meinv2.jpg";
            File file1 = new File(path1);
            Uri uri1 = Uri.fromFile(file1);
            Glide.with(this).load(uri1).placeholder(R.mipmap.ic_launcher).into(mIvGlide6);
    
            mTvGlide7.setText("加载本地小视频和快照");
            String path2 = Environment.getExternalStorageDirectory() + "/video.mp4";
            File file2 = new File(path2);
            Uri uri2 = Uri.fromFile(file2);
            Glide.with(this).load(uri2).placeholder(R.mipmap.ic_launcher).into(mIvGlide7);
    
    
            mTvGlide8.setText("设置缩略图比例,然后,先加载缩略图,再加载原图");
            Glide.with(this).load("http://7xi8d6.com1.z0.glb.clouddn.com/2017-04-28-18094719_120129648541065_8356500748640452608_n.jpg").thumbnail(0.1f).centerCrop().placeholder(R.mipmap.ic_launcher).into(mIvGlide8);
    
            mTvGlide9.setText("先建立一个缩略图对象,然后,先加载缩略图,再加载原图");
            DrawableRequestBuilder builder = Glide.with(this).load("http://7xi8d6.com1.z0.glb.clouddn.com/2017-04-28-18094719_120129648541065_8356500748640452608_n.jpg");
    
                                 Glide.with(this).load(uri2).thumbnail(builder).centerCrop().placeholder(R.mipmap.ic_launcher).into(mIvGlide9);
        }
  4. Glide的各种变换效果:
    效果都在注释中,你可以看效果来进行选择哪种变换。有些我也叫不出是啥,尴尬….还是看demo效果吧。

    @Override
    public void onBindViewHolder(TransViewHolder holder, int position) {
        int integer = Integer.parseInt(mList.get(position));
        holder.mTvTran.setText("样式"+(position+1));
        switch (integer) {
            //五角星形的外框Mask
            case 1: {
                int width = UIUtils.dip2px(mContext, 133.33f);
                int height = UIUtils.dip2px(mContext, 126.33f);
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .override(width, height)
                        .bitmapTransform(new CenterCrop(mContext),
                                new MaskTransformation(mContext, R.drawable.mask_starfish))
                        .into(holder.mIvTran);
                break;
            }
            //点9图片的外框Mask
            case 2: {
                int width = UIUtils.dip2px(mContext, 150.0f);
                int height = UIUtils.dip2px(mContext, 100.0f);
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .override(width, height)
                        .bitmapTransform(new CenterCrop(mContext),
                                new MaskTransformation(mContext, R.drawable.mask_chat_right))
                        .into(holder.mIvTran);
                break;
            }
            //裁剪图片的上方部分区域
            case 3:
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .bitmapTransform(
                                new CropTransformation(mContext, 300, 100, CropTransformation.CropType.TOP))
                        .into(holder.mIvTran);
                break;
            //裁剪图片的上方100-300区域
            case 4:
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .bitmapTransform(new CropTransformation(mContext, 300, 100))
                        .into(holder.mIvTran);
                break;
            //裁剪图片的下方部分区域
            case 5:
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .bitmapTransform(
                                new CropTransformation(mContext, 300, 100, CropTransformation.CropType.BOTTOM))
                        .into(holder.mIvTran);
    
                break;
            //显示方形图
            case 6:
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .bitmapTransform(new CropSquareTransformation(mContext))
                        .into(holder.mIvTran);
                break;
            //显示圆形图
            case 7:
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .bitmapTransform(new CropCircleTransformation(mContext))
                        .into(holder.mIvTran);
                break;
            //彩色滤镜样式
            case 8:
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .bitmapTransform(new ColorFilterTransformation(mContext, Color.argb(80, 255, 0, 0)))
                        .into(holder.mIvTran);
                break;
            //灰度图
            case 9:
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .bitmapTransform(new GrayscaleTransformation(mContext))
                        .into(holder.mIvTran);
                break;
            //边框圆角化图片
            case 10:
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .bitmapTransform(new RoundedCornersTransformation(mContext, 30, 0,
                                RoundedCornersTransformation.CornerType.BOTTOM))
                        .into(holder.mIvTran);
                break;
            //毛玻璃效果
            case 11:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(new BlurTransformation(mContext, 25))
                        .into(holder.mIvTran);
                break;
    
            case 12:
                Glide.with(mContext)
                        .load(R.drawable.demo)
                        .bitmapTransform(new ToonFilterTransformation(mContext))
                        .into(holder.mIvTran);
                break;
            case 13:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(new SepiaFilterTransformation(mContext))
                        .into(holder.mIvTran);
                break;
            case 14:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(new ContrastFilterTransformation(mContext, 2.0f))
                        .into(holder.mIvTran);
                break;
            case 15:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(new InvertFilterTransformation(mContext))
                        .into(holder.mIvTran);
                break;
            case 16:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(new PixelationFilterTransformation(mContext, 20))
                        .into(holder.mIvTran);
                break;
            case 17:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(new SketchFilterTransformation(mContext))
                        .into(holder.mIvTran);
                break;
            case 18:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(
                                new SwirlFilterTransformation(mContext, 0.5f, 1.0f, new PointF(0.5f, 0.5f)))
                        .into(holder.mIvTran);
                break;
            case 19:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(new BrightnessFilterTransformation(mContext, 0.5f))
                        .into(holder.mIvTran);
                break;
            case 20:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(new KuwaharaFilterTransformation(mContext, 25))
                        .into(holder.mIvTran);
                break;
            case 21:
                Glide.with(mContext)
                        .load(R.drawable.check)
                        .bitmapTransform(new VignetteFilterTransformation(mContext, new PointF(0.5f, 0.5f),
                                new float[] { 0.0f, 0.0f, 0.0f }, 0f, 0.75f))
                        .into(holder.mIvTran);
                break;
        }
    }
  5. Glide的小综合案列(RecyclerView+CardView加载美女图片):

    • 使用的api:Gankio的福利——http://gank.io/api/data/福利/10/1,10代表每页加载数量,1代表加载第几页数据。
    • 使用CardView:

      app:cardBackgroundColor         //这是设置背景颜色 
      app:cardCornerRadius            //这是设置圆角大小 
      app:cardElevation               //这是设置z轴的阴影 
      app:cardMaxElevation            //这是设置z轴的最大高度值 
      app:cardUseCompatPadding        //是否使用CompatPadding 
      app:cardPreventCornerOverlap    //是否使用PreventCornerOverlap 
      app:contentPadding              // 设置内容的padding 
      app:contentPaddingLeft          //设置内容的左padding 
      app:contentPaddingTop           //设置内容的上padding 
      app:contentPaddingRight         //设置内容的右padding 
      app:contentPaddingBottom        //设置内容的底padding
      

      我的item_mm.xml如下,其中CardView的高度要设置为wrap_content,ImageView的高度也要设置为wrap_content。别问我为什么,我也不准备回答这个问题,因为我也不知道,你可以设置成别的试试就知道了。

      
      <android.support.v7.widget.CardView
          xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          app:cardCornerRadius="3dp"
          app:cardElevation="3dp"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:orientation="vertical">
      
          <ImageView
              android:id="@+id/iv_mm"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:scaleType="centerCrop"/>
      
      android.support.v7.widget.CardView>
    • 配置权限:

      <uses-permission android:name="android.permission.INTERNET"/>
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    • 编写Addapter类,在onBindViewHolder中使用Glide通过URL加载图片即可:

      public class MeiziAdapter extends RecyclerView.Adapter<MeiziAdapter.ViewHolder>{
      
          private Context mContext;
          private List mData;
      
          public MeiziAdapter(Context context, List data) {
              mContext = context;
              mData = data;
          }
      
          @Override
          public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
              View view = LayoutInflater.from(mContext).inflate(R.layout.item_meizi, parent, false);
              return new ViewHolder(view);
          }
      
          @Override
          public void onBindViewHolder(ViewHolder holder, int position) {
              String url = mData.get(position).getResults().get(0).getUrl();
              Glide.with(mContext).load(url).into(holder.mIvMm);
          }
      
          @Override
          public int getItemCount() {
              if(mData == null)
                  return 0;
              return mData.size();
          }
      
          public class ViewHolder extends RecyclerView.ViewHolder {
              @BindView(R.id.iv_mm)
              ImageView mIvMm;
      
              ViewHolder(View view) {
                  super(view);
                  ButterKnife.bind(this, view);
              }
          }
      }
    • 问题:现在我们已经可以实现加载图片的效果了,但是在上下滑动时,出现重新定义宽高,导致cardview滑动状态。这篇文章也提到这件事。我参考一个开源的软件中的写法,附在下面:

      • 在Adapter中重写getItemViewType方法;

        @Override
        public int getItemViewType(int position) {
            WindowManager windowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
            DisplayMetrics dm = new DisplayMetrics();
            Display display = windowManager.getDefaultDisplay();
            display.getMetrics(dm);
            final int screenWidth = dm.widthPixels;
            return Math.round((float) screenWidth / (float) mData.get(position).getHeight() * 10f);
        }
      • 对我们的Meizi.java添加一个属性——高度height,并生成getter、setter方法;

        ......
        private int height;
        
        public int getHeight() {
            return height;
        }
        
        public void setHeight(int height) {
            this.height = height;
        }
        
        public boolean isError() {
            return error;
        }
        .......
      • 下面着重是我们的onBindViewHolder方法,主体思想还是根据宽度获取该显示的高度。

        @Override
        public void onBindViewHolder(final ViewHolder holder, final int position) {
        
           //存在记录的高度时先Layout再异步加载图片
           if (mData.get(holder.getAdapterPosition()).getHeight() > 0) {
               ViewGroup.LayoutParams layoutParams = holder.mIvMm.getLayoutParams();
               layoutParams.height = mData.get(holder.getAdapterPosition()).getHeight();
           }
        
        //获取屏幕宽度
           WindowManager windowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
           DisplayMetrics dm = new DisplayMetrics();
           Display display = windowManager.getDefaultDisplay();
           display.getMetrics(dm);
           final int screenWidth = dm.widthPixels;
        
           String url = mData.get(position).getResults().get(0).getUrl();
           Glide.with(mContext).load(url).asBitmap().diskCacheStrategy(DiskCacheStrategy.ALL)
                   .into(new SimpleTarget(screenWidth, screenWidth) {
                       @Override
                       public void onResourceReady(Bitmap resource, GlideAnimationsuper Bitmap> glideAnimation) {
                           if(holder.getAdapterPosition() != RecyclerView.NO_POSITION) {
                               if (mData.get(holder.getAdapterPosition()).getHeight() <= 0) {
                                   int width = resource.getWidth();
                                   int height = resource.getHeight();
                                   int realHeight = screenWidth * height / width / 2;
                                   mData.get(holder.getAdapterPosition()).setHeight(realHeight);
                                   ViewGroup.LayoutParams lp = holder.mIvMm.getLayoutParams();
                                   lp.height = realHeight;
                                   if(width < screenWidth / 2)
                                       lp.width = screenWidth / 2;
                               }
                               holder.mIvMm.setImageBitmap(resource);
                           }
                       }
                   });
        }

五、Demo下载:

     源码链接


个人公众号:每日推荐一篇技术博客,坚持每日进步一丢丢…欢迎关注,想建个微信群,主要讨论安卓和Java语言,一起打基础、用框架、学设计模式,菜鸡变菜鸟,菜鸟再起飞,愿意一起努力的话可以公众号留言,谢谢…

Android框架之路——Glide加载图片(结合RecyclerView、CardView)_第1张图片

你可能感兴趣的:(Android,Android框架之路)