Jacksonkr
Jacksonkr

Reputation: 32247

android on view removed from parent

on a child layout (View) is there a callback for when the view is removed from it's parent? I need to recycle some images when the view is done. I've been looking around on the web for what to do, but haven't found anything helpful yet.

Upvotes: 8

Views: 8377

Answers (4)

davidfrancis
davidfrancis

Reputation: 3849

The Android KTX (Core KTX) library gives you a nice solution for this.

You'll need this dependency: androidx.core:core-ktx:1.3.0

You can then call a function "doOnDetach" to signal you want to run some code (once) when the view is removed from the window:

fun myInitCode() {
    ...
    myView.doOnDetach(this::doOnMyViewDetachFromWindow)
    ...
}

fun doOnMyViewDetachFromWindow(view: View) {
    ... put your image cleanup code here ...
}

You can pass a lambda to "doOnDetach" but a method reference as shown above may be cleaner, depending on how much work you have to do.

The description of doOnDetach is as follows:

androidx.core.view ViewKt.class public inline fun View.doOnDetach( crossinline action: (View) → Unit ): Unit

Performs the given action when this view is detached from a window. If the view is not attached to a window the action will be performed immediately, otherwise the action will be performed after the view is detached from its current window. The action will only be invoked once, and any listeners will then be removed.

Upvotes: 0

matheszabi
matheszabi

Reputation: 624

I fall in that trap what marmor said:)

@Override
protected void onDetachedFromWindow() { I want to do something here, sometimes called sometimes not!!}

protected void onAttachedToWindow() {It is working fine, always}

This code is in a CustomView.

The calling code is:

    contentHolder.removeAllViews();
    // ... init my  CustomView   ...
    contentHolder.addView(myCustomView);
    contentHolder.requestLayout();// useless, not need
    contentHolder.invalidate();// useless, not need

To understand why is not working you have to go inside Android API:

public void removeAllViews() {
    removeAllViewsInLayout();
    requestLayout();
    invalidate(true);
}

public void removeAllViewsInLayout() {
    final int count = mChildrenCount;
    if (count <= 0) {
        return;
    }

    final View[] children = mChildren;
    mChildrenCount = 0;

    final View focused = mFocused;
    final boolean detach = mAttachInfo != null;
    boolean clearChildFocus = false;

    needGlobalAttributesUpdate(false);

    for (int i = count - 1; i >= 0; i--) {
        final View view = children[i];

        if (mTransition != null) {
            mTransition.removeChild(this, view);
        }

        if (view == focused) {
            view.unFocus(null);
            clearChildFocus = true;
        }

        view.clearAccessibilityFocus();

        cancelTouchTarget(view);
        cancelHoverTarget(view);

        if (view.getAnimation() != null ||
                (mTransitioningViews != null && mTransitioningViews.contains(view))) {
            addDisappearingView(view);
        } else if (detach) {
           view.dispatchDetachedFromWindow();
        }

        if (view.hasTransientState()) {
            childHasTransientStateChanged(view, false);
        }

        dispatchViewRemoved(view);

        view.mParent = null;
        children[i] = null;
    }

    if (clearChildFocus) {
        clearChildFocus(focused);
        if (!rootViewRequestFocus()) {
            notifyGlobalFocusCleared(focused);
        }
    }
}

The key is here:

        if (view.getAnimation() != null ||
                (mTransitioningViews != null && mTransitioningViews.contains(view))) {

So, if you have animation ( and in 1 case I have and in 9 cases not) it will not called the onDetachedFromWindow() and it will mess the whole UI :)

public void endViewTransition(View view) {
    if (mTransitioningViews != null) {
        mTransitioningViews.remove(view);
        final ArrayList<View> disappearingChildren = mDisappearingChildren;
        if (disappearingChildren != null && disappearingChildren.contains(view)) {
            disappearingChildren.remove(view);
            if (mVisibilityChangingChildren != null &&
                    mVisibilityChangingChildren.contains(view)) {
                mVisibilityChangingChildren.remove(view);
            } else {
                if (view.mAttachInfo != null) {
                    view.dispatchDetachedFromWindow();
                }
                if (view.mParent != null) {
                    view.mParent = null;
                }
            }
            invalidate();
        }
    }
}

Again in some cases will be called even with animation. addDisappearingView(view);

The accepted answer suggest something like this:

    addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
        @Override
        public void onViewAttachedToWindow(View v) {
        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            System.out.println("MyCustomView.onViewDetachedFromWindow");
        }
    });

Sadly on animation will not print the desired text.

Some important code from android.view.ViewGroup API:

void dispatchViewRemoved(View child) {
    onViewRemoved(child);
    if (mOnHierarchyChangeListener != null) {
        mOnHierarchyChangeListener.onChildViewRemoved(this, child);
    }
}

public void onViewRemoved(View child) {
}

So, you can override your RelativeLayout for this method. My animation is an infinite animation, and it will not be called very soon any of the methods!

If you have an infinite animation the correct way is to write this code, when you call remove all views:

    if(contentHolder.getChildCount() > 0 ){
        View child0 = contentHolder.getChildAt(0);
        Animation animation = child0.getAnimation();
        if(animation != null) {
            animation.cancel();
            child0.clearAnimation();
        }
    }
    contentHolder.removeAllViews();

Now it will be called the protected void onDetachedFromWindow()!

Upvotes: 4

marmor
marmor

Reputation: 28229

Instead of registering a new listener, you can override onDetachedFromWindow in your custom View code.

Upvotes: 5

nmw
nmw

Reputation: 6764

I've been looking for something like this too. The best I can find is View.OnAttachStateChangeListener. I doubt it's ideal, as it's the callback for when the View is added & removed from the Window - not the parent, but it's sufficient for my needs.

Upvotes: 11

Related Questions