Material Design: Add circular reveal animation whi

2019-03-22 02:12发布

问题:

I was reading the "Selection" section in material design guidelines (https://material.io/guidelines/patterns/selection.html), and one effect i wanted to add in my app was the circular reveal animation while switching between the app bar and the ActionMode ? An other toolbar ?

Here is an example from the guidelines: https://storage.googleapis.com/material-design/publish/material_v_10/assets/0Bwp7jtSjaW36RGF3eUFsRkdqU1U/patterns_selection_item_controlling_desktop_click.webm

I didn't found any explanations about how to do that. I do not even know if they use an ActionMode or something else...

Is there someone who could give me the good direction to follow ?

edit: minSdk 21

edit 2: look at the status bar which also changes itself...

Thanks François

回答1:

Ok finally i found a solution.

It is not a very good one... but i have no other idea to use something else so if you have some other tips to share... you're welcome!

Here is the final result and code below:

Sample project on GitHub

https://github.com/fbourlieux/android-material-circular_reveal_animation

Goal & idea

Switching from a toolbar to another one by using a "smooth" circular revealed animation. That animation need to update the app bar AND status bar.

To do so, first we need to force the activity to display it content under the status bar by using android:fitsSystemWindows=false property on the main layout container and <item name="android:windowTranslucentStatus">true</item> in the App theme. Based on that we will not only create a Toolbar but also a view that will be displayed under the statusBar, juste to draw a nice background during the animation. Here is the point i don't like in my sample, but i didn't found any other solution.

Let's see the code

styles.xml

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="android:windowTranslucentStatus">true</item>
</style>

we just added the android:windowTranslucentStatus property.

app_bar_main.xml

<android.support.design.widget.CoordinatorLayout
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="false"
    tools:context="sample.test.fbo.circularrevealanimation.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <!-- used to force the two toolbars to display above each other -->
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <!-- initial toolbar layout with the status bar 
            and the original toolbar. That layout need to have a 
            background to show the elevation even if it will never 
            be visible (because of inner component backgrounds) -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/colorPrimary"
                android:elevation="4dp"
                android:orientation="vertical">

                <!-- status bar background: height of 24dp 
                and initial color darker than the toolbar color -->
                <View
                    android:layout_width="match_parent"
                    android:layout_height="24dp"
                    android:background="@color/colorPrimaryDark" />

                <!-- main toolbar. A very basic one.-->
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="?attr/colorPrimary"
                    app:popupTheme="@style/AppTheme.PopupOverlay" />

            </LinearLayout>

            <!-- reveal section layout. Here is our second toolbar
            section which will be animated. It contains a view to
            fake the status bar background and the second toolbar
            to display. -->
            <LinearLayout
                android:id="@+id/revealedToolBar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/colorAccentDark"
                android:elevation="4dp"
                android:orientation="vertical"
                android:visibility="invisible">

                <!-- revealed status bar. Just to change it background. -->
                <View
                    android:id="@+id/revealBackgroundStatus"
                    android:layout_width="match_parent"
                    android:layout_height="24dp"
                    android:background="@color/colorAccentDark" />

                <!-- revealed toolbar. The second one with in our case 
                a simple button and text inside. -->
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar2"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="@color/colorAccent"
                    app:popupTheme="@style/AppTheme.PopupOverlay">

                    <!-- a click on that button will trigger 
                         the animation close event -->
                    <ImageButton
                        android:id="@+id/toolbar_arrow"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:background="@android:color/transparent"
                        android:src="@drawable/arrow_left" />

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_marginLeft="24dp"
                        android:fontFamily="sans-serif-regular"
                        android:gravity="center_vertical"
                        android:text="Foo Bar Baz"
                        android:textColor="@android:color/white"
                        android:textSize="18sp"
                        android:textStyle="bold"
                        tools:text="Foo Bar Baz" />
                </android.support.v7.widget.Toolbar>
            </LinearLayout>

        </RelativeLayout>
    </android.support.design.widget.AppBarLayout>

    <!-- content_main just contains a ToggleButton to trigger 
    the animation-->
    <include layout="@layout/content_main" />

</android.support.design.widget.CoordinatorLayout>

Create 2 overlapped layout that contains a view to draw the status bar and a view to draw the toolbar. By default, the layout to animate is set invisible.

MainActivity.java

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, View.OnClickListener {

    private final static int ANIMATION_DURATION = 400;
    private ToggleButton mActionButton;
    private View mRevealedToolBar;
    private ImageButton mArrowButton;
    private boolean mIsHidden = true;

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

        // main toolbar
        final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        final ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.setDrawerListener(toggle);
        toggle.syncState();

        final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        // trigger circular reveal animation
        mActionButton = (ToggleButton) findViewById(R.id.actionButton);
        mActionButton.setOnClickListener(this);

        // toolbar view to reveal. Inivisible by default
        mRevealedToolBar = findViewById(R.id.revealedToolBar);
        mRevealedToolBar.setVisibility(View.INVISIBLE);

        // button in revealed toolbar to dismiss it
        mArrowButton = (ImageButton) findViewById(R.id.toolbar_arrow);
        mArrowButton.setOnClickListener(this);
    }

    @Override
    public void onBackPressed() {
        final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

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

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        if (item.getItemId() == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(final MenuItem item) {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }



    @Override
    public void onClick(final View view) {

        if (view == mActionButton || view == mArrowButton) {


            // compute started X and Y co-ordinates for the animation + radius
            int x = mRevealedToolBar.getLeft();
            int y = mRevealedToolBar.getBottom();
            int startRadius = 0;
            int endRadius = Math.max(mRevealedToolBar.getWidth(), mRevealedToolBar.getHeight());
            int reverseStartRadius = endRadius;
            int reverseEndRadius = startRadius;



            if (mIsHidden) {

                // show secondary toolbar
                // performing circular reveal when icon will be tapped
                Animator animator = ViewAnimationUtils.createCircularReveal(mRevealedToolBar, x, y, startRadius, endRadius);
                animator.setInterpolator(new AccelerateDecelerateInterpolator());
                animator.setDuration(ANIMATION_DURATION);

                mRevealedToolBar.setVisibility(View.VISIBLE);
                animator.start();
                mIsHidden = false;


            } else {

                // dismiss secondary toolbar
                // performing circular reveal for reverse animation
                Animator animate = ViewAnimationUtils.createCircularReveal(mRevealedToolBar, x, y, reverseStartRadius, reverseEndRadius);
                animate.setInterpolator(new AccelerateDecelerateInterpolator());
                animate.setDuration(ANIMATION_DURATION);

                // to hide layout on animation end
                animate.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        mRevealedToolBar.setVisibility(View.INVISIBLE);
                        mIsHidden = true;
                    }
                });

                mRevealedToolBar.setVisibility(View.VISIBLE);
                animate.start();
            }
        }
    }
}

So in MainActivity, after having listen the onclick event of my ToggleButton, i trigger the animation of my second toolbar group (status view + toolbar) by using ViewAnimationUtils.createCircularReveal methods. First argument is the view to animate, followed by the start coordinate of the animation and followed by the radius.

In onClick method i also launch a reverse animation when i click on the arrow or a second time on my ToggleButton.

Finally it is a pretty simple solution even if we need to fake the status bar background.

Hope my solution could help someone.

François

Userful links:

  • Link1: Circular-Reveal-Animation project on GitHub
  • Link2: Create Circular Reveal Animation And Ripple Effect like Whatsapp
  • Link3: Simple Ripple + Reveal + Elevation tutorial


回答2:

You can use custom ripple library for such animation. It can be found here.

Then, to have the same effect, your toolbar will reside in rippleview, as given in the instructions of custom library. You need to have coordinates from where ripple effect should begin, for your case, this coordinate is the center of floating action button with plus symbol. Then, on item selection, run this ripple animation and fade in floating action button, you will have same effect.

Some similar examples can be seen on above mentioned library page.

Hope it helps !