本文将通过模仿手机QQ聊天页面的图片列表,来学习如何使用Picasso展示图片,以及一些图片列表中一些问题的解决方案,完整示例代码地址,赠送一个 BaseRecyclerViewAdapter,不用谢!
先看一下手机QQ的效果
qq.gif
数据源
简单起见我们从相册中查询出一百张本地图片
new Thread(new Runnable() { @Override public void run() { Cursor mCursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA, MediaStore.Images.Media.WIDTH, MediaStore.Images.Media.HEIGHT}, MediaStore.Images.Media.MIME_TYPE + "=? OR " + MediaStore.Images.Media.MIME_TYPE + "=?", new String[] { "image/jpeg", "image/png" }, MediaStore.Images.Media._ID + " DESC"); if (mCursor == null) return; // Take 100 images while (mCursor.moveToNext() && mImageList.size() < MAX_IMAGE) { long id = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Images.Media._ID)); Log.i(TAG, "MediaStore.Images.Media_ID=" + id + ""); String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA)); int width = mCursor.getInt(mCursor.getColumnIndex(MediaStore.Images.Media.WIDTH)); int height = mCursor.getInt(mCursor.getColumnIndex(MediaStore.Images.Media.HEIGHT)); Image image = new Image(Uri.fromFile(new File(path)), width, height); mImageList.add(image); } mCursor.close(); runOnUiThread(new Runnable() { @Override public void run() { mImageListAdapter.addAllData(mImageList); mImageListAdapter.notifyDataSetChanged(); } }); } }).start();
上面的代码从本地获取了最多100张图片,然后将图片信息存储到 ImageInfo 类中,最后通过一个list传递给adapter
其中ImageInfo类的主要属性如下
private final Uri mUri; private int mWidth; private int mHeight; private boolean mNeedResize;
这里的属性 mNeedResize,用来表示是否需要重新计算图片宽高,下面会说到如何使用
到这里,我们的数据源就准备完毕了
解决图片乱跳问题
这是一个比较常见的问题,问题原因是加载图片是一个异步的过程,如果异步回来Bitmap无法对应正确ImageView,就会出现图片跳来跳去的情况。解决方法有很多,这里只说一种(选择困难症的福音)
通过给ImageView设置tag,绑定图片信息,Adapter中具体代码如下:
public void onBindViewHolder(final ImageListAdapter.ImageHolder holder, int position) { final ImageInfo imageInfo = mDataList.get(position); holder.mImageIv.setTag(imageInfo.getUri().getPath()); mPicasso.load(imageInfo.getUri()) .resize(imageInfo.getWidth(), imageInfo.getHeight()) .config(Bitmap.Config.RGB_565) .centerCrop() .into(new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { if (holder.mImageIv.getTag().equals(imageInfo.getUri().getPath())) { holder.mImageIv.setImageBitmap(bitmap); } } ... }); }
在load图片之前,给ImageView设置一个tag,在回调回来之后通过tag判断是否为正确的ImageView,再进行图片的设置,这样就解决了图片乱跳的问题
解决图片尺寸问题
获取到图片的尺寸是高效显示图片的一个前提
从数据源获取
1. 图片从服务器中获取,服务器应该同时返回图片的宽高等信息
2. 图片从本地获取,例如上面的代码,可以看一下是否有相应的API可以查询到图片的宽高
然而很多时候,我们事先并不知道图片的尺寸
预先读取图片信息得到宽高
这种方式是在拿到图片的 Uri 之后,通过预加载,得到图片的宽高信息。
以 Picasso 举例来说(这个方法需要在非主线程中执行)
try { Bitmap bitmap = Picasso.with(context).load(uri).get(); int width = bitmap.getWidth(); int height = bitmap.getHeight(); } catch (IOException e) { e.printStackTrace(); }
一般情况下,我们不会使用这种方式去获取图片的宽高。对于网络图片来说,预加载会把图片下载下来,但是我们并不知道用户是否会查看这张图片,这样就 造成了流量的浪费。同时,对于本地图片来说图片不需要下载,不过读取图片和decode图片也会耗费CPU和内存。另一方面来说,如果使用的是Glide 或者Fresco,会更加的麻烦,这两个库是通过异步的方式返回Bitmap,处理起来更加麻烦
动态计算图片信息得到宽高
个人比较推荐这种做法
具体思路如下:
给ImageView设置一个固定的宽高
在得到Bitmap之后,根据Bitmap的宽高重新计算ImageView的宽高
Adapter中代码如下:
public void onBindViewHolder(final ImageListAdapter.ImageHolder holder, int position) { if (mHeight == 0) return; final ImageInfo imageInfo = mDataList.get(position); holder.mImageIv.setImageResource(R.color.defaultImageSource); if (imageInfo.getHeight() != mHeight) { if (imageInfo.getHeight() == 0) { // set default size imageInfo.setHeight(mHeight); imageInfo.setWidth(mHeight); imageInfo.setNeedResize(true); } else { int width = mHeight * imageInfo.getWidth() / imageInfo.getHeight(); imageInfo.setWidth(Math.min(width, mMaxWidth)); imageInfo.setHeight(mHeight); } } resizeImageView(holder.mImageIv, imageInfo); holder.mImageIv.setTag(imageInfo.getUri().getPath()); Target target = new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { if (holder.mImageIv.getTag().equals(imageInfo.getUri().getPath())) { if (imageInfo.isNeedResize()) { // resize imageView after get bitmap info imageInfo.setHeight(bitmap.getHeight()); imageInfo.setWidth(bitmap.getWidth()); resizeImageView(holder.mImageIv, imageInfo); imageInfo.setNeedResize(false); } holder.mImageIv.setImageBitmap(bitmap); } } ... }; mTargetMap.put(imageInfo.getUri().toString(), target); mPicasso.load(imageInfo.getUri()) .resize(imageInfo.getWidth(), imageInfo.getHeight()) .config(Bitmap.Config.RGB_565) .centerCrop() .into(mTargetMap.get(imageInfo.getUri().toString())); } private static void resizeImageView(ImageView imageView, ImageInfo imageInfo) { ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams(); layoutParams.height = imageInfo.getHeight(); layoutParams.width = imageInfo.getWidth(); imageView.setLayoutParams(layoutParams); }
首先在发现ImageInfo中存储的宽高异常之后,给ImageView设置一个默认的宽高,并且标记当前的图片需要重新计算宽高。在 onBitmapLoaded
之后判断如果需要计算宽高,将bitmap中的宽高取出,重新配置ImageView的宽高并且设置对应的ImageInfo
最终效果
bitmap.gif
如果你在使用Picasso的时候碰到一些问题,这里获取能找到一些答案Picasso中一些问题的解决方法
作者:李冬冬