8

I add and remove a view dynamically to a custom view (FrameLayout) doing this:

LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mMyView = inflater.inflate(resId, this, true);

Later on I try to remove the view but the view won't remove:

removeView(mMyView);

If I do this instead everything works as expected:

mMyView = inflater.inflate(resId, this, **false**);
addView(mMyView);

The only difference is that I manually add the view instead of letting the inflate call do it. Does anyone know why this would make a difference?

Emanuel Moecklin
  • 28,488
  • 11
  • 69
  • 85

4 Answers4

19

The answer lies in this call

mMyView = inflater.inflate(resId, this, true);

The documentation states:

Returns The root View of the inflated hierarchy. If root was supplied and attachToRoot is true, this is root; otherwise it is the root of the inflated XML file.

http://developer.android.com/reference/android/view/LayoutInflater.html#inflate(int, android.view.ViewGroup, boolean)

I was assuming the inflate call always returns the root of the inflated layout (defined by resId) but if the root parameter is provided and attachToRoot is true then it's the root of the layout the inflated layout was added to (it's new parent view) and that's root (in this case root == this).

So while

mMyView = inflater.inflate(resId, this, false);

assigns the view to mMyView that we want to remove later

mMyView = inflater.inflate(resId, this, true);

assigns the parent of that view. The call removeView(mMyView) is actually identical to removeView(this) and that obviously doesn't do anything.

This behavior is IMO very counter-intuitive and prone to errors. Why should it return the root layout instead of the inflated layout when we already have that root layout with the root parameter while we don't have a reference to the inflated layout yet?

Emanuel Moecklin
  • 28,488
  • 11
  • 69
  • 85
  • 1
    4 years later and the same stupid Android API decisions are still tripping people up. Thanks for this answer. – Isaac Supeene Oct 16 '17 at 06:08
  • Thanks for the reminder :) I'd learned this a while back after careful reading of the documentation, but came across this problem recently without remembering the cause. You saved me some time thanks to your post! – MacD May 09 '18 at 03:09
3

It seems like in newer Android versions, the layout transitions may delay removing views, either delay the call to addView() or use parent.setLayoutTransition(null).

Steelight
  • 3,347
  • 1
  • 20
  • 17
2

In my experience, it depends on where do you create your reference to the parent view which you pass into inflate(...) call. I've been in your situation and (even though it shouldn't fail, it does sometimes if we don't call it from the right place) I've proven that it's much better when creating a custom view in code, to set the LayoutParams programatically as well. From the documentation you can deduce that the parent ViewGroup is mainly used to get the layout parameters. For that reason, after you inflate (with 'false'), you can build your own FrameLayout.LayoutParams, provide the inflated view with them, and then adding the view to the parent. You may wanna try also instead of removing the view directly from your custom layout, calling View.getParent(), casting it to your custom FrameLayout, and then calling removeView(...) from the casted result.

   ((MyCustomFrameLayout)mMyView.getParent()).removeView(mMyView); 

For debugging purposes (when you use 'true'), you can check if the resulting parent of the inflated view (and the child itself) are the same as the one that get passed to the original calls. Maybe they are getting changed (or the reference is lost) at some point in the code.

Hope it works for you.

Junior Buckeridge
  • 2,075
  • 2
  • 21
  • 27
  • Whether to provide LayoutParams explicitely or using those provided by the root layout isn't relevant to the question. Furthermore using getParent() would just return the custom FrameLayout which is the same as "this" and doing ((MyCustomFrameLayout)mMyView.getParent()).removeView(mMyView) is identical to removeView(mMyView), at least if mMyView is actually added to the custom FrameLayout (which it isn't but for reasons you don't explain in your answer). – Emanuel Moecklin Dec 06 '13 at 23:13
1

If you are using list view, have you tried invalidating it after removing.

ListView.invalidate()
Piyush
  • 2,040
  • 16
  • 14
  • Adding the view happens in the getView() method of a list adapter so while creating the view to display. Invalidating the list view would probably cause an infinity loop... – Emanuel Moecklin Dec 06 '13 at 19:28
  • are you putting the removeView in getView() ? – Piyush Dec 06 '13 at 19:30
  • OK I see what you mean. The view is removed when a button is clicked in the list item. I added an invalidate after I remove the view but nothing changes. – Emanuel Moecklin Dec 06 '13 at 19:35