Android Circular Reveal Effect Like In Whatsapp

By | November 20, 2015

In Android whatsapp while sending media files you can notice a attachment icon on right side of the toolbar which performs a neat toggle circular effect , as you can see in below gif image which is exactly what I am talking about in this tutorial we will see how to mimic this effect .

Reveal Effect .

The Reveal Effect uses the RippleDrawable class to achieve this . So what is “reveal”? It is just an animation which animates view’s clipping boundaries. Android provides really convenient helper method to create
this animation.

The reveal is intended to for LOLIPOP and Above version , this effect does not work with below LOLIPOP . However there is a Library the manages to support to work with lower version , this library is very convenient to use .Though one must be careful about the imports.

Build Gradle

Open build.gradle(app:module) , add the repository first, and then the dependency . on sync the library will be included .

file : build.gradle

repositories {
    maven {
        url "https://jitpack.io"
        }
    }

 dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.1.0'
    compile ('com.github.ozodrukh:CircularReveal:1.1.1@aar') {
                   transitive = true;
                }
  }

Menu

open res/menu/main_menu.xml and include the below menu item . The Highlighted menu item i.e action attachment menu icon will be in our Toolbar .
file : main_menu.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".MainActivity">
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:title="@string/action_settings"
        app:showAsAction="never" />
    <item
        android:id="@+id/action_clip"
        android:icon="@drawable/ic_attach"
        android:title="Media"
        app:showAsAction="ifRoom" />
</menu>

XML Layout

Create a XML layout by name toolbar.xml inside the res/layout and add below toolbar widget to it .

file : toolbar.xml

<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.Toolbar 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

Create a XML Layout by name media_attach_menu.xml inside the res/layout and design this layout similar to WhatsApp’s Attach option.

file : media_attach_menu.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/reveal_items"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <!--row 1 -->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#F0EFED"
        android:orientation="horizontal"
        android:padding="16dp">

        <!--Gallery Icon -->
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <ImageButton
                android:id="@+id/gallery_img_btn"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:background="@drawable/gallery" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="Gallery" />
        </LinearLayout>

        <!--Photo Icon -->
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <ImageButton
                android:id="@+id/photo_img_btn"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:background="@drawable/photo" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="Photo" />
        </LinearLayout>

        <!--Video Icon -->
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <ImageButton
                android:id="@+id/video_img_btn"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:background="@drawable/video" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="Video" />
        </LinearLayout>

    </LinearLayout>

    <!--row 2 -->
    <!-- similarly create second row -- >
  
</LinearLayout>

Create a XML Layout by name activity_main.xml inside the res/layout and inside this layout as you can see the highlighted XML Tag RevealFrameLayout . which is custom XML Tag from circular reveal library inside this xml tag include the above designed xml layout media_attach_menu.xml .

file : activity_main.xml

<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg"
    tools:context="com.suleiman.material.activities.RevealAnimation">

    <include layout="@layout/toolbar" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?attr/actionBarSize">

        <io.codetail.widget.RevealFrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <include layout="@layout/media_attach_menu" />

        </io.codetail.widget.RevealFrameLayout>

    </FrameLayout>

</FrameLayout>

androidcircularrevealeffect

MainActivity

  • Create a class MainActivity and extend this class to Activity and set the content of activity with above defined XML layout (activity_main.xml).
  • Create a method initView() and inside this method initialize the all the required views and set the onclick listener for ImageButton.
  • Inside the onOptionsItemSelected method for action_clip menu item we will add the animation effect .

Circular Reveal Effect Animation

As I said above reveal effect is intended to for LOLIPOP and Above version , for version below LOLIPOP we using circular reveal effect library. Firstly we will check at runtime for android version below in the code you can see the comment for if-else which is self explanatory . And also I said earlier that you must be careful about the imports

For Below android LOLIPOP Version we will use api from library .

import io.codetail.animation.SupportAnimator;
import io.codetail.animation.ViewAnimationUtils;

SupportAnimator animator = ViewAnimationUtils.createCircularReveal(mRevealView, cx, cy, 0, radius);

Reverse : The library’s SupportAnimator provides a method reverse() .
For Android LOLIPOP and Above we will use native android api .

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;

Animator anim = android.view.ViewAnimationUtils.createCircularReveal(mRevealView, cx, cy, 0, radius);

Reverse : we will add the AnimatorListener on animator inside and inside the onAnimationEnd we will hide the RevealFrameLayout .

Parameters of createCircularReveal methods .

createCircularReveal(View view, int cx, int cy, float startRadius, float endRadius)

  • view – view to reveal
  • cx– start X coordinate of reveal
  • cy– start Y coordinate of reveal
  • startRadius – start radius. In most cases – 0
  • endRadius – end radius – depends on your view’s bounds .

Toggle Effect

  • We will use a simple boolean hidden to handle the toggling and update it’s value inside if-else .
  • Onclick of Image Button inside Action Option we will make a call to hideRevealView method , inside this method we will hide the RevealFrameLayout .

file : MainActivity.java

package com.tutorialsbuzz.circularreveal;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.ImageButton;
import android.widget.LinearLayout;

import io.codetail.animation.SupportAnimator;
import io.codetail.animation.ViewAnimationUtils;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private LinearLayout mRevealView;
    private boolean hidden = true;
    private ImageButton gallery_btn, photo_btn, video_btn, audio_btn, location_btn, contact_btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    private void initView() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mRevealView = (LinearLayout) findViewById(R.id.reveal_items);
        mRevealView.setVisibility(View.GONE);

        gallery_btn = (ImageButton) findViewById(R.id.gallery_img_btn);
        photo_btn = (ImageButton) findViewById(R.id.photo_img_btn);
        video_btn = (ImageButton) findViewById(R.id.video_img_btn);
        audio_btn = (ImageButton) findViewById(R.id.audio_img_btn);
        location_btn = (ImageButton) findViewById(R.id.location_img_btn);
        contact_btn = (ImageButton) findViewById(R.id.contact_img_btn);

        gallery_btn.setOnClickListener(this);
        photo_btn.setOnClickListener(this);
        video_btn.setOnClickListener(this);
        audio_btn.setOnClickListener(this);
        location_btn.setOnClickListener(this);
        contact_btn.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        hideRevealView();
        switch (v.getId()) {

            case R.id.gallery_img_btn:

                break;
            case R.id.photo_img_btn:

                break;
            case R.id.video_img_btn:

                break;
            case R.id.audio_img_btn:

                break;
            case R.id.location_img_btn:

                break;
            case R.id.contact_img_btn:

                break;
        }
    }

    private void hideRevealView() {
        if (mRevealView.getVisibility() == View.VISIBLE) {
            mRevealView.setVisibility(View.GONE);
            hidden = true;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {

            case R.id.action_clip:

                int cx = (mRevealView.getLeft() + mRevealView.getRight());
                int cy = mRevealView.getTop();
                int radius = Math.max(mRevealView.getWidth(), mRevealView.getHeight());

                //Below Android LOLIPOP Version
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    SupportAnimator animator =
                            ViewAnimationUtils.createCircularReveal(mRevealView, cx, cy, 0, radius);
                    animator.setInterpolator(new AccelerateDecelerateInterpolator());
                    animator.setDuration(700);

                    SupportAnimator animator_reverse = animator.reverse();

                    if (hidden) {
                        mRevealView.setVisibility(View.VISIBLE);
                        animator.start();
                        hidden = false;
                    } else {
                        animator_reverse.addListener(new SupportAnimator.AnimatorListener() {
                            @Override
                            public void onAnimationStart() {

                            }

                            @Override
                            public void onAnimationEnd() {
                                mRevealView.setVisibility(View.INVISIBLE);
                                hidden = true;

                            }

                            @Override
                            public void onAnimationCancel() {

                            }

                            @Override
                            public void onAnimationRepeat() {

                            }
                        });
                        animator_reverse.start();
                    }
                }
                // Android LOLIPOP And ABOVE Version
                else {
                    if (hidden) {
                        Animator anim = android.view.ViewAnimationUtils.
                                createCircularReveal(mRevealView, cx, cy, 0, radius);
                        mRevealView.setVisibility(View.VISIBLE);
                        anim.start();
                        hidden = false;
                    } else {
                        Animator anim = android.view.ViewAnimationUtils.
                                createCircularReveal(mRevealView, cx, cy, radius, 0);
                        anim.addListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(Animator animation) {
                                super.onAnimationEnd(animation);
                                mRevealView.setVisibility(View.INVISIBLE);
                                hidden = true;
                            }
                        });
                        anim.start();
                    }
                }
                return true;

            case android.R.id.home:
                supportFinishAfterTransition();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

androidripplemenueffect