How we made a Before and after view in Greetings from Zagreb Android application

Historical Zagreb comes to life in a mobile application!

Have you heard about an award-winning application for National & University Library in Zagreb?

Greetings from Zagreb is a mobile application made for the biggest Croatian library. The application provides historical and presents virtual walk on Zagreb city center, it combines maps, postcards, and photos to take you on a tour of Zagreb as it once was!

Looking into the past is the most exciting feature because the user can compare two images — one from history and other from the present. The application has 10 the most popular locations in Zagreb, so the user can see how some square or street looks now and how it looked then. That’s why we call this new idea — Before and after view!

This feature was so original that BibLibre International Library Marketing Award gave our client, National and University Library in Zagreb the third place in international librarian competition!

If 150 postcards in the application are not enough for you, the application will take you directly to the entire (and ever-growing) postcard collection of the library so you can continue exploring the city’s rich past.

This application was really exciting and pleasing to work on because we didn’t work on a feature like this, and also could not find some guidelines on the internet, so we needed to think how we can solve it and how to present the best solution.

Here are our developer’s thoughts through the process of developing this feature.

About the Before and after view

This feature has a simple usage. Due to thumb horizontal movement, one side of an image should overlap another side. A result of this action should be one image which has two images inside, one historical and one from the present. This approach is ideal for comparing two images. Feature example is shown below.

The code presented in this blog post is available on Factory Github profile.

The idea behind Before and after feature

The idea was to create two ImageViews inside some layout. The layout also should have a placeholder, mask and some thumb for dragging.
Placeholder should be shown before images are fully loaded after that placeholder should be hidden. That was archived by putting placeholder ImageView on a higher hierarchy inside layout then the two main ImageViews used for images loading.

Creating a movable thumb was tricky. The first approach was to put some view and calculate the current horizontal position and based on that figure how much of ImageView should be cropped. But that was too much work. So I decide to use something more native, and that is SeekBar.

SeekBar was an excellent solution because it provides everything I needed. It contains horizontal movable thumb and onProgressChanged listener which gives me a value of thumb current position/progress, so I do not need to calculate it.
Maybe you ask yourself why I need a mask? Mask is used just for editing, and that is it.

Here comes XML code:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:overScrollMode="never">

<RelativeLayout
android:id="@+id/rlProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_marginEnd="20dp"
android:layout_marginStart="20dp"
android:layout_marginTop="2dp"
android:overScrollMode="never">

<ImageView
android:id="@+id/ptBackgroundImageBefore"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />

<ImageView
android:id="@+id/ptBackgroundImageAfter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
</RelativeLayout>

<ImageView
android:id="@+id/ivPlaceHolder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />

<View
android:id="@+id/vMask"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="18dp"
android:layout_marginStart="18dp"
android:overScrollMode="never"
android:visibility="gone" />
</RelativeLayout>

<SeekBar
android:id="@+id/ptSeekBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:max="10000"
android:overScrollMode="never"
android:progressDrawable="@drawable/drawable_seek_bar"
android:visibility="gone" />
</RelativeLayout>

Troubles in Heaven

Solving XML was the easy part, now we need to choose the way for solving image overlapping.

My first idea was to change a width of both ImageViews. After stopping SeekBar, I thought to get progress value and calculate how much far away is left and right margin from SeekBar. After that setting ImageViews width and height should be the easy part.

But this approach is bad because I need to resize both ImageViews dynamically and I didn’t know how to load an image inside ImageView. If ImageView is resized then how to load properly image inside it without any scaling before and after thumb movement. The image inside was scaled not clipped.

The second shoot was a little bit better. I decided to save memory and processor power by making right ImageView static and left ImageView cropable. This approach improved ram usage and processor power but didn’t solve scaling/clipping problem.

Finally, I discovered the right way how to deal with image clipping.

The solution was to create ClipDrawable. ClipDrawable clips another Drawable based on this Drawable’s current level value, you can control how much the child Drawable gets clipped in width and height based on the level, as well as gravity to control where it is placed in its overall container.

After implementing a right approach, another problem was image loading. Images for loading are fetched from a server, so I used Glide. With Glide, I can quickly get bitmap and crate ClipDrawable, but this ClipDrawable doesn’t fit image loaded on the right side even if images are same. The left image was a little bit bigger than right.

The solution was to create ClipDrawable for both images and then load it to the ImageViews. The right static image was clipped by zero level and the left dynamic image was clipped by level obtain from SeekBar The same thing is done for images loaded from drawable folder.
And after that, I finally did my job well.

Here is a library of this feature

After a lot of work, I made an Android library and a documentation for this feature. We made the library even more flexible by adding a bunch of custom attributes.

Feel free to use it and find a library and documentation on our Factory GitHub profile.

Want to know more about this Android application?

Check our case study of Greetings from Zagreb mobile application, download it on Google Play Store.

What do you think about this original feature? Do you have some ideas how to solve it? Let us know your thoughts in Factory contact!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Factory.hr

Factory.hr

We design and develop native mobile & web applications for startups and successful companies.