How To Create Menu Attachment Popup Like WhatsApp on Android
WhatsApp is one of the popular chatting application on Android. It’s simple, yet intuitive UI makes the user easy to navigate and do some actions. There are several UI components that caught my attention and wonder how they build it on Android?. One of the components that I am going to talk about in this post is the attachment menu on the chat room page. I will make a separate post for the others.
After several days of Googling, I finally able to create the component. Although it’s not 100% the same, I think it’s close enough with the real one. You can head over to the repo page and compile it on your own machine. Okay, here is how I built it.
Behaviors
The attachment menu button in WhatsApp is located on the chat room page with an attachment icon overlay the EditText on the right. As you can see in the GIF above, when it is clicked, it opens up a window with circular animation and shows several options with what attachment to send, such as images, audios, contacts, etc.
There are two behaviors for this component when the soft keyboard is:
- opened
- closed
When the keyboard is opened, the menu will appear below the EditText and completely overlay the keyboard. On the other hand, when the keyboard is closed, the menu will appear above the EditText with a reasonable margin between the EditText and the window on the right and the left.
Is It a Fragment?
Okay, now probably you wonder what is that menu window anyway? A Fragment? A Dialog?. The answer is a PopupWindow. What is PopupWindow? As the doc says:
This class represents a popup window that can be used to display an arbitrary view. The popup window is a floating container that appears on top of the current activity.
Why not use a Dialog? A Dialog has several built-in functions or features that set as default when shows up like the dimmed background, shows in the middle of the screen. Whereas with PopupWindow, you have full control of everything, such as positioning, sizing, layout, and etc. I will no go into detail to explain the difference and which one is better. You can go search for it yourself. To save you some trouble, here is the StackOverflow link.
Another reason for me using PopupWindow is it’s exactly what Telegram is using. Yes, I read the Telegram open source code, although the code structure is a disaster 🙈 lacks separation whatsoever, there are many hidden gems in it, and you actually can learn a lot of things from it.
To Do’s
Here are several things that need to develop:
- Creating the PopupWindow class.
- Handle when tapping outside of the popup.
- Calculating the keyboard height.
- Inflating the menu Layout.
- Determine if the keyboard state open/close.
- Showing/Dismissing the popup with a circular animation.
- Handle when tapping the back button.
Creating The PopupWindow Class
There are several additional parameters needed for the constructor:
- rootView = the topmost ViewGroup of your layout. The root view is needed for the keyboard height calculation.
- editText = the EditText view where you type message. Needed for signaling the keyboard to open/close.
- anchorView = the anchor view is a view that behaves as an anchor for the popup window to help position the popup when showing it above the editText. (in this case, I pass the same view reference of editText and anchorView)
- triggerView = the trigger view used to define the starting
x
position of the circular animation.
Handle When Tapping Outside of the Popup
Popup window has a function that let us intercept touch event. with setTouchInterceptor
function we can detect whether the user is tapping outside of the popup or not. You also need to set isOutsideTouchable
to true. When the user is tapping outside the popup window, we want to dismiss the popup.
Calculating the keyboard height & Determine if the keyboard state open/close
Unfortunately, the Android framework doesn’t provide a convenient function to get the device soft keyboard height. In that case, we need to calculate the keyboard height ourselves.
What this code does are add the addOnGlobalLayoutListener
to the rootView viewTreeObserver
so that we can detect changes in the rootView height. To calculate the keyboard height, we first calculate the total screen height of the phone. Then calculate current rootView height. After that, we subtract the screen height with the rootView height to calculate the height difference. If the result greater than the offset, the keyboard is open. If the result is zero, then the keyboard is closed. we then save the keyboard state in the class field variable
Inflating the menu Layout
To give the popup menu a view that user can interact to, we simply create the layout resource file and inflate it using the LayoutInflater
. You can also do some initiation of the view here like adding listener to the views and others, much like the onCreate
function in an Activity. In this case, I add the addOnLayoutChangeListener
so that when the popup shows up, it calls the revealView
function which does a circular reveal animation for the popup menu. In this example, the layout file just contained a customed RecyclerView. You can also create your own layout and customized it the way you want.
After we finish do some view configuration, we call the PopupWindow setContentView
function with the view we just inflate as the param to set it as the popup menu view.
Showing/Dismissing the popup with a circular animation
Before showing the popup menu, we first need to check whether the state of the soft keyboard is opened or not. If yes, we show it over the keyboard, otherwise at the top of the editText. We also need to set the keyboard width & height, because the two use cases have different menu size.
To show over the keyboard, we don’t need to do any calculation. We just passed the Gravity.BOTTOM
to the function with 0 offset at x and y coordinate.
It’s not the case for showing above the editText. We need to calculate the width of the popup menu and the x, y offset coordinates. We don’t need to calculate the x offset because the view is centered horizontally, so we just pass 0 to it. For the y offset, we need to calculate the coordinate so that the popup menu appears above the editText with the specified margin. See the variable y calculation in the code.
To create circular reveal animation, we just need to use the ViewAnimationUtils.createCircularReveal() function. Before calling the function first, we need to calculate several variables:
- The start x and y coordinate of the center of the circle.
- The radius of the circle.
In this case, we want the circular animation to cover the entire popup menu. We set the radius by calculating the diagonal length of the popup menu by simply using the trigonometry concept.
To show the popup menu with the circular reveal, we pass the radius length as the endRadius
argument and give the startRadius
Zero value. To dismiss the keyboard with circular animation, we just swap the startRadius
value with the endRadius
value.
Handle when tapping the back button
By default, the PopUp window class already handles the back button. It all went as expected if the popup menu shows above the editText. But if it shows over the keyboard and you tap the back button, it will both close the popup menu and the keyboard. In our case, if the user taps the back button and the popup menu is opened, we want to close to popup first, and then when the user taps the back button again, it closes the keyboard.
We can need to create a custom view that extends the editText class and override the onKeyPreIme
function. Inside of that function, we then check whether the user taps the back button and whether popup is showed or not. If it is, close the popup menu first.
So that’s a big picture of how I create the attachment menu like WhatsApp on Android. You can also fork the repo, build it on your local machine, and examine the code thoroughly yourself. Feel free to create an issue and PR for improvement. Thank you