編輯:關於Android編程
Android Fresco圖片處理庫用法API英文原文文檔2-2(Facebook開源Android圖片庫)
這是英文文檔的第二部分(2):DRAWEE GUIDE
由於第二部分內容多一些,所以分為2個文章發。方便大家查看。
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.
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:
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:
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.
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.
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.
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.
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);
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() {
@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();
}
Animations do not currently support postprocessors.
The methods on this page require setting your own image request.
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);
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);
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.
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.
To use it, you merely define an instance of the ControllerListener
interface. We recommend subclassing BaseControllerListener:
ControllerListener controllerListener = new BaseControllerListener() {
@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.
These features require you to construct an image request directly.
Resizing is rarely necessary. Scaling is almost always preferred, even with resizing.
There are several limitations with resizing:
N/8
with 1 <= N <= 8
.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 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);
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
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.
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);
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.
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.
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();
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.
The image pipeline follows a definite sequence in where it looks for the image.
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.
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
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.
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();
}
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);
}
You must call
Drawable drawable = mDraweeHolder.getHierarchy().getTopLevelDrawable();
drawable.setBounds(...);
or the Drawee won't appear at all.
verifyDrawable:
@Override
protected boolean verifyDrawable(Drawable who) {
if (who == mDraweeHolder.getHierarchy().getTopLevelDrawable()) {
return true;
}
// other logic for other Drawables in your view, if any
}
invalidateDrawable
invalidates the region occupied by your Drawee.
This should be done carefully.
We recommend the following pattern for constructors:
init
method.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.
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 mDraweeHolder;
// constructors following above pattern
private void init() {
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());
.set...
.set...
.build();
mDraweeHolder = DraweeHolder.create(hierarchy, context);
}
}
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);
Instead of using a DraweeHolder
, use a MultiDraweeHolder
. There are add
, remove
, and clear
methods for dealing with Drawees:
MultiDraweeHolder mMultiDraweeHolder;
private void init() {
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());
.set...
.build();
mMultiDraweeHolder = new MultiDraweeHolder();
mMultiDraweeHolder.add(new DraweeHolder(hierarchy, context));
// repeat for more hierarchies
}
You must override system events, set bounds, and do all the same responsibilities as for a single DraweeHolder.
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.
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.
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.
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.
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.
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.
圓環交替、等待效果效果就這樣,分析了一下,大概有這幾個屬性,兩個顏色,一個速度,一個圓環的寬度。自定View的幾個步驟:1、自定義View的屬性2、在View的構造方法中
曾經有一個朋友問過我一個問題, 一張512*512 150KB PNG格式圖片和一張512*512 100KB 壓縮比是8的JPG格式的圖片,加載到內存中,也
這篇博文中主要從以下幾點進行敘述: 1、Android Studio安裝與使用 2、Android Studio特
本文實例講述了android開發之listView組件用法。分享給大家供大家參考,具體如下:關於Android ListView組件中android:drawSelect