版权声明:本文为博主原创文章,转载注明出处即可。
Android框架之路——Glide加载图片(结合RecyclerView、CardView)
一、简介:
在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 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'
三、解锁姿势:
-
最简单的使用:
Glide.with(this) .load("http://inthecheesefactory.com/uploads/source/nestedfragment/fragments.png") .into(imageView);
-
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生命周期控制。
-
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(“http://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)
- 重要功能:
- .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) 先显示缩略图,再显示原图
- 对图片进行裁剪、模糊、滤镜等处理
- 对请求状态进行监听
- 对资源的下载进度进行监听
- 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:
- 这里我们会用到ButterKnife,不会使用的可以看一下Android框架之路——ButterKnife的使用;
-
看一下Demo效果:
-
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); }
-
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; } }
-
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。别问我为什么,我也不准备回答这个问题,因为我也不知道,你可以设置成别的试试就知道了。
<?xml version="1.0" encoding="utf-8"?> <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<Meizi> mData; public MeiziAdapter(Context context, List<Meizi> 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<Bitmap>(screenWidth, screenWidth) { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super 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); } } }); }
-