引言
第一篇给大家分享的是picasso,非常有名的一个图片加载缓存框架。每个强大的框架往往使用是非常简单的,但是背后还有很多不容忽视的功能或者技巧,Picasso也是如此。
目录
- Picasso入门
- Picasso高级
- 在适配器(ListView,GridView...)中使用
- 列表中处理null/空值
- placeholder(),error(),淡入淡出
- 图像缩放,大小调整
Picasso入门
添加到依赖
首选,你需要添加依赖,当前我们会使用最新的版本 2.5.2,添加依赖有众所周知的两种方式。
Gradle
compile 'com.squareup.picasso:picasso:2.5.2'
Maven
<dependency> <groupId>com.squareup.picasso</groupId> <artifactId>picasso</artifactId> <version>2.5.2</version> </dependency>
基本的加载图片
Picasso使用的链式编程,它至少需要三个参数才能完成基本的图像请求
- with(Context context) - 很多 Androd API 会去调用,这里也没区别。
- load(String imageUrl) - 这里指应该加载哪个图像,大多数情况下,我们会使用String ,代表一个url图像。
- into(ImageView targetImageView) - 目标显示的ImageView
不妨来个简单的例子。
ImageView targetImageView = (ImageView) findViewById(R.id.imageView); String internetUrl = "http://i.imgur.com/DvpvklR.png"; Picasso .with(context) .load(internetUrl) .into(targetImageView);
到此,只要保证url众的Image是没问题的,稍等片刻就能见到你想要的图片,如果图像不存在,Picasso将返回到错误回调,如何请求错误回调,稍后我会做说明。简单的三句话就已经让你完成了图片请求,但是这只是冰上的一角。
Picasso高级
除了简单的使用,我们会写发现load是方法是一个重载的方法。其实除了基本的String类型加载,我们还可以尝试从各个渠道去加载我们想要的图片
资源加载
int resourceId = R.mipmap.ic_launcher; Picasso .with(context) .load(resourceId) .into(imageViewResource);
文件加载
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Running.jpg"); Picasso .with(context) .load(file) .into(imageViewFile);
Uri加载
Uri uri = resourceIdToUri(context, R.mipmap.future_studio_launcher); Picasso .with(context) .load(uri) .into(imageViewUri); public static final String ANDROID_RESOURCE = "android.resource://"; public static final String FOREWARD_SLASH = "/"; private static Uri resourceIdToUri(Context context, int resourceId) { return Uri.parse(ANDROID_RESOURCE + context.getPackageName() + FOREWARD_SLASH + resourceId); }
在适配器(ListView,GridView...)中使用
首先,我们需要一些测试图像。
public static String[] eatFoodyImages = { "http://i.imgur.com/rFLNqWI.jpg", "http://i.imgur.com/C9pBVt7.jpg", "http://i.imgur.com/rT5vXE1.jpg", "http://i.imgur.com/aIy5R2k.jpg", "http://i.imgur.com/MoJs9pT.jpg", "http://i.imgur.com/S963yEM.jpg", "http://i.imgur.com/rLR2cyc.jpg", "http://i.imgur.com/SEPdUIx.jpg", "http://i.imgur.com/aC9OjaM.jpg", "http://i.imgur.com/76Jfv9b.jpg", "http://i.imgur.com/fUX7EIB.jpg", "http://i.imgur.com/syELajx.jpg", "http://i.imgur.com/COzBnru.jpg", "http://i.imgur.com/Z3QjilA.jpg", };
我们需要一个Activity,在这之中创建一个Adapter,并为ListView适配。
public class UsageExampleAdapter extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_usage_example_adapter); listView.setAdapter(new ImageListAdapter(UsageExampleAdapter.this, eatFoodyImages)); } }
接下来,怎么可以少了Adapter的布局文件,非常的简单,就是我们想要显示的 ImageView
<?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="200dp"/>
最后,我们就需要去实现 ImageListAdapter 。
public class ImageListAdapter extends ArrayAdapter { private Context context; private LayoutInflater inflater; private String[] imageUrls; public ImageListAdapter(Context context, String[] imageUrls) { super(context, R.layout.listview_item_image, imageUrls); this.context = context; this.imageUrls = imageUrls; inflater = LayoutInflater.from(context); } @Override public View getView(int position, View convertView, ViewGroup parent) { if (null == convertView) { convertView = inflater.inflate(R.layout.listview_item_image, parent, false); } Picasso .with(context) .load(imageUrls[position]) .fit() // 稍后解释 .into((ImageView) convertView); return convertView; } }
重要的地方是在 getView() 这个方法中。当然你会发现Picasso的使用没有太多的变化。专门拿这篇文章去讲解这个看起来不值得,其实背后的Picasso它会在滚动的时自动取消图片请求,清除图片和加载正确的图像到 ImageView。
注 后面我们会进一步说明优化适配器的使用 fit()与tags()。
Picasso的力量:cache
当你在反复的上下滚动,你会看到图像显示比以前快多了。我想你一定是猜到了,这些图片来自高速缓存,无需再从网络加载。Picasso针对加载图片主要源自三个源: 内存-> 磁盘 -> 网络 。最后,有什么是你需要做的,Picasso都已经为了做好了!甚至可以更改缓存的大小,这些我们后续会讲解。
列表中处理null/空值
在使用Picasso中,或许我们会遇到两个问题: IllegalArgumentException: Path must not be empty ,再者就是不完整图片映射,为了确保程序能够正常运行,我们会给出两种方式解决问题。
取消Picasso的图片请求
如果不打算加载,你需要取消这个请求,使用 cancelRequest() 。可以确保这个ImageView没有请求回调。如果用户滚动飞快,可能会发生 ListView与imageView重用。取消请求可以避免不正确的图像显示。
使用占位符
例外一种就是使用占位符,说简单点就是使用默认图片
代码示例
@Override public View getView(int position, View convertView, ViewGroup parent) { if (null == convertView) { convertView = inflater.inflate(R.layout.listview_item_image, parent, false); } ImageView imageView = (ImageView) convertView; if (TextUtils.isEmpty(imageUrls[position])) { // option 1: 取消Picasso的图片请求 Picasso .with(context) .cancelRequest(imageView); imageView.setImageDrawable(null); // option 2: 使用占位符 /* Picasso .with(context) .load(R.drawable.floorplan) .into(imageView); */ } else { Picasso .with(context) .load(imageUrls[position]) .fit() // will explain later .into(imageView); } return convertView; }
placeholder(),error(),淡入淡出
.placeholder() 占位符
我们差点忘记去考虑一个问题:空的ImageView体验非常的不友好,如果你使用Picasso网络加载图片,不同的网络环境会导致不能立即加载出图像,这时候就用到了我们的占位符 .placeholder()。 他会预先加载一张图片(当然,是本地的),直到网络图片加载成功为止。
Picasso .with(context) .load(UsageExampleListViewAdapter.eatFoodyImages[0]) .placeholder(R.mipmap.ic_launcher) .into(imageViewPlaceholder);
.error() 错误占位符
假设我们加载的图片链接已经失效了,我们可以通过Picasso的错误回调选取合适的操作,但是这样未免太复杂了,大多数的情况只需要调用 error() 即可。
Picasso .with(context) .load("http://futurestud.io/non_existing_image.png") .placeholder(R.mipmap.ic_launcher) .error(R.mipmap.future_studio_launcher) .into(imageViewError);
使用 noFade()
如果你想直接显示图片不使用淡入淡出的效果,可以使用 noFade()。
Picasso .with(context) .load(UsageExampleListViewAdapter.eatFoodyImages[0]) .placeholder(R.mipmap.ic_launcher) .error(R.mipmap.future_studio_launcher) .noFade() .into(imageViewFade);
使用noPlaceholder()
我们来想一下下面这种情况:你在ImageView中加载了一张图片,一段时间过后,你想在同样的ImageView中加载另一张图片。使用默认的设置,你在重新调用Picasso的时候,ImageView将会加载之前设置的placeholder的图片。如果这个ImageView在你的UI中很重要的话,在几秒钟内快速的改变ImageView的图片会看起来很不合适。一个比较好的解决方案是调用 .noPlaceholder() 这个方法。在第二张图片加载之前,ImageView会一直显示第一张图片。这对用户来说会很友好。
Picasso .with(context) .load(UsageExampleListViewAdapter.eatFoodyImages[0]) .placeholder(R.mipmap.ic_launcher) .into(imageViewnoPlaceholder, new Callback() { @Override public void onSuccess() { Picasso .with(context) .load(UsageExampleListViewAdapter.eatFoodyImages[1]) .noPlaceholder() .into(imageViewNoPlaceholder); } @Override public void onError() { } });
图像缩放,大小调整
通过resize(x,y)改变大小
通常来说,如果你的服务器的图片刚好吻合你图像的尺寸,这将会很好的平衡带宽,内存消耗,图像质量,但是这些总是无法如愿以偿。如果得到的图像是一个无法满足现象的大小,可以使用 resize(horizontalSize, verticalSize) 来改变图片的大小。
Picasso .with(context) .load(UsageExampleListViewAdapter.eatFoodyImages[0]) .resize(600, 200) .into(imageViewResize);
使用scaleDown()
如果我们调用了 resize(x,y) 方法的话,Picasso一般会重新计算以改变图片的加载质量,比如一张小图变成一张大图进行展示的时候,但是如果我们的原图是比我们从新resize的新图规格大的时候,我们就可以调用onlyScaleDown()来直接进行展示而不再重新计算.
Picasso .with(context) .load(UsageExampleListViewAdapter.eatFoodyImages[0]) .resize(6000, 2000) .onlyScaleDown() //如果它大于6000x2000像素,将仅对图像进行调整大小。 .into(imageViewResizeScaleDown);
避免图像缩放与拉伸
当我们操作图片时,原图与目标的尺寸比例,会导致失真。为了防止这种情况发生。Picasso提供了两种解决的方法:调用 centerCrop()或centerInside()。 centerCrop() centerCrop()使用了一种裁剪技术去缩放图片,去填充ImageView的界限,然后裁剪掉多余的部分。使用这个方法会使ImageView会被填充的很合适,但是图片可能不能完全显示出来。
Picasso .with(context) .load(UsageExampleListViewAdapter.eatFoodyImages[0]) .resize(600, 200) // resizes the image to these dimensions (in pixel) .centerCrop() .into(imageViewResizeCenterCrop);
CenterInside()
centerInside()也是一种裁剪技术,缩放时图片的宽和高同时等于或小于设定的ImageView的边界。图片会被完全显示出来,但是也许不能完全填充ImageView。
Picasso .with(this) .load(eatFoodyImages[0]) .resize(600, 200) .centerInside() .into(imageView);
fit()
前面介绍的功能以及可以基本解决你的缩放需求了,这里还有一个功能会非常有用:fit()。
Picasso .with(context) .load(UsageExampleListViewAdapter.eatFoodyImages[0]) .fit() // call .centerInside() or .centerCrop() to avoid a stretched image .into(imageViewFit);
fit()会去测量ImageView的宽高,并且在内部使用resize(),从而缩小图片的尺寸去适配ImageView。关于fit()有两点必须知道:首先,调用fit()会延时图片请求,因为Picasso需要等待直到ImageView的尺寸被测量完毕;其次,只可以使用一个ImageView做完fit的目标(稍后我们会看看其他目标)。 优势是图片使用的是最低的分辨率,不会受到图片质量的影响。更低的分辨率意味着需要占用更少的缓存。
总结
到此,Picasso的第一阶段学习就先到这里,在之前博主对于Picasso的使用大部分停留在这一部分,是的,这里足够使用了。如果你还有充足的时间不妨接着往下看,因为它还有更多小功能。