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:
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:
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
with1
.
<= 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 example
meshPostprocessor.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
URIsautoRotateEnabled
- 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.
- Check the bitmap cache. This is nearly instant. If found, return.
- Check the encoded memory cache. If found, decode the image and return.
- Check the "disk" (local storage) cache. If found, load from disk, decode, and return.
- 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 imageMultiDraweeHolder
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;
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mDraweeHolder.onDetach();
}
@Override
public void onStartTemporaryDetach() {
super.onStartTemporaryDetach();
mDraweeHolder.onDetach();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mDraweeHolder.onAttach();
}
@Override
public 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:
@Override
public 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:
@Override
protected 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 add
, remove
,
and clear
methods 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 scaleType
, src
, 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.