70

I'm trying to create the reveal effect in my application but without success. What i want is reveal a cardview when i open a fragment. What i tried so far is:

private void toggleInformationView(View view) {
        infoContainer = view.findViewById(R.id.contact_card);

        int cx = (view.getLeft() + view.getRight()) / 2;
        int cy = (view.getTop() + view.getBottom()) / 2;
        float radius = Math.max(infoContainer.getWidth(), infoContainer.getHeight()) * 2.0f;

        if (infoContainer.getVisibility() == View.INVISIBLE) {
            infoContainer.setVisibility(View.VISIBLE);
            ViewAnimationUtils.createCircularReveal(infoContainer, cx, cy, 0, radius).start();
        } else {
            Animator reveal = ViewAnimationUtils.createCircularReveal(
                    infoContainer, cx, cy, radius, 0);
            reveal.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    infoContainer.setVisibility(View.INVISIBLE);
                }
            });
            reveal.start();
        }
    }

and in the onCreateView i started the method:

toggleInformationView(view);

this is the cardview:

<android.support.v7.widget.CardView
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:id="@+id/contact_card"
        android:visibility="invisible"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:elevation="4dp"
        android:foreground="?android:attr/selectableItemBackground"
        android:orientation="vertical"
        android:padding="10dp"
        card_view:cardCornerRadius="2dp" >

        <LinearLayout   
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            >
            <ImageView
                android:id="@+id/bg_contact"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layout_weight="0"
                android:adjustViewBounds="true"
                android:scaleType="centerCrop"
                android:src="@drawable/banner" />

        </LinearLayout>
    </android.support.v7.widget.CardView>

and the logcat:

11-08 17:25:48.541: E/AndroidRuntime(26925): java.lang.IllegalStateException: Cannot start this animator on a detached view!
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.view.RenderNode.addAnimator(RenderNode.java:817)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.view.RenderNodeAnimator.setTarget(RenderNodeAnimator.java:277)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.view.RenderNodeAnimator.setTarget(RenderNodeAnimator.java:261)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.animation.RevealAnimator.<init>(RevealAnimator.java:37)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.view.ViewAnimationUtils.createCircularReveal(ViewAnimationUtils.java:48)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at com.as.asapp.TFragment.toggleInformationView(TFragment.java:64)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at com.as.asapp.TFragmentshowInformation(TFragment.java:52)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at com.as.asapp.TFragment.onCreateView(TFragment.java:37)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.support.v4.app.Fragment.performCreateView(Fragment.java:1786)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:947)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1126)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:739)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1489)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:454)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.os.Handler.handleCallback(Handler.java:739)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.os.Handler.dispatchMessage(Handler.java:95)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.os.Looper.loop(Looper.java:135)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at android.app.ActivityThread.main(ActivityThread.java:5221)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at java.lang.reflect.Method.invoke(Native Method)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at java.lang.reflect.Method.invoke(Method.java:372)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
11-08 17:25:48.541: E/AndroidRuntime(26925):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

Checking, seems that getWidth() and getHeight() returns 0. But i don't know if it's the real problem. Thanks

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Atlas91
  • 5,754
  • 17
  • 69
  • 141

11 Answers11

78

This is how I solved it, I added a onLayoutChange listener to the view in onCreateView callback, so whenever it is attached to the view and ready to draw, it makes the reveal

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        // Inflate the layout for this fragment
        final View view = inflater.inflate(R.layout.fragment_map_list, container, false);
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    v.removeOnLayoutChangeListener(this);
                    toggleInformationView(view);
                }
            });
        }
        return view;
    }

EDIT: As some people posted, it is better to call toggleInformationView in onViewCreated and avoid the View.OnLayoutChangeListener

Fernando Gallego
  • 4,064
  • 31
  • 50
  • 1
    Well, it works! but i added a button and onClick i started the transition. But now it works perfectly!! One more question.. is it possible decide the animation time? it's too much fast to me right now.. – Atlas91 Nov 11 '14 at 11:18
  • 1
    you can modify your reveal Animator object and put reveal.setDuration(500); or any other duration in milliseconds – Fernando Gallego Nov 11 '14 at 11:29
  • worked for me too, thanks @ferdy182! Fixing this bug reminded me how terrible of an operating system Android is – Cat Feb 22 '15 at 06:18
  • 1
    it works, but what about API level lower than Lollipop? – Hendra Anggrian Aug 13 '15 at 08:33
  • @Anggrian that would require another approach like this library https://github.com/ozodrukh/CircularReveal – Fernando Gallego Aug 31 '15 at 13:05
  • This One Worked for me,hence an UPVOTE :) – iAviatorJose Oct 19 '15 at 11:14
  • I applied what you posted inside OnViewCreated() but the reveal animation doesn't work properly it seems to just pop out . I also added view.postDelayed() inside this method I call my circularReveal method. view.postDelayed() is call inside onLayoutChange(). – JustGotStared Mar 25 '22 at 16:35
50

You can use runnable to do the anim . The runnable will run after the view's creation .

view.post(new Runnable() {
            @Override
            public void run() {
                //create your anim here
            }
        });
Muhammed Refaat
  • 8,914
  • 14
  • 83
  • 118
BennyKok
  • 809
  • 10
  • 14
18

Small addition to Hitesh's answer. It's better to use https://developer.android.com/reference/android/support/v4/view/ViewCompat.html#isAttachedToWindow(android.view.View)

android.support.v4.view.ViewCompat

boolean isAttachedToWindow (View view)

Example:

LinearLayout rootView = null;
rootView = (LinearLayout) findViewById(R.id.rootView);
boolean isAttachedToWindow = ViewCompat.isAttachedToWindow(rootView);
Community
  • 1
  • 1
Aleksandr
  • 4,906
  • 4
  • 32
  • 47
7

How about simply placing the method in the onViewCreated() method:

@Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        toggleInformationView(view);
    }

it works

Ziv Kesten
  • 1,206
  • 25
  • 41
5

simplified outside of the scope of the question

private void startAnimation(@NonNull final View view, @NonNull final Runnable onAttachedToWindow) {
    if (view.isAttachedToWindow()) {
        onAttachedToWindow.run();
    } else {
        view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View view) {
                onAttachedToWindow.run();
                view.removeOnAttachStateChangeListener(this)
            }

            @Override
            public void onViewDetachedFromWindow(View view) {

            }
        });
    }
}

call startAnimation for one or many animations in mind

startAnimation(view, new Runnable() {
        @Override
        public void run() {
          //the code inside toggleInformationView(view)
        }
    });
Mark Hetherington
  • 1,612
  • 14
  • 24
Juan Mendez
  • 2,658
  • 1
  • 27
  • 23
4

ah !!!, so finally here is the solution for this error. Actually, the problem is that we are assigning a view to animation with its height and width, and suddenly we didn't notice that there may be a case, that view won't load properly that time when we are assigning it to animation. So for that, we have to wait until view properly loads.

LayoutInflater inflater = this.getLayoutInflater();
View rowView = inflater.inflate( R.layout.fileselecttype,null, true );

rowView   or [Your View]

[Your View].post(new Runnable() {
            @Override
            public void run() {
                //Do your animation work here.
            }
        });
Sohaib Aslam
  • 1,245
  • 17
  • 27
2

Try to call it on the onViewCreated method

Fernando Gallego
  • 4,064
  • 31
  • 50
1

I was using custom View so I used isAttachedToWindow() method to check if view is attached and avoid doing reveal while view is not attached.

Hitesh Sahu
  • 41,955
  • 17
  • 205
  • 154
1

Use a ViewTreeObserver.

 ViewTreeObserver viewTreeObserver = rootLayout.getViewTreeObserver();
    if (viewTreeObserver.isAlive()) {
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                revealActivity(revealX, revealY);
                rootLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    }
josedlujan
  • 5,357
  • 2
  • 27
  • 49
0

Check your Layout is attached to window or not by using rootLayout.isAttachedToWindow() method.

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        if (rootLayout!= null) {

            if(rootLayout.isAttachedToWindow()) {
                ViewTreeObserver viewTreeObserver = drawerLayout.getViewTreeObserver();
                if (viewTreeObserver.isAlive()) {
                    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                        @Override
                        public void onGlobalLayout() {
                            startRevealAnimation(rootLayout);
                            rootLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        }
                    });
                }
            }
        }
Rehan Sarwar
  • 994
  • 8
  • 20
0

looks like your view has not been attached yet. view.post() goes into the main thread queue and gets executed after the other pending tasks are finished and it means after your view gets attached so this will work for you:

view.post(new Runnable() {
        @Override
        public void run() {
            //do what you want
        }
    });