The beauty and the ugly of animating view on keyboard opens

Piotr Prus
Schibsted Tech Polska
6 min readOct 9, 2019

Handling view changes when keyboard opens in Android is a painful game - everyone that tried that knows what I am talking about. Animating views on keyboard opens in fragment is even worst. I came up with a solution to this problem, but it is still far from perfect. In this article I will try to show you what ugly tricks needed to be done to create a slick animation when softInputKeyboard opens.

Defining problem

In GoodOnes (Swedish/Norwegian dating app by Mötesplatsen), we always aim for having a cool design, which also includes animations. One of the requirements from our designer was particularly complicated. See that below GIF from the iOS implementation:

Above gif shows GoodOnes like message functionality. This is recorded on dev environment.

We have several actions that are animating here in one sequence:

  • photo squeeze by about 5% to the center;
  • new text and icon appears on the top;
  • green gradient applies;
  • white frame around green view appears;
  • keyboard is opening;
  • EditText together with 2 buttons are moving up;
  • The background photo(next person) is visible under the green view;
  • Both pictures do not move when keyboard open;

We were trying to accomplish that using shared element transitions, some complex animation xmls and a lot of callbacks for keyboard opening. The effect was not satisfying. But then we met MotionLayout and everything changed.

Building a motion scene

MotionLayout is a class available in the ConstraintLayout 2.0 library that lets create great animations right from the XML. If you want to start experimenting with it, here is a good place to look at: Introduction to MotionLayout

Lets check at layout hierarchy :

Two things that should draw our attention :

  • The root layout is type of androidx.constraintlayout.motion.widget.MotionLayout
  • There is a layoutDescription attribute used that point on the xml scene file: app:layoutDescription=”@xml/send_like_msg_scene”

The scene file is the place where all the magic starts. Let’s dive into the first part of animation displayed to user: squeeze photo by 5%

If you had contact with Motion Layout before, the code above is pretty self-explanatory. For those who have doubts, a few words about the above functions.

  • ConstraintSet — the most important xml tag in MotionScene. Set contains all constraints that are taking part in animation
  • Constraint — the basic tag that override attributes from layout file( if not used along with <Layout>, <Transform> etc.), more info in docs
  • Transition — here, we are informing animator what are the start and end position for layout, we can also set the duration, motion interpolator, autoTransition etc

With those set up, the current animation looks as shown below:

Now, let’s apply this green gradient. I have prepared it together with a white frame, so we will combine two actions into a one element. The construction of this xml is not part of this article, but if you would like to check it, here is the source.

Since our main imageView is animating nice and the frame is a child view, we do not need to add anything to our motionScene. We just need to constrain the frame. Here is the effect 👇

Similarly for the buttons, text and other view components. Those are all child view for our main animating image (see view hierarchy posted before). Works like this:👇

So far, so good. The animation looks really nice and smooth, but what with the keyboard? Unfortunately everything jumps and ruins the whole smoothness of this view. Let’s try to fix it.

Keyboard opens = troubles

At the beginning I tried all available windowSoftInputMode that you can set in AndroidManifest file. Each is explained here. Unfortunately, none of them satisfied our needs. After that I tried to make a listener that notify the app when the keyboard opens and move the views as designed, but I ended up with really ugly and jumpy view. The clever hack was needed, and eventually I’ve found the one.

The hack

I needed some way to move only a few views and keep the rest of UI in the exact same place. Screenshot as a background of layout suited perfectly. This technique is used in a UI test. In Android testing support library there is a class Screenshot that does exactly what I needed, but tho it is not available in normal package. I needed to catch Bitmap. Below code shows how to do it.

Since I needed background image and foreground image (the one with frame and green gradient) static, the screenshot covers the whole screen (window.decorView.rootView)

We have a screenshot that is displayed as window background drawable. Now, we need to take it (screenshot) in right moment after our view opens and clear it when it is no longer needed. Transition Listener from motion layout is keen to help us out.

Observe transition changes

As seen in the code snippet above, the Transition Listener has five override functions that we can use to manipulate our views depending on animation progress. I will use two of them, onTransitionStarted and onTransitionCompleted.

Inside onTransitionCompleted I check following:

if (endId == R.id.endScene && !isScreenshotTaken) {
isScreenshotTaken = true
takeScreenshot()
}

My endScene is the one with a view settled on position, squeezed by 5%, green gradient applied, etc. When everything is set, I can make my screenshot and apply it as backgroundDrawable. After that I request focus on editText and keyboard show off, moving only those views that are still visible. The rest is just a screenshot.

Opening up the keyboard triggers again onTransitionCompleted. I use this opportunity to set up visibility for views. Let’s have a look how this scenario looks like.

At the end it is still a little jumpy, that’s because of a bug that we have in loading photos on the main fragment, nothing motionLayout related. 😃

During closing we need to clear bitmap (screenshot) and apply default backgroundDrawable. Those steps looks as follows:

requireActivity().window.setBackgroundDrawable(defaultBackgroundDrawable)
binding.etSendLikeMsgMessage.hideKeyboard()
binding.motionLayoutRoot.transitionToStart()
releaseBitmapFromMemory()

set background drawable to default value, you can get this value in initial state of fragment by requireActivity().window.decorView.background

  • hide keyboard
  • Trigger the transition to start, which will create smooth animation from endScene to startScene defined previously in motion scene
  • Release a bitmap. This step is important, cause recycling the bitmap simply allows it to be garbage collected.

The last animation

Something is missing here, right? It is dating app, so swiping is an important feature. Let’s swipe this view out, right after user send the message. We will use Motion Layout for that, of course.

Each motionLayout transition has two constraint sets, start and end. We will use our endScene as start and add endSwipeScene that will leads to close the view. The transition will start on click event on send button.

To produce nice swipe animation we need translation and rotation. After some testing I decide to go for a 45 degree angle and 600dp translationX.

The final effect:

Also in this scenario, this is important to release memory, set background to default and hide the keyboard. 😃

If you have any tip for this specific animation case or have questions about presented layout, feel free to comment.

Until next time.

Piotr

--

--

Piotr Prus
Schibsted Tech Polska

Android Developer @Tilt, Enthusiast of kotlin, jetpack compose and clean architecture. Currently Composing and KMMing all the things ❤️