Circular Reveal Animation In Android

Android
 

Introduction

 
In this article, we will learn how to create layout reveal animation in Android. This feature is available by default from Lollipop. For pre-Lollipop Android versions, an awesome library is available and it is very easy to use.
 
Circular Reveal animations can be found in WhatsApp while clicking the attachment option. It’s more interactive to users. We will learn in detail how to use in Lollipop and Pre-Lollipop devices separately.
 

Circular Reveal Animation

 
We already know this feature is available from Lollipop devices. To perform the same features in pre-Lollipop devices we will go for a third party library and it’s awesome to use.
 
Steps
 
I have divided this Implementation into 4 steps as shown in the following.
  • Step 1 - Creating a New Project with Android Studio
  • Step 2 - Setting up the library for the project.
  • Step 3 - Implementation of Circular Reveal for Pre-Lollipop devices.
  • Step 4 - Implementation of Circular Reveal for Lollipop devices.
Step 1 - Creating a New Project with Android Studio
  1. Open Android Studio and select Create a new project.
  2. Name the project as you wish and select your activity template.
     
    Android
     
  3. Click the Finish button to create a new project in Android Studio.
Step 2 - Setting up the library for the project
  1. Open the App Level gradle file and add the following lines to add ButterKnife Library.
    1. dependencies {  
    2.     ...  
    3.     implementation 'com.android.support:appcompat-v7:26.1.0'  
    4.     implementation 'com.android.support:support-annotations:27.1.1'  
    5.     implementation 'com.android.support.constraint:constraint-layout:1.1.2'  
    6.     implementation 'com.android.support:design:26.1.0'  
    7.   
    8.     // Third Party Library  
    9.     implementation 'com.github.ozodrukh:CircularReveal:1.1.0'  
    10.     ...  
    11. }  
    12.   
    13. repositories {  
    14.     maven {  
    15.         url "https://jitpack.io"  
    16.     }  
  1. Then click Sync Nowto add the library.
Step 3 - Implementation of Circular Reveal for Pre-Lollipop devices
  1. Open your designer file that is your XML layout file (In my case, I have opened my xml file) and paste the following code below toolbar if you want.
    1. <io.codetail.widget.RevealFrameLayout  
    2.     android:layout_width="match_parent"  
    3.     android:layout_height="match_parent">  
    4.   
    5.     <LinearLayout  
    6.         android:id="@+id/reveal_items"  
    7.         android:layout_width="match_parent"  
    8.         android:layout_height="match_parent"  
    9.         android:background="@android:color/white"  
    10.         android:orientation="horizontal"  
    11.         android:gravity="center"  
    12.         android:padding="16dp">  
    13.   
    14.         <LinearLayout  
    15.             android:layout_width="match_parent"  
    16.             android:layout_height="wrap_content"  
    17.             android:orientation="horizontal">  
    18.   
    19.             <ImageView  
    20.                 android:layout_gravity="center"  
    21.                 android:id="@+id/imageView"  
    22.                 android:layout_width="wrap_content"  
    23.                 android:layout_height="wrap_content"  
    24.                 android:background="@drawable/ic_search_green" />  
    25.   
    26.             <EditText  
    27.                 android:id="@+id/search_box"  
    28.                 android:layout_width="match_parent"  
    29.                 android:layout_height="wrap_content"  
    30.                 android:hint="Search Here..."  
    31.                 android:inputType="text"  
    32.                 android:imeOptions="actionSearch"  
    33.                 android:maxLines="1"/>  
    34.         </LinearLayout>  
    35.     </LinearLayout>  
    36.   
    37. </io.codetail.widget.RevealFrameLayout>  
  1. Here, Reveal Frame Layout itself is used to handle Reveal animation. The Reveal effect can be applied to the whole screen.
     
  2. We have initialized the immediate child of the RevealFrameLayout and by default, its state should be GONE or INVISIBLE. While changing its View states, the RevealFrameLayout will clip its child’s shape
    1. revealLayout = findViewById(R.id.reveal_items);  
    2. revealLayout.setVisibility(View.INVISIBLE);  
  1. To perform the animation, import SupportAnimator class from the third party library included for Pre-Lollipop devices.
    1. import io.codetail.animation.SupportAnimator;  
    2. import io.codetail.animation.ViewAnimationUtils;  
  1. Then add the following to perform the animation in forth and reverse. We need X, Y co-ordinates to perform the animation.
    1. SupportAnimator animator = ViewAnimationUtils.createCircularReveal(layout, cx, cy, 0, radius);  
    2. animator.setInterpolator(new AccelerateDecelerateInterpolator());  
    3. animator.setDuration(800);  
    4.   
    5. SupportAnimator animator_reverse = animator.reverse();  
    6.   
    7. if (hidden) {  
    8.     layout.setVisibility(View.VISIBLE);  
    9.     animator.start();  
    10.     invalidateOptionsMenu();  
    11.     hidden = false;  
    12. else {  
    13.     animator_reverse.addListener(new SupportAnimator.AnimatorListener() {  
    14.         @Override  
    15.         public void onAnimationStart() {  
    16.   
    17.         }  
    18.   
    19.         @Override  
    20.         public void onAnimationEnd() {  
    21.             layout.setVisibility(View.INVISIBLE);  
    22.             invalidateOptionsMenu();  
    23.             hidden = true;  
    24.         }  
    25.   
    26.         @Override  
    27.         public void onAnimationCancel() {  
    28.   
    29.         }  
    30.   
    31.         @Override  
    32.         public void onAnimationRepeat() {  
    33.   
    34.         }  
    35.     });  
    36.     animator_reverse.start();  
    37. }  
  1. Here,
     
    • cx - co-ordinate in X axis. cy – co-ordinate in Y axis. These two are required for starting the animation in X, Y axis of the screen. It can be calculated by the following code. 
      1. int cx = revealLayout.getRight();  
      2. int cy = revealLayout.getTop();  
    • radius
       
      can be calculated by the following code. 
      1. int radius = Math.max(layout.getWidth(), layout.getHeight());  
    • Animation can be started by SupportAnimator and it is assigned to another SupportAnimator which is used to start the reverse animation. So, the reverse animation will be handled by itself. 
      1. SupportAnimator animator = ViewAnimationUtils.createCircularReveal(layout, cx, cy, 0, radius);  
      2. animator.setInterpolator(new AccelerateDecelerateInterpolator());  
      3. animator.setDuration(800);  
      4.   
      5. SupportAnimator animator_reverse = animator.reverse();  
      6.   
      7. if (hidden) {  
      8.     layout.setVisibility(View.VISIBLE);  
      9.     animator.start();  
      10.     invalidateOptionsMenu();  
      11.     hidden = false;  
      12. else {  
      13.     animator_reverse.addListener(new SupportAnimator.AnimatorListener() {  
      14.         @Override  
      15.         public void onAnimationStart() {  
      16.   
      17.         }  
      18.   
      19.         @Override  
      20.         public void onAnimationEnd() {  
      21.             layout.setVisibility(View.INVISIBLE);  
      22.             invalidateOptionsMenu();  
      23.             hidden = true;  
      24.         }  
      25.   
      26.         @Override  
      27.         public void onAnimationCancel() {  
      28.   
      29.         }  
      30.   
      31.         @Override  
      32.         public void onAnimationRepeat() {  
      33.   
      34.         }  
      35.     });  
      36.     animator_reverse.start();  
      37. }  
    • hidden – is a Boolean value used to know the View State.
  1. Probably this does not seem to work from Lollipop devices. So we have to go for a different method which is shown in the next step.
Step 4 - Implementation of Circular Reveal for Lollipop devices
  1. No 5 in the previous step is slightly changed for Lollipop devices. Instead of “io.codetail.animation.ViewAnimationUtils”, use “android.view.ViewAnimationUtils”.
      
  2. No need to assign animation.reverse(). Instead of it, use the following 
    1. if (hidden) {  
    2.     Animator anim = android.view.ViewAnimationUtils.createCircularReveal(layout, cx, cy, 0, radius);  
    3.     layout.setVisibility(View.VISIBLE);  
    4.     anim.start();  
    5.     invalidateOptionsMenu();  
    6.     hidden = false;  
    7.   
    8. else {  
    9. Animator anim = android.view.ViewAnimationUtils.createCircularReveal(layout, cx, cy, radius, 0);  
    10.     anim.addListener(new AnimatorListenerAdapter() {  
    11.         @Override  
    12.         public void onAnimationEnd(Animator animation) {  
    13.             super.onAnimationEnd(animation);  
    14.             layout.setVisibility(View.INVISIBLE);  
    15.             hidden = true;  
    16.             invalidateOptionsMenu();  
    17.         }  
    18.     });  
    19.     anim.start();  
    20. }  
  1. To reverse the animation, the radius value is interchanged. The animation is handled from toolbar menu options.
Full Code
 
You can find the full code implementation here.
  1. public class MainActivity extends AppCompatActivity {  
  2.   
  3.     Toolbar toolbar;  
  4.     LinearLayout revealLayout;  
  5.     EditText searchBox;  
  6.     boolean hidden = true;  
  7.   
  8.     @Override  
  9.     protected void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.         setContentView(R.layout.activity_main);  
  12.   
  13.         toolbar = findViewById(R.id.toolbar);  
  14.         setSupportActionBar(toolbar);  
  15.   
  16.         revealLayout = findViewById(R.id.reveal_items);  
  17.         revealLayout.setVisibility(View.INVISIBLE);  
  18.         searchBox = findViewById(R.id.search_box);  
  19.         searchBox.setOnEditorActionListener(new TextView.OnEditorActionListener() {  
  20.             @Override  
  21.             public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {  
  22.                 if (actionId == EditorInfo.IME_ACTION_SEARCH) {  
  23.                     handleKeyboard(searchBox);  
  24.                     searchBox.clearFocus();  
  25.                     Toast.makeText(MainActivity.this, textView.getText().toString(), Toast.LENGTH_SHORT).show();  
  26.                     return true;  
  27.                 }  
  28.                 return false;  
  29.             }  
  30.         });  
  31.     }  
  32.   
  33.     void handleKeyboard(View view) {  
  34.         InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);  
  35.         assert inputManager != null;  
  36.         inputManager.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);  
  37.     }  
  38.   
  39.     @Override  
  40.     public boolean onCreateOptionsMenu(Menu menu) {  
  41.         getMenuInflater().inflate(R.menu.menu_reveal, menu);  
  42.         if (hidden) {  
  43.             menu.findItem(R.id.action_search).setIcon(R.drawable.ic_search);  
  44.         } else {  
  45.             menu.findItem(R.id.action_search).setIcon(R.drawable.ic_close);  
  46.         }  
  47.         return true;  
  48.   
  49.     }  
  50.   
  51.     @Override  
  52.     public boolean onOptionsItemSelected(MenuItem item) {  
  53.         switch (item.getItemId()) {  
  54.             case R.id.action_search:  
  55.                 int cx = revealLayout.getRight();  
  56.                 int cy = revealLayout.getTop();  
  57.                 makeEffect(revealLayout, cx, cy);  
  58.                 searchBox.setText(null);  
  59.                 return true;  
  60.             case android.R.id.home:  
  61.                 supportFinishAfterTransition();  
  62.                 return true;  
  63.         }  
  64.   
  65.         return super.onOptionsItemSelected(item);  
  66.     }  
  67.   
  68.     private void makeEffect(final LinearLayout layout, int cx, int cy) {  
  69.   
  70.         int radius = Math.max(layout.getWidth(), layout.getHeight());  
  71.   
  72.         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {  
  73.   
  74.             SupportAnimator animator = ViewAnimationUtils.createCircularReveal(layout, cx, cy, 0, radius);  
  75.             animator.setInterpolator(new AccelerateDecelerateInterpolator());  
  76.             animator.setDuration(800);  
  77.   
  78.             SupportAnimator animator_reverse = animator.reverse();  
  79.   
  80.             if (hidden) {  
  81.                 layout.setVisibility(View.VISIBLE);  
  82.                 animator.start();  
  83.                 invalidateOptionsMenu();  
  84.                 hidden = false;  
  85.             } else {  
  86.                 animator_reverse.addListener(new SupportAnimator.AnimatorListener() {  
  87.                     @Override  
  88.                     public void onAnimationStart() {  
  89.   
  90.                     }  
  91.   
  92.                     @Override  
  93.                     public void onAnimationEnd() {  
  94.                         layout.setVisibility(View.INVISIBLE);  
  95.                         invalidateOptionsMenu();  
  96.                         hidden = true;  
  97.                     }  
  98.   
  99.                     @Override  
  100.                     public void onAnimationCancel() {  
  101.   
  102.                     }  
  103.   
  104.                     @Override  
  105.                     public void onAnimationRepeat() {  
  106.   
  107.                     }  
  108.                 });  
  109.                 animator_reverse.start();  
  110.             }  
  111.         } else {  
  112.             if (hidden) {  
  113.                 Animator anim = android.view.ViewAnimationUtils.createCircularReveal(layout, cx, cy, 0, radius);  
  114.                 layout.setVisibility(View.VISIBLE);  
  115.                 anim.start();  
  116.                 invalidateOptionsMenu();  
  117.                 hidden = false;  
  118.   
  119.             } else {  
  120.                 Animator anim = android.view.ViewAnimationUtils.createCircularReveal(layout, cx, cy, radius, 0);  
  121.                 anim.addListener(new AnimatorListenerAdapter() {  
  122.                     @Override  
  123.                     public void onAnimationEnd(Animator animation) {  
  124.                         super.onAnimationEnd(animation);  
  125.                         layout.setVisibility(View.INVISIBLE);  
  126.                         hidden = true;  
  127.                         invalidateOptionsMenu();  
  128.                     }  
  129.                 });  
  130.                 anim.start();  
  131.             }  
  132.         }  
  133.     }  
  134. }  
Output Demo
 
You can find the output of this example.
 
Output Demo
 
Download Code
 
You can download the full source code of the article in GitHub. If you like this article, do star the repo in GitHub.