Make your app shine #2: How to make a button morph into a form
Hi folks, this is the second article of a series that I am working in to show how to make simple, but beautiful, micro interaction. You can see the first one here and the third one here. The complete code of this tutorial can be found here:
https://github.com/leandroBorgesFerreira/buttonMorphToFormAndroid
Interactions between views take some time to get good, but those are the things that make a user fall in love with your product. There is a quote about the price of design that I really like:
“If you think good design is expensive, you should look at the cost of bad design.” –Ralf Speth
So expend some time to get your UI polished can be a really good idea (Just don't forget to be realistic about your deadlines, ok?). So, let's learn how to make an animation that will make your users want to fill your forms.
Part 1 — Let's make the skeleton of the project.
So, before make any animation we need to create our layout with the buttons and forms that are going to be animated. Let's create the XML for our first button. This is our button:
You must be thinking: "All of this just to create a button?? o.O". Remember that we are going to morph this button into a form, so we need to put all the views inside the button. But let's understand this layout:
1 — The CardView acts as a decorator. It gives rounded corners o the button and act as the button/form container. This is the view that is going to be animated.
2 — The button with id login_btn is the one that will listen for clicks.
3 — The LinearLayout has all the views of the form.
4 — All the views of the form are with visibility GONE. This way, for now, our form is nothing more than just a button. When the the visibility of our Views changes, we get the button with the animation at the top of this article.
So, with the views hidden, we have:
But if we show all the views, we get:
Part 2 — The animation
Now we need to make our animation. The transitions are going to be animated, so in the root view we need to insert android:animateLayoutChanges=”true”.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#aaa"
android:animateLayoutChanges="true">
We are going to make the translation animation by changing the gravity of our CardView. Did you notice that the gravity is start|bottom? If the change it to center we will see the translation animation.
Ok, so this will be out animation:
public void morphIntoForm() {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) buttonContainer.getLayoutParams();
initialWidth = layoutParams.width;
initialGravity = layoutParams.gravity;
layoutParams.gravity = Gravity.CENTER;
layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT;
buttonContainer.setLayoutParams(layoutParams);
for (int i = 1; i < viewsContainer.getChildCount(); i++) {
viewsContainer.getChildAt(i).setVisibility(View.VISIBLE);
}
isPressed = true;
}
Really simple, right? All we do is change the gravity and width of our buttonContainer and make all the views of the form visible.
Well, we need to declare our variable and make a reverse animation. The animation class is:
public class MorphAnimation {
private final FrameLayout parentView;
private View buttonContainer;
private ViewGroup viewsContainer;
private boolean isPressed;
private int initialWidth;
private int initialGravity;
public boolean isPressed() {
return isPressed;
}
public MorphAnimation(View buttonContainer, FrameLayout parentView, ViewGroup viewsContainer) {
this.buttonContainer = buttonContainer;
this.parentView = parentView;
this.viewsContainer = viewsContainer;
LayoutTransition layoutTransition = parentView.getLayoutTransition();
layoutTransition.setDuration(400);
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
isPressed = false;
}
public void morphIntoForm() {
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) buttonContainer.getLayoutParams();
initialWidth = layoutParams.width;
initialGravity = layoutParams.gravity;
layoutParams.gravity = Gravity.CENTER;
layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT;
buttonContainer.setLayoutParams(layoutParams);
for (int i = 1; i < viewsContainer.getChildCount(); i++) {
viewsContainer.getChildAt(i).setVisibility(View.VISIBLE);
}
isPressed = true;
}
public void morphIntoButton() {
for (int i = 1; i < viewsContainer.getChildCount(); i++) {
viewsContainer.getChildAt(i).setVisibility(View.GONE);
}
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) buttonContainer.getLayoutParams();
layoutParams.gravity = initialGravity;
layoutParams.width = initialWidth;
buttonContainer.setLayoutParams(layoutParams);
isPressed = false;
}
}
Besides the android:animateLayoutChanges=”true”, the also need the to setlayoutTransition.enableTransitionType(LayoutTransition.CHANGING) in our root view. This command enables the translation animation to happen. Without it, the form would simply jump to the center of the screen. You can also set the duration of the animation, like I set to 400ms.
The morphIntoButton() method is just the reversal animation of morphIntoForm().
Part 3 — Let's call the animation!
This is where the fun comes!!
Now all you have to do is call the animations, like this:
public class MainActivity extends AppCompatActivity {
private MorphAnimation morphAnimationLogin;
private MorphAnimation morphAnimationRegister;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View loginContainer = findViewById(R.id.form_btn);
View registeContainer = findViewById(R.id.form_register);
Button buttonLogin = (Button) findViewById(R.id.login_btn);
Button buttonRegister = (Button) findViewById(R.id.button_register);
ViewGroup loginViews = (ViewGroup) findViewById(R.id.login_views);
ViewGroup registeViews = (ViewGroup) findViewById(R.id.register_views);
final FrameLayout rootView = (FrameLayout) findViewById(R.id.root_view);
morphAnimationLogin = new MorphAnimation(loginContainer, rootView, loginViews);
morphAnimationRegister = new MorphAnimation(registeContainer, rootView, registeViews);
buttonLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!morphAnimationLogin.isPressed()) {
morphAnimationLogin.morphIntoForm();
} else {
morphAnimationLogin.morphIntoButton();
}
}
});
buttonRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!morphAnimationRegister.isPressed()) {
morphAnimationRegister.morphIntoForm();
} else {
morphAnimationRegister.morphIntoButton();
}
}
});
}
}
I added another button in my xml to make the animation a bit more interesting. I got this:
And that's it! Please remember that this animation won't work in any kind of situation, you need to adapt this tutorial to your needs. But I believe that, with some effort, you can achieve really nice interactions using this concept.
If you enjoyed reading this, please click the heart icon below. This will help to share the story with others. If you would like to see more content like this one, follow me.
Reference:
I got inspiration to make this tutorial from this site