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.
4 Answers
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.

- 6,664
- 3
- 31
- 32
-
Something simlar on API 10? – Gelldur Feb 25 '15 at 12:54
Instead of registering a new listener, you can override onDetachedFromWindow
in your custom View
code.

- 27,641
- 11
- 107
- 150
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()
!

- 594
- 5
- 16
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.

- 3,734
- 2
- 24
- 21
-
This is not ideal. OnViewDetach is called every time view is removed from a Window, not the parent. So in a case of ViewPager switching between tabs will call doOnDetach on all components inside, therefore recycling their images. Those images will be on return to the old tab empty and handling doOnAttach would be needed. But even if you had recycling in doOnDetach and reinitializing in doOnAttach, in cases of Glide and large images it would waste RAM and even network. – Jakub Kostka Dec 10 '21 at 11:05