Reputation: 1217
I'm having an unusual error. I have this inside a custom viewgroup. The method receives a view and add it to the layout but i keep getting the same error:
if((ViewGroup)view.getParent() != null){
((ViewGroup)view.getParent()).removeView(view);
}
addView(view); <--- Breakpoints puts the error on this line
The error is:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
Using breakpoints around this shows that "view" even after calling removeView onthe parent keep a reference to its parent..
Some people proposed using a runnable to wait a few seconds before adding it to the view. I havent tried this because it seems more a hack than a solution.. Either way i hope someone may be able to help
Thanks!
PS: Not that it should matter but the parent of this view i'm adding is a custom grid layout i made and its parent is a viewpager.
Edit:
I did a little more breakpoints and debugging and from the looks of it the grid effectively remove the view from its child list (debug) but the child view keeps a reference to that same grid in its mParent variable (debug). How is this possible
EDIT:
In activity:
Button button = new Button(mContext);
button.setOnClickListener(mClickListener);
(...)
Random random = new Random();
button.setText(random.nextInt(9999) + " ");
mCurrentGridLayout.addCustomView(button);
In CustomGridLayout viewgroup class:
public void addCustomView(View view){
if((ViewGroup)view.getParent() != null){
((ViewGroup)view.getParent()).removeView(view);
}
addView(view);
}
Upvotes: 10
Views: 9647
Reputation: 4897
In case you have to do with viewGroups
that have layoutTransition
or not you can do something like this:
/**
* When we don't have [LayoutTransition] onEnd is called directly
* Or
* function [onEnd] will be called on endTransition and when
* the parent is the same as container and view parent is null,
* and will remove also the transition listener
*/
private fun doOnParentRemoved(parent: ViewGroup, onEnd: () -> Unit) {
val layoutTransition = parent.layoutTransition
if (layoutTransition == null) {
onEnd.invoke()
return
}
val weakListener = WeakReference(onEnd)
layoutTransition.addTransitionListener(object : LayoutTransition.TransitionListener {
override fun startTransition(
transition: LayoutTransition?,
container: ViewGroup?,
view: View?,
transitionType: Int
) {
}
override fun endTransition(
transition: LayoutTransition?,
container: ViewGroup?,
view: View?,
transitionType: Int
) {
transition?.removeTransitionListener(this)
weakListener.get()?.invoke()
}
})
}
This is how you can use it:
sourceLayout.removeView(textView)
doOnParentRemoved(sourceLayout) {
// do your stuff when view has no parent
// add more logic to check is your view who called it in case of multiple views
}
I would suggest to double check your implementation as it can happen sometimes endTransition
is not guaranteed to be called or animations can stop in middle. In my case I have used it in drag drop actions
Upvotes: 0
Reputation: 1912
I had the same problem, and the answer from Iree really helped me. The cause was the animation for the layout transition, but if i set it its value null i will lose my transition animation. So what i did is add a layout transition listener, that way you can listener when the transition is done, and then add the view to its new parent.
Using Java
LayoutTransition layoutTransition = ((ViewGroup)view.getParent()).getLayoutTransition();
layoutTransition.addTransitionListener(new TransitionListener(){
@Override
public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
}
@Override
public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
// now you can add the same view to another parent
addView(view);
}
});
Using Kotlin
val layoutTransition = (view.parent as ViewGroup).layoutTransition
layoutTransition.addTransitionListener(object : LayoutTransition.TransitionListener {
override fun startTransition(transition: LayoutTransition?,container: ViewGroup?,view: View?,transitionType: Int) {
}
override fun endTransition(transition: LayoutTransition?,container: ViewGroup?,view: View?,transitionType: Int) {
// now you can add the same view to another parent
addView(view)
}
})
Upvotes: 0
Reputation: 358
I had that same issue when trying to create a custom banner. I believe it's because of animation during layout, that's why a delay could work. In my case, I made a custom viewgroup class to eliminate the animation delay:
private class BannerLayout extends LinearLayout {
public BannerLayout(Context context) {
super(context);
}
@Override
protected void removeDetachedView(View child, boolean animate) {
super.removeDetachedView(child, false);
}
}
Once I did this, everything worked as expected.
Hope it helps!
Upvotes: 13