33

On the android material design principles page, one of the examples shows a FAB expanding into a new full screen. (Under "Full Screen")

http://www.google.com/design/spec/components/buttons-floating-action-button.html#buttons-floating-action-button-transitions

I've tried to implement the same effect in my app, but with little success.
I managed to create a FAB that expands into a view using this code as reference: https://gist.github.com/chris95x8/882b5c5d0aa2096236ba.

It worked, but I was wondering whether I could apply the same effect to an activity transition. I've tried looking it up and playing with it myself but could not find anything that might work.

I know I could make the FAB expand into a Fragment and not a whole new activity, but I'm not sure if that's what being done, and whether that's optimal or not.

And so my question is, is there a way to implement the fab-expanding reveal effect as an activity transition, or is it supposed to just reveal a new fragment?

Asaf
  • 2,005
  • 7
  • 37
  • 59
  • 1
    What you link to in the material design guidelines is definitely an `Activity` transition. Using a `Fragment` there makes no sense at all. – Xaver Kapeller Jul 25 '15 at 10:20
  • I downloaded your code from Github. I don't see any code related to Floating Action Button. Where is it. I think you should post the relevant code instead of having people download them (it's a hassle). And with few/some explanations. – The Original Android Aug 04 '15 at 02:51
  • To answer your question, as far as I know, Floating Action Button is not related to specific fragment or activity. Would you like sample project on it? – The Original Android Aug 04 '15 at 02:53
  • @TheOriginalAndroid Thanks for commenting! This is not my code, but a code I used as a reference, as I stated. What I'm looking for is to use the same animation that's done with a view in the github link I posted, only as an activity transition. I could not find such example, and my knowledge of android is not enough to achieve that effect. – Asaf Aug 04 '15 at 19:51
  • Sorry to say, the code reference is not helpful but misleading to readers. The Google's webpage and animation is sufficient. However I don't know what your existing code is like, if any. Anyway I posted an answer to start with. And hopefully we can progress quickly on this due to time constraints. – The Original Android Aug 04 '15 at 20:32
  • I may implement "transitioning" in the near future and want to assist in this effort. But there is a lack of response from the author for me to be interested in posting another answer. Perhaps the author already has a solution in mind. – The Original Android Aug 06 '15 at 17:01

4 Answers4

32

I am developing an app which expands a FloatingActionButton into a new Activity. I'm not sure that if you like my implementation, but please see pictures at first:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

So the first picture shows MainActivity and the last one shows SecondActivity, which is "expanded" from FAB.

Now, I want to mention that I'm not actually expanding a FAB into a new Activity but I can let user feel that the new page is expanded from that FAB, and I think that's enough for both developers and users.

Here's implementation:

Preparation:

  1. A FloatingActionButton of course,
  2. Visit https://github.com/kyze8439690/RevealLayout and import this library to your project. It is used to play reveal animation. It has a custom BakedBezierInterpolator to control reveal animation and make it material-styled.

Steps:

  1. create activity_main.xml like this:

    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <!--Your main content here-->
    
        <RevealLayout
            android:id="@+id/reveal_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="invisible">
    
            <View
                android:id="@+id/reveal_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="invisible"/>
    
        </RevealLayout>
    
    </FrameLayout>
    
  2. find Views:

    mRevealLayout = (RevealLayout) findViewById(R.id.reveal_layout);
    mRevealView = findViewById(R.id.reveal_view);
    
  3. expand when user clicks FAB:

    mFab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mFab.setClickable(false); // Avoid naughty guys clicking FAB again and again...
            int[] location = new int[2];
            mFab.getLocationOnScreen(location);
            location[0] += mFab.getWidth() / 2;
            location[1] += mFab.getHeight() / 2;
    
            final Intent intent = new Intent(MainActivity.this, SecondActivity.class);
    
            mRevealView.setVisibility(View.VISIBLE);
            mRevealLayout.setVisibility(View.VISIBLE);
    
            mRevealLayout.show(location[0], location[1]); // Expand from center of FAB. Actually, it just plays reveal animation.
            mFab.postDelayed(new Runnable() {
                @Override
                public void run() {
                    startActivity(intent);
                    /**
                     * Without using R.anim.hold, the screen will flash because of transition
                     * of Activities.
                     */
                    overridePendingTransition(0, R.anim.hold);
                }
            }, 600); // 600 is default duration of reveal animation in RevealLayout
            mFab.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mFab.setClickable(true);
                    mRevealLayout.setVisibility(View.INVISIBLE);
                    mViewToReveal.setVisibility(View.INVISIBLE);
                }
            }, 960); // Or some numbers larger than 600.
        }
    });
    

    And here is hold.xml in res/anim:

    <set
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:shareInterpolator="false">
    
        <translate
            android:duration="960" <!-- Enough-large time is OK -->
            android:fromXDelta="0%"
            android:fromYDelta="0%"
            android:toXDelta="0%"
            android:toYDelta="0%"/>
    
    </set>
    

That's all.

Improvements:

  1. RevealLayout has a bug(plays rectangular instead of circular reveal animation) for devices under API 17(Android 4.2), you can add these lines in constructor of it:

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }
    
  2. If your SecondActivity contains complicated contents, a simple View used as reveal_view in the layout.xml isn't enough/perfect. You can include the second layout inside the RevealLayout reveal_layout. It seems wasteful and hard to control if the second layout won't appear same at every time. But for me, it will. So you can make other improvements if you should.

  3. If you want to implement totally same animation shown in Material Design Guide, you can set layout_height of the RevealLayout into a specific number instead of match_parent. After expanding animation ends(or some time after the animation plays, which should make the whole process of animation smoothly), then you can animate translationY. The important point is, just cheat users visually by controlling animation duration.

Finally, this is my own experience/attempt and I'm a beginner in developing Android apps. If there are any mistakes/further improvements, please leave comments/edit my answer. Thank you.

Sathish Kumar
  • 919
  • 1
  • 13
  • 27
ywwynm
  • 11,573
  • 7
  • 37
  • 53
  • 1
    This is an interesting library and you do good work on your detailed post. However implementing this seems a lot of work especially when compared to using FragmentTransactions and the animation methods. – The Original Android Aug 07 '15 at 06:55
  • 3
    @The Original Android Yes I agree. However, when you choose to use fragment to expand into, it's hard to let you expand animation full-screen. You can only animate changes under status bar and above Navigation Bar. Maybe you can set style to windowTranslucentStatus/Navigation so that every animation can appear truly full-screen, but it will cause other problems like "adjustResize won't work", which may be unsolvable. On the other hand, the question is "FAB expand into an Activity"----author did say that he wants to expand into an Activity instead of a Fragment and that's his requirement. – ywwynm Aug 07 '15 at 07:23
  • @ywwynm Thank you for your post, I've considered implementing the effect in the same manner you did, but as you too said, it's kind of a dirty way to do it, which I find very disappointing. I've been searching for apps that might have implemented the effect as an activity transition but could not find any, and hoped there would be a clean SDK-ish way to do it. Unfortunately I guess either doing what you did, or otherwise using a single activity with fragments is the way to go. Thanks for taking your time to answer! – Asaf Aug 10 '15 at 20:27
  • So sorry that RevealLayout has the hardware acceleration bugs, because I test lower api version on Genymotion and it seems works. I will fix it soon. – Yugy Feb 25 '16 at 02:31
  • 1
    @yugy Thank you for your simple but gorgeous library. I didn't care so much for open source world when I answered the question so I forgot to commit an issue to you. Sorry for that. – ywwynm Feb 25 '16 at 05:04
  • @Asaf take a look at my answer. It doesn't use fragments and the effect is handled by the second activity. – Bronx Aug 18 '16 at 07:51
  • the mRevealLayout.show(...) line says .show(int, int) isn't a method. Anybody else? – Kevin Dixson Sep 29 '16 at 18:01
6

I made a custom activity, based on this question Circular reveal transition for new activity , that handle the CircularRevealAnimation and his reverse effect when the activity finish:

public class RevealActivity extends AppCompatActivity {

private View revealView;

public static final String REVEAL_X="REVEAL_X";
public static final String REVEAL_Y="REVEAL_Y";

public void showRevealEffect(Bundle savedInstanceState, final View rootView) {

    revealView=rootView;

    if (savedInstanceState == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        rootView.setVisibility(View.INVISIBLE);

        ViewTreeObserver viewTreeObserver = rootView.getViewTreeObserver();

        if(viewTreeObserver.isAlive()) {
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {

                    circularRevealActivity(rootView);

                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                        rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    } else {
                        rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    }

                }
            });
        }

    }
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void circularRevealActivity(View rootView) {

    int cx = getIntent().getIntExtra(REVEAL_X, 0);
    int cy = getIntent().getIntExtra(REVEAL_Y, 0);

    float finalRadius = Math.max(rootView.getWidth(), rootView.getHeight());

    // create the animator for this view (the start radius is zero)
    Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootView, cx, cy, 0, finalRadius);
    circularReveal.setDuration(400);

    // make the view visible and start the animation
    rootView.setVisibility(View.VISIBLE);
    circularReveal.start();
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {

case android.R.id.home: onBackPressed();break;
return super.onOptionsItemSelected(item);

}    

}

@Override
public void onBackPressed() {
    destroyActivity(revealView);
}

private void destroyActivity(View rootView) {
    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP)
        destroyCircularRevealActivity(rootView);
    else
        finish();
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void destroyCircularRevealActivity(final View rootView) {
    int cx = getIntent().getIntExtra(REVEAL_X, 0);
    int cy = getIntent().getIntExtra(REVEAL_Y, 0);

    float finalRadius = Math.max(rootView.getWidth(), rootView.getHeight());

    // create the animator for this view (the start radius is zero)
    Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootView, cx, cy, finalRadius, 0);
    circularReveal.setDuration(400);

    circularReveal.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animator) {

        }

        @Override
        public void onAnimationEnd(Animator animator) {
            rootView.setVisibility(View.INVISIBLE);
            finishAfterTransition();
        }

        @Override
        public void onAnimationCancel(Animator animator) {

        }

        @Override
        public void onAnimationRepeat(Animator animator) {

        }
    });

    // make the view visible and start the animation
    rootView.setVisibility(View.VISIBLE);
    circularReveal.start();
}
}

You can extend this with your own activity and call in your onCreate the method 'showRevealEffect' like this:

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

    //your code

    View root= findViewById(R.id.your_root_id);
    showRevealEffect(savedInstanceState, root);

}

You also have to use a transparent theme like this one:

<style name="Theme.Transparent" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="colorControlNormal">@android:color/white</item>
</style>

In the end, to launch this activity you should pass via extra the coordinates where the animation should start:

int[] location = new int[2];

    fab.getLocationOnScreen(location);

    Intent intent = new Intent(this, YourRevealActivity.class);

    intent.putExtra(SearchActivity.REVEAL_X, location[0]);
    intent.putExtra(SearchActivity.REVEAL_Y, location[1]);

    startActivity(intent);
Community
  • 1
  • 1
Bronx
  • 4,480
  • 4
  • 34
  • 44
0

you can use this lib [https://github.com/sergiocasero/RevealFAB][1]

[1]: https://github.com/sergiocasero/RevealFAB 3rd party its easy and simple to use

Add to your layout

<RelativeLayout...>

    <android.support.design.widget.CoordinatorLayout...>
        <!-- YOUR CONTENT -->
    </android.support.design.widget.CoordinatorLayout>

    <com.sergiocasero.revealfab.RevealFAB
        android:id="@+id/reveal_fab"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:fab_color="@color/colorAccent"
        app:fab_icon="@drawable/ic_add_white_24dp"
        app:reveal_color="@color/colorAccent" />
</RelativeLayout>

Important: This component goes above your content. You can use Coordinator, LinearLayout... or another Relative layout if you want :)

As you can see, you have 3 custom attributes for customizing colors and icon

Setting information about intent:

revealFAB = (RevealFAB) findViewById(R.id.reveal_fab);
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
revealFAB.setIntent(intent);

revealFAB.setOnClickListener(new RevealFAB.OnClickListener() {
    @Override
    public void onClick(RevealFAB button, View v) {
        button.startActivityWithAnimation();
    }
});

Don't forget call onResume() method!

@Override
protected void onResume() {
    super.onResume();
    revealFAB.onResume();
}
Samuel Moshie
  • 570
  • 1
  • 5
  • 20
-1

Someone investigated the implementation of transition between activities from Plaid. Her example were published via https://github.com/hujiaweibujidao/FabDialogMorph.

Briefly speaking, she transits two activities with:

  1. The FAB as the shared element.
  2. The layout in the target activity with the same android:transitionName as the FAB.
  3. To smooth the animation, MorphDrawable (extended from Drawable) and MorphTransition (extended from ChangeBounds) are implemented and applied.
Ting Yi Shih
  • 792
  • 2
  • 9
  • 19