This article will learn how to use Picasso to show pictures by imitating the picture list of mobile QQ chat page, as well as some solutions to some problems in the picture list, complete example code address, complimentary a BaseRecyclerViewAdapter, you are welcome!

Take a look at the effect of mobile QQ first





qq.gif

The data source

Just to keep things simple let’s look up 100 local images from the album

new Thread(new Runnable() {
    @Override
    public void run(a) {
        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(a) { mImageListAdapter.addAllData(mImageList); mImageListAdapter.notifyDataSetChanged(); }}); } }).start();Copy the code

The above code retrieves up to 100 images locally and stores the image information in the ImageInfo class, which is passed to adapter through a list. The main attributes of the ImageInfo class are as follows

private final Uri mUri;
private int mWidth;
private int mHeight;
private boolean mNeedResize;Copy the code

The mNeedResize property here, which tells us if we need to recalculate the width and height of the image, and we’ll talk about how to use that, and now we have our data source ready, okay

Solve picture hopping problem

This is a common problem because loading an image is an asynchronous process, and if the Bitmap does not correspond to the correct ImageView, the image will jump around. There are many ways to solve this problem, but one of them is to attach a tag to the ImageView. The Adapter code is as follows:

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); }}... }); }Copy the code

Before loading the image, set a tag to the ImageView, and then use the tag to determine if it is the correct ImageView after the callback. This will solve the problem of image hopping

Solve image size problem

Obtaining the size of an image is a prerequisite for efficient display of images

Get from data source

  1. The image is fetched from the server, which should also return the width and height of the image
  2. The image is obtained locally, such as the code above. Let’s see if there is an API that can query the width and height of the image

Many times, however, we don't know the size of the image in advance

Read the picture information in advance to get the width and height

In this way, after getting the Uri of the image, the width and height information of the image can be obtained through preloading.

Use Picasso as an example (this method needs to be executed in a non-main thread)

try {
    Bitmap bitmap = Picasso.with(context).load(uri).get();
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
} catch (IOException e) {
    e.printStackTrace();
}Copy the code

Normally, we don’t use this method to get the width and height of the image. For web images, preloading will download the image, but we do not know whether the user will view the image, which causes traffic waste. Also, images don’t need to be downloaded for local images, but reading and decode images can also be CPU and memory intensive. On the other hand, using Glide or Fresco is even more troublesome, as both libraries return bitmaps asynchronously and are more cumbersome to process

Dynamically calculate the picture information to get the width and height

The specific idea of this approach is recommended as follows:

  1. Give the ImageView a fixed width and height
  2. After the Bitmap is obtained, the width and height of the ImageView are recalculated based on the width and height of the Bitmap

The code in Adapter is as follows:

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);
}Copy the code

First, after finding an abnormal width and height stored in ImageInfo, set a default width and height for the ImageView, and mark the current image to recalculate the width and height. After onBitmapLoaded, if the width and height need to be calculated, extract the width and height from the bitmap, reconfigure the width and height of the ImageView and set the corresponding ImageInfo

The final result





bitmap.gif

If you have problems with Picasso, here are some solutions to find answers to some of the problems with Picasso