Android Fresco图片处理库用法API英文原文文档2-2(Facebook开源Android图片库)

来源:互联网 发布:软件流程图制作软件 编辑:程序博客网 时间:2024/04/29 14:03

Android Fresco图片处理库用法API英文原文文档2-2(Facebook开源Android图片库)

这是英文文档的第二部分(2):DRAWEE GUIDE

由于第二部分内容多一些,所以分为2个文章发。方便大家查看。

Using the ControllerBuilder

SimpleDraweeView has two methods for specifying an image. The easy way is to just callsetImageURI.

If you want more control over how the Drawee displays your image, you can use aDraweeController. This page explains how to build and use one.

Building a DraweeController

Then pass the image request to a PipelineDraweeControllerBuilder. You then specify additional options for the controller:

ControllerListener listener = new BaseControllerListener() {...}DraweeController controller = Fresco.newDraweeControllerBuilder()    .setUri(uri)    .setTapToRetryEnabled(true)    .setOldController(mSimpleDraweeView.getController())    .setControllerListener(listener)    .build();mSimpleDraweeView.setController(controller);

You should always call setOldController when building a new controller. This prevents an unneeded memory allocation.

More details:

  • Controller Listeners

Customizing the ImageRequest

For still more advanced usage, you might need to send an ImageRequest to the pipeline, instead of merely a URI. An example of this is using a postprocessor.

Uri uri;Postprocessor myPostprocessor = new Postprocessor() { ... }ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)    .setPostprocessor(myPostprocessor)    .build();DraweeController controller = Fresco.newDraweeControllerBuilder()    .setImageRequest(request)    .setOldController(mSimpleDraweeView.getController())    // other setters as you need    .build();

More details:

  • Postprocessors
  • Requesting Multiple Images
  • Resizing and Rotating

Progressive JPEGs

Note: the API in this page is still preliminary and subject to change.

Fresco supports the streaming of progressive JPEG images over the network.

Scans of the image will be shown in the view as you download them. Users will see the quality of the image start out low and gradually become clearer.

This is only supported for the network images. Local images are decoded at once.

Initialization

When you configure the image pipeline, you must pass in an instance ofProgressiveJpegConfig. We plan to remove this requirement.

This example will decode no more than every other scan of the image, using less CPU than decoding every scan.

ProgressiveJpegConfig pjpegConfig = new ProgressiveJpegConfig() {  @Override  public int getNextScanNumberToDecode(int scanNumber) {    return scanNumber + 2;  }      public QualityInfo getQualityInfo(int scanNumber) {    boolean isGoodEnough = (scanNumber >= 5);    return ImmutableQualityInfo.of(scanNumber, isGoodEnough, false);  }}ImagePipelineConfig config = ImagePipelineConfig.newBuilder()    .setProgressiveJpegConfig(pjpeg)    .build();

Instead of implementing this interface yourself, you can also instantiate theSimpleProgressiveJpegConfig class.

At Request Time

Currently, you must explicitly request progressive rendering while building the image request:

Uri uri;ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)    .setProgressiveRenderingEnabled(true)    .build();DraweeController controller = Fresco.newDraweeControllerBuilder()    .setImageRequest(request)    .setOldController(mSimpleDraweeView.getController())    .build();mSimpleDraweeView.setController(controller);

We hope to add support for using progressive images with setImageURI in a future release.

Animated Images

Fresco supports animated GIF and WebP images.

We support WebP animations, even in the extended WebP format, on versions of Android going back to 2.3, even those that don't have built-in native support.

Playing animations automatically

If you want your animated image to start playing automatically when it comes on-screen, and stop when it goes off, just say so in your image request:

Uri uri;DraweeController controller = Fresco.newDraweeControllerBuilder()    .setUri(uri)    .setAutoPlayAnimations(true)    . // other setters    .build();mSimpleDraweeView.setController(controller);

Playing animations manually

You may prefer to directly control the animation in your own code. In that case you'll need to listen for when the image has loaded, so it's even possible to do that.

ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {    @Override    public void onFinalImageSet(        String id,        @Nullable ImageInfo imageInfo,        @Nullable Animatable anim) {    if (anim != null) {      // app-specific logic to enable animation starting      anim.start();    }};Uri uri;DraweeController controller = Fresco.newDraweeControllerBuilder()    .setUri(uri)    .setControllerListener(controllerListener)    // other setters    .build();mSimpleDraweeView.setController(controller);

The controller exposes an instance of the Animatable interface. If non-null, you can drive your animation with it:

Animatable animatable = mSimpleDraweeView.getController().getAnimatable();if (animatable != null) {  animatable.start();  // later  animatable.stop();}

Limitations

Animations do not currently support postprocessors.

Requesting Multiple Images (Multi-URI)

The methods on this page require setting your own image request.

Going from low to high resolution

Suppose you want to show users a high-resolution, slow-to-download image. Rather than let them stare a placeholder for a while, you might want to quickly download a smaller thumbnail first.

You can set two URIs, one for the low-res image, one for the high one:

Uri lowResUri, highResUri;DraweeController controller = Fresco.newDraweeControllerBuilder()    .setLowResImageRequest(ImageRequest.fromUri(lowResUri))    .setImageRequest(ImageRequest.fromUri(highResUri))    .setOldController(mSimpleDraweeView.getController())    .build();mSimpleDraweeView.setController(controller);

Using thumbnail previews

This option is supported only for local URIs, and only for images in the JPEG format.

If your JPEG has a thumbnail stored in its EXIF metadata, the image pipeline can return that as an intermediate result. Your Drawee will first show the thumbnail preview, then the full image when it has finished loading and decoding.

Uri uri;ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)    .setLocalThumbnailPreviewsEnabled(true)    .build();DraweeController controller = Fresco.newDraweeControllerBuilder()    .setImageRequest(request)    .setOldController(mSimpleDraweeView.getController())    .build();mSimpleDraweeView.setController(controller);

Loading the first available image

Most of the time, an image has no more than one URI. Load it, and you're done.

But suppose you have multiple URIs for the same image. For instance, you might have uploaded an image taken from the camera. Original image would be too big to upload, so the image is downscaled first. In such case, it would be beneficial to first try to get the local-downscaled-uri, then if that fails, try to get the local-original-uri, and if even that fails, try to get the network-uploaded-uri. It would be a shame to download the image that we may have already locally.

The image pipeline normally searches for images in the memory cache first, then the disk cache, and only then goes out to the network or other source. Rather than doing this one by one for each image, we can have the pipeline check for all the images in the memory cache. Only if none were found would disk cache be searched in. Only if none were found there either would an external request be made.

Just create an array of image requests, and pass it to the builder.

Uri uri1, uri2;ImageRequest request = ImageRequest.fromUri(uri1);ImageRequest request2 = ImageRequest.fromUri(uri2);ImageRequest[] requests = { request1, request2 };DraweeController controller = Fresco.newDraweeControllerBuilder()    .setFirstAvailableImageRequests(requests)    .setOldController(mSimpleDraweeView.getController())    .build();mSimpleDraweeView.setController(controller);

Only one of the requests will be displayed. The first one found, whether at memory, disk, or network level, will be the one returned. The pipeline will assume the order of requests in the array is the preference order.

Listening to Download Events

Motivation

Your app may want to execute actions of its own when an image arrives - perhaps make another view visible, or show a caption. You may also want to do something in case of a network failure, like showing an error message to the user.

Loading images is, of course, asynchronous. So you need some way of listening to events posted by the DraweeController. The mechanism for doing this is a controller listener.

Note: this does not allow you to modify the image itself. To do that, use a Postprocessor.

Usage

To use it, you merely define an instance of the ControllerListener interface. We recommend subclassing BaseControllerListener:

ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {    @Override    public void onFinalImageSet(        String id,        @Nullable ImageInfo imageInfo,        @Nullable Animatable anim) {      if (imageInfo == null) {        return;      }      QualityInfo qualityInfo = imageInfo.getQualityInfo();      FLog.d("Final image received! " +           "Size %d x %d",          "Quality level %d, good enough: %s, full quality: %s",          imageInfo.getWidth(),          imageInfo.getHeight(),          qualityInfo.getQuality(),          qualityInfo.isOfGoodEnoughQuality(),          qualityInfo.isOfFullQuality());    }    @Override     public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {      FLog.d("Intermediate image received");    }    @Override    public void onFailure(String id, Throwable throwable) {      FLog.e(getClass(), throwable, "Error loading %s", id)    }};Uri uri;DraweeController controller = Fresco.newControllerBuilder()    .setControllerListener(controllerListener)    .setUri(uri);    // other setters    .build();mSimpleDraweeView.setController(controller);

onFinalImageSet or onFailure is called for all image loads.

If progressive decoding is enabled, and the image supports it, onIntermediateImageSet is called in response to each scan that gets decoded. Which scans get decoded is determined by yourconfiguration.

Resizing and Rotating

These features require you to construct an image request directly.

Resizing Images

Terminology: resizing vs scaling

  • Resizing is a pipeline operation executed in software. It returns a completely new bitmap, of a different size.
  • Scaling is a canvas operation and is usually hardware accelerated. The bitmap itself is always the same size. It just gets drawn upscaled or downscaled.

Should you resize or scale?

Resizing is rarely necessary. Scaling is almost always preferred, even with resizing.

There are several limitations with resizing:

  • Resize is restricted so that it never returns a bigger image. It can only make the image smaller.
  • At the moment, only JPEG images can be resized.
  • There is only a rough control over the resulting image dimensions. Image cannot be resized to the exact size, but will be reduced by one of the supported resize factors. That means that even resized images need to be scaled before displaying.
  • Only the following resize factors are supported: N/8 with 1 <= N <= 8.
  • Resize is performed in software, which is much slower than hardware-accelerated scaling.

Scaling, on the other hand, doesn't suffer any of these limitations. Scaling uses Android's own built-in facilities to match the image to the view size. On Android 4.0 and later, this is hardware-accelerated on devices with a GPU. Most of the time, it is the fastest and most effective way to display the image in the size you want. The only downside is if the image is much bigger than the view, then the memory gets wasted.

Why should you ever use resizing then? It's a trade-off. You should only ever use resize if you need to display an image that is much bigger than the view in order to save memory. One valid example is when you want to display an 8MP photo taken by the camera in a 1280x720 (roughly 1MP) view. An 8MP image would occupy 32MB of memory when decoded to 4 bytes-per-pixel ARGB bitmap. If resized to the view dimensions, it would occupy less than 4 MB.

When it comes to network images, before thinking about resizing, try requesting the image of the proper size first. Don't request an 8MP high-resolution photo from a server if it can return a smaller version. Your users pay for their data plans and you should be considerate of that. Besides, fetching a smaller image saves internal storage and CPU time in your app.

Only if the server doesn't provide an alternate URI with the smaller image, or if you are using local photos, should you resort to resizing. In all other cases, including upscaling the image, scaling should be used. To scale, simply specify the layout_width and layout_height of yourSimpleDraweeView, as you would for any Android view. Then specify a scale type.

Resizing

Resizing does not modify the original file. Resizing just resizes an encoded image in memory, prior to being decoded.

This can carry out a much greater range of resizing than is possible with Android's facilities. Images taken with the device's camera, in particular, are often much too large to scale and need to be resized before display on the device.

We currently only support resizing for images in the JPEG format, but this is the most widely used image format anyway and most Android devices with cameras store files in the JPEG format.

To resize pass a ResizeOptions object when constructing an ImageRequest:

Uri uri = "file:///mnt/sdcard/MyApp/myfile.jpg";int width = 50, height = 50;ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)    .setResizeOptions(new ResizeOptions(width, height))    .build();PipelineDraweeController controller = Fresco.newDraweeControllerBuilder()    .setOldController(mDraweeView.getController())    .setImageRequest(request)    .build();mSimpleDraweeView.setController(controller);

Auto-rotation

It's very annoying to users to see their images show up sideways! Many devices store the orientation of the image in metadata in the JPEG file. If you want images to be automatically rotated to match the device's orientation, you can say so in the image request:

ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)    .setAutoRotateEnabled(true)    .build();// as above

Modifying the Image

Motivation

Sometimes the image downloaded from the server, or fetched from local storage, is not exactly what you want to display on the screen. If you want to apply custom code to the image in-place, use a Postprocessor.

Example

The following example applies a red mesh to the image:

Uri uri;Postprocessor redMeshPostprocessor = new Postprocessor() {   @Override  public String getName() {    return "redMeshPostprocessor";  }  @Override  public void process(Bitmap bitmap) {    for (int x = 0; x < bitmap.getWidth(); x+=2) {      for (int y = 0; y < bitmap.getHeight(); y+=2) {        bitmap.setPixel(x, y, Color.RED);      }    }  }}ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)    .setPostprocessor(redMeshPostprocessor)    .build();PipelineDraweeController controller = Fresco.newDraweeControllerBuilder()    .setImageRequest(request)    .setOldController(mSimpleDraweeView.getOldController())    // other setters as you need    .build();mSimpleDraweeView.setController(controller);

Things to Know

The image is copied before it enters your postprocessor. The copy of the image in cache isnot affected by any changes you make in your postprocessor. On Android 4.x and lower, the copy is stored outside the Java heap, just as the original image was.

If you show the same image repeatedly, you must specify the postprocessor each time it is requested. You are free to use different postprocessors on different requests for the same image.

Postprocessors are not currently supported for animated images.

Repeated Postprocessors

What if you want to post-process the same image more than once? No problem at all. Just subclass BaseRepeatedPostprocessor. This class has a method update which can be invoked at any time to run the postprocessor again.

The example below allows you to change the color of the mesh at any time.

public class MeshPostprocessor extends BaseRepeatedPostprocessor {   private int mColor = Color.TRANSPARENT;  public void setColor(int color) {    mColor = color;    update();  }  @Override  public String getName() {    return "meshPostprocessor";  }  @Override  public void process(Bitmap bitmap) {    for (int x = 0; x < bitmap.getWidth(); x+=2) {      for (int y = 0; y < bitmap.getHeight(); y+=2) {        bitmap.setPixel(x, y, mColor);      }    }  }}MeshPostprocessor meshPostprocessor = new MeshPostprocessor();/// setPostprocessor as in above examplemeshPostprocessor.setColor(Color.RED);meshPostprocessor.setColor(Color.BLUE);

You should have still have one Postprocessor instance per image request, as internally the class is stateful.

Image Requests

If you need an ImageRequest that consists only of a URI, you can use the helper methodImageRequest.fromURI. Loading multiple-images is a common case of this.

If you need to tell the image pipeline anything more than a simple URI, you need to useImageRequestBuilder:

Uri uri;ImageDecodeOptions decodeOptions = ImageDecodeOptions.newBuilder()    .setBackgroundColor(Color.GREEN)    .build();ImageRequest request = ImageRequestBuilder    .newBuilderWithSource(uri)    .setAutoRotateEnabled(true)    .setLocalThumbnailPreviewsEnabled(true)    .setLowestPermittedRequestLevel(RequestLevel.FULL_FETCH)    .setProgressiveRenderingEnabled(false)    .setResizeOptions(new ResizeOptions(width, height))    .build();

Fields in ImageRequest

  • uri - the only mandatory field. See Supported URIs
  • autoRotateEnabled - whether to enable auto-rotation.
  • progressiveEnabled - whether to enable progressive loading.
  • postprocessor - component to postprocess the decoded image.
  • resizeOptions - desired width and height. Use with caution. See Resizing.

Lowest Permitted Request Level

The image pipeline follows a definite sequence in where it looks for the image.

  1. Check the bitmap cache. This is nearly instant. If found, return.
  2. Check the encoded memory cache. If found, decode the image and return.
  3. Check the "disk" (local storage) cache. If found, load from disk, decode, and return.
  4. Go to the original file on network or local file. Download, resize and/or rotate if requested, decode, and return. For network images in particular, this will be the slowest by a long shot.

The setLowestPermittedRequestLevel field lets you control how far down this list the pipeline will go. Possible values are:

  • BITMAP_MEMORY_CACHE
  • ENCODED_MEMORY_CACHE
  • DISK_CACHE
  • FULL_FETCH

This is useful in situations where you need an instant, or at least relatively fast, image or none at all.

Writing Custom Views

DraweeHolders

There will always be times when DraweeViews won't fit your needs. You may need to show additional content inside the same view as your image. You might to show multiple images inside a single view.

We provide two alternate classes you can use to host your Drawee:

  • DraweeHolder for a single image
  • MultiDraweeHolder for multiple images

Responsibilities of custom views

Android lays out View objects, and only they get told of system events. DraweeViews handle these events and use them to manage memory effectively. When using the holders, you must implement some of this functionality yourself.

Handling attach/detach events

Your app may leak memory if this steps are not followed.

There is no point in images staying in memory when Android is no longer displaying the view - it may have scrolled off-screen, or otherwise not be drawing. Drawees listen for detaches and release memory when they occur. They will automatically restore the image when it comes back on-screen.

All this is automatic in a DraweeView, but won't happen in a custom view unless you handle four system events. These must be passed to the DraweeHolder. Here's how:

DraweeHolder mDraweeHolder;@Overridepublic void onDetachedFromWindow() {  super.onDetachedFromWindow();  mDraweeHolder.onDetach();}@Overridepublic void onStartTemporaryDetach() {  super.onStartTemporaryDetach();  mDraweeHolder.onDetach();}@Overridepublic void onAttachedToWindow() {  super.onAttachedToWindow();  mDraweeHolder.onAttach();}@Overridepublic void onFinishTemporaryDetach() {  super.onFinishTemporaryDetach();  mDraweeHolder.onAttach();}

Handling touch events

If you have enabled tap to retry in your Drawee, it will not work unless you tell it that the user has touched the screen. Like this:

@Overridepublic boolean onTouchEvent(MotionEvent event) {  return mDraweeHolder.onTouchEvent(event) || super.onTouchEvent(event);}

Your custom onDraw

You must call

Drawable drawable = mDraweeHolder.getHierarchy().getTopLevelDrawable();drawable.setBounds(...);

or the Drawee won't appear at all.

  • Do not downcast this Drawable.
  • Do not translate it.

Other responsibilities

  • Override verifyDrawable:
@Overrideprotected boolean verifyDrawable(Drawable who) {  if (who == mDraweeHolder.getHierarchy().getTopLevelDrawable()) {    return true;  }  // other logic for other Drawables in your view, if any}
  • Make sure invalidateDrawable invalidates the region occupied by your Drawee.

Constructing a DraweeHolder

This should be done carefully.

Arranging your Constructors

We recommend the following pattern for constructors:

  • Override all three of the three View constructors.
  • Each constructor calls its superclass counterpart and then a private init method.
  • All of your initialization happens in init.

That is, do not use the this operator to call one constructor from another.

This approach guarantees that the correct initialization is called no matter what constructor is used. It is in the init method that your holder is created.

Creating the Holder

If possible, always create Drawees when your view gets created. Creating a hierarchy is not cheap so it's best to do it only once.

class CustomView extends View {  DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;  // constructors following above pattern  private void init() {    GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());      .set...      .set...      .build();    mDraweeHolder = DraweeHolder.create(hierarchy, context);  }}

Setting an image

Use a controller builder, but call setController on the holder instead of a View:

DraweeController controller = Fresco.newControllerBuilder()    .setUri(uri)    .setOldController(mDraweeHolder.getController())    .build();mDraweeHolder.setController(controller);

MultiDraweeHolder

Instead of using a DraweeHolder, use a MultiDraweeHolder. There are addremove, and clearmethods for dealing with Drawees:

MultiDraweeHolder<GenericDraweeHierarchy> mMultiDraweeHolder;private void init() {  GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());    .set...    .build();  mMultiDraweeHolder = new MultiDraweeHolder<GenericDraweeHierarchy>();  mMultiDraweeHolder.add(new DraweeHolder<GenericDraweeHierarchy>(hierarchy, context));  // repeat for more hierarchies}

You must override system events, set bounds, and do all the same responsibilities as for a single DraweeHolder.

Gotchas

Don't downcast

It is tempting to downcast objects returns by Fresco classes into actual objects that appear to give you greater control. At best, this will result in fragile code that gets broken next release; at worst, it will lead to very subtle bugs.

Don't use getTopLevelDrawable

DraweeHierarchy.getTopLevelDrawable() should only be used by DraweeViews. Client code should almost never interact with it.

The sole exception is custom views. Even there, the top-level drawable should never be downcast. We may change the actual type of the drawable in future releases.

Don't re-use DraweeHierarchies

Never call DraweeView.setHierarchy with the same argument on two different views. Hierarchies are made up of Drawables, and Drawables on Android cannot be shared among multiple views.

Don't use Drawables in more than one DraweeHierarchy

This is for the same reason as the above. Drawables cannot be shared in multiple views.

You are completely free, of course, to use the same resourceID in multiple hierarchies and views. Android will create a separate instance of each Drawable for each view.

Don't set images directly on a DraweeView

Currently DraweeView is a subclass of Android's ImageView. This has various methods to set an image (such as setImageBitmap, setImageDrawable)

If you set an image directly, you will completely lose your DraweeHierarchy, and will not get any results from the image pipeline.

Don't use ImageView attributes or methods with DraweeView

Any XML attribute or method of ImageView not found in View will not work on a DraweeView. Typical cases are scaleTypesrc, etc. Don't use those. DraweeView has its own counterparts as explained in the other sections of this documentation. Any ImageView attrribute or method will be removed in the upcoming release, so please don't use those.



0 0
原创粉丝点击