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