Re: Gregs comment:
I gave it another shot (with almost a year of extra XP) and came up with this:
<ImageView android:scaleType="center" ... />
similar to my other solution and the following animation wrapper:
...
.fitCenter()
.animate(new PaddingAnimationFactory<>(new DrawableCrossFadeFactory<GlideDrawable>(2000)))
.into(imageView)
;
class PaddingAnimationFactory<T extends Drawable> implements GlideAnimationFactory<T> {
private final DrawableCrossFadeFactory<T> realFactory;
@Override public GlideAnimation<T> build(boolean isFromMemoryCache, boolean isFirstResource) {
return new PaddingAnimation<>(realFactory.build(isFromMemoryCache, isFirstResource));
}
}
class PaddingAnimation<T extends Drawable> implements GlideAnimation<T> {
private final GlideAnimation<? super T> realAnimation;
@Override public boolean animate(T current, final ViewAdapter adapter) {
int width = current.getIntrinsicWidth();
int height = current.getIntrinsicHeight();
return realAnimation.animate(current, new PaddingViewAdapter(adapter, width, height));
}
}
class PaddingViewAdapter implements ViewAdapter {
@Override public Drawable getCurrentDrawable() {
Drawable drawable = realAdapter.getCurrentDrawable();
if (drawable != null) {
int padX = Math.max(0, targetWidth - drawable.getIntrinsicWidth()) / 2;
int padY = Math.max(0, targetHeight - drawable.getIntrinsicHeight()) / 2;
if (padX != 0 || padY != 0) {
drawable = new InsetDrawable(drawable, padX, padY, padX, padY);
}
}
return drawable;
}
@Override public void setDrawable(Drawable drawable) {
if (VERSION.SDK_INT >= VERSION_CODES.M && drawable instanceof TransitionDrawable) {
// For some reason padding is taken into account differently on M than before in LayerDrawable
// PaddingMode was introduced in 21 and gravity in 23, I think NO_GRAVITY default may play
// a role in this, but didn't have time to dig deeper than this.
((TransitionDrawable)drawable).setPaddingMode(TransitionDrawable.PADDING_MODE_STACK);
}
realAdapter.setDrawable(drawable);
}
}
Trivial parts of the implementations are omitted, each class's constructor initializes the fields from arguments. Full code available on GitHub in TWiStErRob/glide-support.

If you're stuck on an older version of Glide (before 3.8.0), the same effect can be achieved by:
.fitCenter()
.placeholder(R.drawable.glide_placeholder)
.crossFade(2000)
.into(new GlideDrawableImageViewTarget(imageView) {
@Override public void onResourceReady(GlideDrawable resource,
GlideAnimation<? super GlideDrawable> animation) {
super.onResourceReady(resource, new PaddingAnimation<>(animation));
}
})
Note how the two solutions require the same amount of classes, but the post-3.8.0 solution has better separation of concerns and it can be cached in a variable to prevent allocations.