In part 2 of this series we looked at guidelines for implementing our Shared Element Transitions and also how we utilise them when going from Fragment
to Fragment
.
In part 3 we're going to look at how we can tie together our previous examples using Picasso and Glide image loading libraries. These two are probably the most widely used but if you use something else then you can likely still follow the concepts and get the same results.
Picasso
We're going to be loading our images in both PicassoActivityA
and PicassoActivityB
using Picasso, not using the xml layout.
final ImageView imageView = (ImageView) findViewById(R.id.simple_activity_a_imageView);
Picasso.with(this)
.load(TIGER_PIC_URL)
.fit()
.centerCrop()
.into(imageView);
Button button = (Button) findViewById(R.id.simple_activity_a_btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(PicassoSimpleActivityA.this, PicassoSimpleActivityB.class);
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(PicassoSimpleActivityA.this,
imageView,
ViewCompat.getTransitionName(imageView));
startActivity(intent, options.toBundle());
}
});
Here in PicassoActivityA
we're just loading our image into the ImageView
, nothing special if you've loaded images with Picasso before.
Also the same as in part 1 is the creation of the ActivityOptionsCompat
so we can pass this as a bundle along with our intent so PicassoActivityB
knows we have a Shared Element Transition to do. If this is new to you then head back to part 1 where I explain this in more detail.
Most of the setup code is in PicassoActivityB
, lets take a look.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.picasso_activity_b);
ImageView imageView = (ImageView) findViewById(R.id.picasso_activity_b_image);
supportPostponeEnterTransition();
Picasso.with(this)
.load(PicassoActivityA.TIGER_PIC_URL)
.fit()
.noFade()
.centerCrop()
.into(imageView, new Callback() {
@Override
public void onSuccess() {
supportStartPostponedEnterTransition();
}
@Override
public void onError() {
supportStartPostponedEnterTransition();
}
});
}
The first thing that's new here is supportPostponeEnterTransition
on line 7. This tells the Activity to delay loading until we say it's ok to and the window is left transparent. We need this because we need to ensure that Picasso has loaded the image before we do the transition. Otherwise we'll just get some fade animation for the image. We use supportPostponeEnterTransition
rather than postponeEnterTransition
because in our sample app we're supporting 19. This just means we don't have to do the if
statement to determine the SDK version.
We load our image in with Picasso, just like PicassoActivityA
with a couple of extras. In line 12 we set noFade()
. This is a Shared Element Transition so as such we want the image to be on screen at all times. Picasso performs a default fade so we need to turn it off.
Finally in our into we add an extra Callback
parameter to listen for when the image has been loaded. On line 17 and line 22, within the Callback
onSuccess
and onError
methods we call supportStartPostponedEnterTransition
to say that now we're ready to start the transition and load the UI for PicassoActivityB
. If you don't call supportStartPostponedEnterTransition
the Activity
will never start.
And that's it. That's all that's all that's needed extra to load. I won't go through the code doing this for a Fragment
to Fragment
example as it's so similar to the above and I'm sure you could work it out. I will put the code in the github project though if you would like to see. There's only one issue I have in with Fragments
and that's getting the returning transition back to the previous Fragment
to operate with the image in view. If you know how what I'm missing then please let me know!
One last thing, if you want to force Picasso to use the image from the cache you'll need to set a size for the image, otherwise it won't work properly. Is it necessary? No, but the image in PicassoActivityB
will load quicker.
Glide
Glide follows a very similar pattern to the Picasso code with just a few specific Glide method changes. So if you want to know in more detail about what's going on refer to the above text.
GlideActivityA
is nothing special and pretty much identical to PicassoActivityA
and our SimpleActivityA
from part 1.
Glide.with(this)
.load(FOX_PIC_URL)
.centerCrop()
.into(imageView);
Button button = (Button) findViewById(R.id.glide_activity_a_btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(GlideActivityA.this, GlideActivityB.class);
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(GlideActivityA.this,
imageView,
ViewCompat.getTransitionName(imageView));
startActivity(intent, options.toBundle());
}
});
GlideActivityB
has a the most changes, mostly due to a different way to listen for an image loading.
supportPostponeEnterTransition();
Glide.with(this)
.load(GlideActivityA.FOX_PIC_URL)
.centerCrop()
.dontAnimate()
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
supportStartPostponedEnterTransition();
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
supportStartPostponedEnterTransition();
return false;
}
})
.into(imageView);
Line 1 we call supportPostponeEnterTransition()
to tell the Activity
to wait to load until we tell it to.
On line 4 we call dontAnimate()
which is necessary as we don't want the image to fade in when it's loaded otherwise we'll lose the transition effect.
On line 5 we set a listener for when the image has been loaded or failed to load. This is so we can call supportStartPostponedEnterTransition()
(line 10 & line 16) to start the transition and load the Activity
. Note we return false as we want to handle all the image loading rather than doing it manually ourselves.
Using Glide and Fragment
should be easy to work out, but like Picasso I'll include them in the source code. Glide doesn't seem to have the same issues as Picasso when it comes to the return Shared Element Transition not operating correctly with Fragments
. Hopefully I can discover out why and update this post in future.
That source code can be found here.
This post has been a short one as I was at hackathon this past weekend. Next week we'll jump into Shared Element Transitions with RecyclerView
.
Any questions hit me up in the comments.
Update (5/03/17): I've made some minor adjustments to this post since first releasing. There's nothing better than having 1000's of eyes on your work for reviewing!
Thanks to Christophe Beyls for pointing out the missing supportStartPostponedEnterTransition() in the error methods for Picasso and Glide