Skip to content

Custom targets

Sam edited this page May 20, 2017 · 10 revisions

Glide V4

For information on Glide v4, see the documentation. This wiki covers Glide v3 only.


In addition to loading images, video stills, and animated GIFs into Views, you can also load media into custom Target implementations.

If you simply want to load a Bitmap so that you can interact with it in some special way other than displaying it directly to the user, maybe to show in a notification, or upload as a profile photo, Glide has you covered.

SimpleTarget provides reasonable default implementations for the much larger Target interface and let's you focus on handling the result of your load.

To use SimpleTarget, you need to provide the pixel width and height you'd like to load your resource at in to SimpleTarget's constructor, and you need to implement [onResourceReady(T resource, GlideAnimation animation)](http://bumptech.github.io/glide/javadocs/latest/com/bumptech/glide/request/target/Target.html#onResourceReady(R, com.bumptech.glide.request.animation.GlideAnimation)).

A typical SimpleTarget use case looks something like this:

int myWidth = 512;
int myHeight = 384;

Glide.with(yourApplicationContext))
    .load(youUrl)
    .asBitmap()
    .into(new SimpleTarget<Bitmap>(myWidth, myHeight) {
        @Override
        public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
            // Do something with bitmap here.
        }
    };

Caveats

Normally when you load resources, you're loading them into Views. When you fragment or activity is paused or destroyed, Glide will pause or cancel your load for you to make sure you're not wasting time or resources fetching media you're not going to display.

For most SimpleTarget implementations, this isn't desirable behavior, so in your Glide.with(context) call, pass in your application context instead of your fragment or activity.

In addition, consider that long running operations may lead to memory leaks. If you're going to perform a long running operation, consider using a static inner class instead of an anonymous inner class.

You can override ViewTarget and/or its subclasses when you want to load an image into a View but you want to observe or override some part of Glide's default behavior.

ViewTarget is a base class you can use when you want Glide to handle determining the size of your View as normal, but when you want to handle starting an animation or setting the resource on the View yourself. Subclassing ViewTarget is particularly appropriate if you're loading an image into a custom View class or something other than an ImageView where Glide's built in ImageViewTarget and subclasses won't work.

To create your own ViewTarget either statically define a new ViewTarget subclass, or pass in an anonymous inner class to your load call:

Glide.with(yourFragment)
    .load(yourUrl)
    .into(new ViewTarget<YourViewClass, GlideDrawable>(yourViewObject) {
        @Override
        public void onResourceReady(GlideDrawable resource, GlideAnimation anim) {
            YourViewClass myView = this.view;
            // Set your resource on myView and/or start your animation here.
        }
    });

Note that if you want to load specifically a Bitmap or a GifDrawable, add .asBitmap() or .asGif() immediately after your .load(yourUrl) call and replace GlideDrawable in your ViewTarget's type parameter with the corresponding type you're loading.

For even more control, you can also implement the LifecycleListener callbacks on your Target, including onStart(), onStop(), and/or onDestroy() which will be called in sync with the lifecycle of the Fragment containing your View.

Overriding default behavior

If all you want to do is observe Glide's default behavior without changing it, you can subclass one of Glide's two default concrete ImageViewTargets:

As long as you call super() for each method, the default behavior will remain the same and you can add in whatever functionality you wish.

For example to start generating a Palette you can do something like:

Glide.with(yourFragment)
    .load(yourUrl)
    .asBitmap()
    .into(new BitmapImageViewTarget(yourImageView)) {
        @Override
        public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
            super.onResourceReady(bitmap, anim);
            Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {  
                @Override
                public void onGenerated(Palette palette) {
                    // Here's your generated palette
                }
            });
        }
    });

Although this is a great simple example, in general I wouldn't recommend generate Palettes this way. Instead checkout Glide's ResourceTranscoder interface and [.transcode()](http://bumptech.github.io/glide/javadocs/latest/com/bumptech/glide/BitmapTypeRequest.html#transcode(com.bumptech.glide.load.resource.transcode.ResourceTranscoder, java.lang.Class)) method and consider returning a custom resource containing both your Bitmap and a Palette generated on a background thread. One possible problem with the above is the second asynchron call: the ViewHolder may be already recycled by the time Palette finishes with the swatch generation so it may end up updating an item at the wrong position.

Palette example

Palette integration should be coming in the next release, but until then here's a how to integrate it quickly with Glide 3's resource loading flow:

public class PaletteBitmap {
    public final Palette palette;
    public final Bitmap bitmap;

    public PaletteBitmap(@NonNull Bitmap bitmap, @NonNull Palette palette) {
        this.bitmap = bitmap;
        this.palette = palette;
    }
}
public class PaletteBitmapResource implements Resource<PaletteBitmap> {
    private final PaletteBitmap paletteBitmap;
    private final BitmapPool bitmapPool;

    public PaletteBitmapResource(@NonNull PaletteBitmap paletteBitmap, @NonNull BitmapPool bitmapPool) {
        this.paletteBitmap = paletteBitmap;
        this.bitmapPool = bitmapPool;
    }

    @Override public PaletteBitmap get() {
        return paletteBitmap;
    }

    @Override public int getSize() {
        return Util.getBitmapByteSize(paletteBitmap.bitmap);
    }

    @Override public void recycle() {
        if (!bitmapPool.put(paletteBitmap.bitmap)) {
            paletteBitmap.bitmap.recycle();
        }
    }
}
public class PaletteBitmapTranscoder implements ResourceTranscoder<Bitmap, PaletteBitmap> {
    private final BitmapPool bitmapPool;

    public PaletteBitmapTranscoder(@NonNull Context context) {
        this.bitmapPool = Glide.get(context).getBitmapPool();
    }

    @Override public Resource<PaletteBitmap> transcode(Resource<Bitmap> toTranscode) {
        Bitmap bitmap = toTranscode.get();
        Palette palette = new Palette.Builder(bitmap).generate();
        PaletteBitmap result = new PaletteBitmap(bitmap, palette);
        return new PaletteBitmapResource(result, bitmapPool);
    }

    @Override public String getId() {
        return PaletteBitmapTranscoder.class.getName();
    }
}

The above are the common classes to glue Glide and Support-palette together. The following is an example usage of those classes (full working code in glide-support):

public static class PaletteAdapter extends Adapter<PaletteAdapter.ImageTextViewHolder> {
	private final BitmapRequestBuilder<String, PaletteBitmap> glideRequest;
	private final List<String> data;
	private final @ColorInt int defaultColor;
	public PaletteAdapter(Context context, RequestManager glide, List<String> data) {
		this.data = data;
		this.defaultColor = ContextCompat.getColor(context, R.color.default_background);
		this.glideRequest = glide
				.fromString()
				.asBitmap()
				.transcode(new PaletteBitmapTranscoder(context), PaletteBitmap.class)
				.fitCenter()
				.diskCacheStrategy(DiskCacheStrategy.ALL)
		;
	}
	@Override public int getItemCount() {
		return data.size();
	}
	@Override public ImageTextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
		LayoutInflater inflater = LayoutInflater.from(parent.getContext());
		View view = inflater.inflate(R.layout.list_item, parent, false);
		return new ImageTextViewHolder(view);
	}
	@Override public void onBindViewHolder(final ImageTextViewHolder holder, int position) {
		// reset color so it looks like the view was just inflated even if it was recycled
		// this is to prevent inheriting another position's colors
		holder.itemView.setBackgroundColor(defaultColor);
		String url = data.get(position);
		if (url != null) { // simulate an optional url from the data item
			holder.imageView.setVisibility(View.VISIBLE);
			glideRequest
					.load(url)
					.into(new ImageViewTarget<PaletteBitmap>(holder.imageView) {
						@Override protected void setResource(PaletteBitmap resource) {
							super.view.setImageBitmap(resource.bitmap);
							int color = resource.palette.getVibrantColor(defaultColor);
							holder.itemView.setBackgroundColor(color);
						}
					});
		} else {
			// clear when no image is shown, don't use holder.imageView.setImageDrawable(null) to do the same
			Glide.clear(holder.imageView);
			holder.imageView.setVisibility(View.GONE);
		}
	}
	@Override public void onViewRecycled(ImageTextViewHolder holder) {
		super.onViewRecycled(holder);
		// optional, but recommended way to clear up the resources used by Glide
		Glide.clear(holder.imageView);
	}
	static class ImageTextViewHolder extends RecyclerView.ViewHolder {
		final ImageView imageView;
		public ImageTextViewHolder(View itemView) {
			super(itemView);
			imageView = (ImageView)itemView.findViewById(R.id.image);
		}
	}
}

And here's how you construct your adapter:

recyclerView.setAdapter(new PaletteAdapter(getContext(), Glide.with(this), Arrays.asList(
		"http://url1",
		"http://url2"
)));

Notice that Glide.with(this) is passed to the adapter. If you implement it this way the same adapter could work anywhere within your app(s), be it Activity or Fragment and there's no dependency of constructor overloads needed to achieve that.