Cannot start this animator on a detached view! reveal effect

JavaAndroidAndroid Animation

Java Problem Overview


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

Java Solutions


Solution 1 - Java

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

Solution 2 - Java

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
            }
        });

Solution 3 - Java

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);

Solution 4 - Java

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

Solution 5 - Java

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)
        }
    });

Solution 6 - Java

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.
            }
        });

Solution 7 - Java

Try to call it on the onViewCreated method

Solution 8 - Java

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.

Solution 9 - Java

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);
            }
        });
    }

Solution 10 - Java

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);
                        }
                    });
                }
            }
        }

Solution 11 - Java

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
        }
    });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAtlas91View Question on Stackoverflow
Solution 1 - JavaFernando GallegoView Answer on Stackoverflow
Solution 2 - JavaBennyKokView Answer on Stackoverflow
Solution 3 - JavaAleksandrView Answer on Stackoverflow
Solution 4 - JavaZiv KestenView Answer on Stackoverflow
Solution 5 - JavaJuan MendezView Answer on Stackoverflow
Solution 6 - JavaSohaib AslamView Answer on Stackoverflow
Solution 7 - JavaFernando GallegoView Answer on Stackoverflow
Solution 8 - JavaHitesh SahuView Answer on Stackoverflow
Solution 9 - JavajosedlujanView Answer on Stackoverflow
Solution 10 - JavaRehan SarwarView Answer on Stackoverflow
Solution 11 - JavaAfshin SamieiView Answer on Stackoverflow