Android模仿手机QQ聊天页面图片列表——Picasso实现
本文将通过模仿手机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,再进行图片的设置,这样就解决了图片乱跳的问题
解决图片尺寸问题
获取到图片的尺寸是高效显示图片的一个前提
从数据源获取
- 图片从服务器中获取,服务器应该同时返回图片的宽高等信息
- 图片从本地获取,例如上面的代码,可以看一下是否有相应的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中一些问题的解决方法