美文网首页兴趣Android 技术收集今日看点
Android模仿手机QQ聊天页面图片列表——Picasso实现

Android模仿手机QQ聊天页面图片列表——Picasso实现

作者: 李冬冬 | 来源:发表于2017-01-09 16:52 被阅读1694次

    本文将通过模仿手机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,处理起来更加麻烦

    动态计算图片信息得到宽高

    个人比较推荐这种做法
    具体思路如下:

    1. 给ImageView设置一个固定的宽高
    2. 在得到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中一些问题的解决方法

    相关文章

      网友评论

      • 亚欧沙龙:怎么源代码消失了?
      • MeloDev:如果再加上拖拽上抛发送的功能就完整了
        李冬冬: @MeloDev 我才知道qq有这个功能😂,不过这个文章主要是演示图片列表一些处理方法,不打算加其他的
      • 顾夕城:不明觉厉

      本文标题:Android模仿手机QQ聊天页面图片列表——Picasso实现

      本文链接:https://www.haomeiwen.com/subject/tpjhbttx.html