Picasso的使用:使用Transformation,下载后预处理图片并显示

在stackoverflow上闲逛的时候,发现有个哥们问了这么一个问题:Draw a line on ImageView set by Picasso

意思是他想在使用Picasso的时候,对加载的图片做一个类似的滤镜的功能,需要将图片的中间画一条横线,该怎么处理呢?

如果熟悉Picasso的机制,当然可以很快搞定,但如果不是很熟悉呢?最好的办法当然是探索源码,从源码中分析Picasso的原理,进而看看到底是怎么样来实现这个功能。我们从以下的角度来分析:

  • 尽可能使用Picasso线程的API,不去修改Picasso的源代码
  • 既然是图片下载之后添加横线,那么必然对应着Picasso中图片获取完成之后,分发结果的相关代码。

我们现在还没有完全分析完Picasso的源代码,但顺着这个路子,我们依然可以发现关于结果分发的相关代码。

在我们没有特殊的需求之前,我们一般是这样简单的使用Picasso的:

Picasso.with(context).load("http://www.baidu.com/img/bdlogo.png").into(imageview);

我们依次分析

  • Picasso.with(context)是利用默认的构造器创建一个Picasso的对象,因此我们首先找一找Picasso的类当中,有没有相关的函数。我们来看Picasso的构造函数:
    Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
        RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers,
        Stats stats, boolean indicatorsEnabled, boolean loggingEnabled) {

    从构造函数的名称,可以大体了解每一个参数是做什么使用的,这里面有一个RequestTransformer对象可能有点靠近,那我们去看看这段函数是做什么使用的。

    /**
     * A transformer that is called immediately before every request is submitted. This can be used to
     * modify any information about a request.
     * 请求在提交之前,会立刻调用一个transformer。这个可以用来一个请求的任何信息
     * <p>
     * For example, if you use a CDN you can change the hostname for the image based on the current
     * location of the user in order to get faster download speeds.
     * <p>
     * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible
     * way at any time.
     */
    public interface RequestTransformer {
      /**
       * Transform a request before it is submitted to be processed.
       *
       * @return The original request or a new request to replace it. Must not be null.
       */
      Request transformRequest(Request request);
    
      /** A {@link RequestTransformer} which returns the original request. */
      /** 返回原始版本的Request */
      RequestTransformer IDENTITY = new RequestTransformer() {
        @Override public Request transformRequest(Request request) {
          return request;
        }
      };
    }

    可以看出,这其实并不是对结果的一个预处理,而是对请求的预处理,也就是,如果我们有需要,那么可以在同一的在Volley中,去对输入的请求进行统一的控制,比如本来要访问的域名都是a.happyhls.me,那么我就可以通过这个都给修改为b.happyhls.me,在一个系统中可能多个版本比如v1,v2同时运行的时候,会特别有用。

  • 既然Picasso的类中没有相关的信息,那么我们继续向下观察:
    Picasso.with(context).load("http://www.baidu.com/img/bdlogo.png")

    根据我们之前的学习,程序执行到这里,其实是构建了一个RequestCreator对象,因此,我们可以继续分析ReqeustCreator对象,有没有可以发现的地方。在其中我们看到

    /**
     * Add a custom transformation to be applied to the image.
     * <p>
     * Custom transformations will always be run after the built-in transformations.
     */
    // TODO show example of calling resize after a transform in the javadoc
    public RequestCreator transform(Transformation transformation) {
      data.transform(transformation);
      return this;
    }

    看到这里,注释很明了了,将需要设置的图片进行一次自定义的变换。作者还标记了一个//TODO,要写一个重新设置大小的例子。当然TODO嘛,肯定是没有。但既然找到地方了,那么我们就自己来写一个就可以了。观察这个方法,其实可以发现其实就是将数据在Transformation中走了一遍。

  • 那么我们继续分析Transformation,看看其中的门道。
    package com.squareup.picasso;
    
    import android.graphics.Bitmap;
    
    /** Image transformation. */
    public interface Transformation {
      /**
       * Transform the source bitmap into a new bitmap. If you create a new bitmap instance, you must
       * call {@link android.graphics.Bitmap#recycle()} on {@code source}. You may return the original
       * if no transformation is required.
       */
      Bitmap transform(Bitmap source);
    
      /**
       * Returns a unique key for the transformation, used for caching purposes. If the transformation
       * has parameters (e.g. size, scale factor, etc) then these should be part of the key.
       */
      String key();
    }
    

    好了,这里是Transformation接口的全部代码,可以看到,Bitmap transform(Bitmap source)就是我们需要处理的地方,通过实现这个方法,可以将已经解码的图片source,根据自己的需要进行处理,并返回一个新的图片。需要注意的,处理完成,在返回之前,我们必须要通过方法{@link android.graphics.Bitmap#recycle()}将之前的资源回收掉。如果不需要的话,之前返回原始的图片就可以。

综上,我们已经根据代码路径,分析完成我们所需要做的事情,下面就是我写的例子,实现的效果也简单,就是解决上面的问题中提到的,如何在图片的中间加一条横线。上代码,Github

public class PostProcessActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_postprocess);
    ImageView imageview = (ImageView)findViewById(R.id.imageview);
    
    Picasso picasso = Picasso.with(getApplicationContext());
    DrawLineTransformation myTransformation = new DrawLineTransformation();
    picasso.load("http://www.baidu.com/img/bdlogo.png").transform(myTransformation).into(imageview);
    
  }

  class DrawLineTransformation implements Transformation {

    @Override
    public String key() {
      // TODO Auto-generated method stub
      return "drawline";
    }

    @Override
    public Bitmap transform(Bitmap bitmap) {
      // TODO Auto-generated method stub
      synchronized (DrawLineTransformation.class) {
        if(bitmap == null) {
          return null;
        }
        Bitmap resultBitmap = bitmap.copy(bitmap.getConfig(), true);
        Canvas canvas = new Canvas(resultBitmap);
        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(10);
        canvas.drawLine(0, resultBitmap.getHeight()/2, resultBitmap.getWidth(), resultBitmap.getHeight()/2, paint);
        bitmap.recycle();
        return resultBitmap;
      }
    }
  }
}

明白思路之后,代码就很简单了,我注意了几个小细节,如果不相近,请朋友们补充:

  • 同解码一样,在处理的时候,添加同步,同一时刻仅仅处理一张图片
  • 像作者注释中写的一样,在新的图片生成之后,要调用bitmap.recycle()方法释放之前的旧图片所占用的空间。

好了,看一下效果:

device-2014-11-23-231406

 

后面我们会和Volley一样,继续分析Picasso的源代码。

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注