引言

第一篇给大家分享的是picasso,非常有名的一个图片加载缓存框架。每个强大的框架往往使用是非常简单的,但是背后还有很多不容忽视的功能或者技巧,Picasso也是如此。


目录

  1. Picasso入门
  2. Picasso高级
  3. 在适配器(ListView,GridView...)中使用
  4. 列表中处理null/空值
  5. placeholder(),error(),淡入淡出
  6. 图像缩放,大小调整

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 = "https://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 = {  
  "https://i.imgur.com/rFLNqWI.jpg",
  "https://i.imgur.com/C9pBVt7.jpg",
  "https://i.imgur.com/rT5vXE1.jpg",
  "https://i.imgur.com/aIy5R2k.jpg",
  "https://i.imgur.com/MoJs9pT.jpg",
  "https://i.imgur.com/S963yEM.jpg",
  "https://i.imgur.com/rLR2cyc.jpg",
  "https://i.imgur.com/SEPdUIx.jpg",
  "https://i.imgur.com/aC9OjaM.jpg",
  "https://i.imgur.com/76Jfv9b.jpg",
  "https://i.imgur.com/fUX7EIB.jpg",
  "https://i.imgur.com/syELajx.jpg",
  "https://i.imgur.com/COzBnru.jpg",
  "https://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="https://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("https://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的使用大部分停留在这一部分,是的,这里足够使用了。如果你还有充足的时间不妨接着往下看,因为它还有更多小功能。