How to specify TransitionManager.beginDelayedTransition to affect only direct child views
Asked Answered
G

2

7

I am trying to use a ConstraintLayout in conjunction with views that slide up and down with animation. These views are organized vertically, with a RecyclerView at the top and two other views stacked under it:

<constraint layout container>
    [                    ]
    [    recycler view   ] 
    [                    ]
    [48dp height 1st view]
    [48dp height 2nd view]
</constraint layout container>

The animation is very simple: When a button is tapped the 1st view moves from the bottom of the container to the position you can see above, when tapped again it moves down and stays overlapped on the 2nd view. When this happens the RecyclerView at the top changes height since it is constrained to that 1st view, otherwise it would leave an empty space.

So far so good and the animation is working well, but the problem arises when the user taps the button too fast, leading to the following error:

    java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

After investigating what was going on I discovered that this problem is a consequence of the TransitionManager that I am using to animate the changes. What I discovered was interesting, when the button was tapped to move the 1st view up (or down, just imagine the opposite steps) the manager was doing the following:

  • Fade out the first/last view from the RecyclerView (depends on whether or not you have a reversed layout or stack from end enabled)
  • Remove that view from the RecyclerView
  • Decrease the height of the RecyclerView
  • Move the 1st view up

Even though I specified in all the places I could imagine that the RecyclerView should not animate item changes, the TransitionManager was overriding it and that meant there is a window when the same view that is being about to be removed from the RecyclerView can be added again to the same parent, and that window is when the view is fading out by the manager.

Since the fade animation is not that fast, a user can very easily tap the button twice during that time, causing the manager to re-add the fading view, which is already in the RecyclerView and thus crashing the app while throwing the above error.

Since this is an issue caused inside the RecyclerView I would be perfectly fine with no animations inside it and for that reason I would like to know how can I specify in the TransitionManager.beginDelayedTransition to only animate the direct child views of the ConstraintLayout, this way it will not animate the views inside the RecyclerView.

The related documentation does not shine anything over this, so I present here this question; How can I restrict the transition to direct child views?

In case it might be helpful I include here the snippet of the transition manager

    final ConstraintSet constraintSet;
    final ConstraintLayout constraintLayout;

    constraintSet = new ConstraintSet();
    constraintLayout = (ConstraintLayout) viewParent;

    TransitionManager.beginDelayedTransition(constraintLayout);

    constraintSet.clone(constraintLayout);
    constraintSet.connect(startId, startSide, endId, endSide, margin);
    constraintSet.applyTo(constraintLayout);
Groome answered 21/5, 2018 at 22:21 Comment(4)
use two param version of TransitionManager.beginDelayedTransition and pass a Transition that excludes some of your viewsSnashall
@Snashall TransitionSet has a method that allows child views to be ignored, thanks. Can you post the answer so that I can accept it?Groome
Transition has that methods: Transition#excludeTarget / Transition#excludeChildren / Transition#removeTargetSnashall
@Snashall I know, it was thanks to your comment that I found it, that is why I asked you to post it as an answer for me to accept it since comments are not allowed to be accepted as answers.Groome
G
19

pskink posted the answer in the comments, but after asking twice for him to post as an answer for me to accept and never doing it, I'll post it instead and mark this as answered.

The TransitionManager had a method excludeChildren that works perfectly for what I was looking for. You can use by class types, children of a specific view and so on.

EDIT: Adding example requested

    AutoTransition autoTransition = new AutoTransition();
    autoTransition.excludeChildren(R.id.recyclerView, true);
    TransitionManager.beginDelayedTransition(constraintLayout, autoTransition);

With constraintLayout as the parent of the target view and R.id.recyclerView being a child view of that parent that I don't want to be animated, in this specific example that is. You can also exclude all children that are of a specific class (EditText for example) and so on.

Groome answered 26/5, 2018 at 11:17 Comment(5)
Thanks for the example. But as I said, using AutoTransition changed the default animation that ConstraintLayout was generating without it, so I can't use it.Bioecology
@KashishMalhotra Sorry about that, but that's the code I am using for the issue I was having. If it doesn't work for your case it would be better if you posted a new question related to this one so someone else can try to help you out.Groome
it worked. I was doing a very silly mistake. After debugging the code, your solution worked nicely. Thanks!Bioecology
Awesome & thanks. It is also related here: https://mcmap.net/q/119321/-error-using-the-recyclerview-the-specified-child-already-has-a-parent So I referenced this answer as wellReligious
In my case excludeChildren not worked, I used excludeTarget insteadMonde
P
9

I fixed it this way:

TransitionManager.beginDelayedTransition(
                        constraintLayout,
                        TransitionSet().apply {
                            ordering = TransitionSet.ORDERING_SEQUENTIAL
                            addTransition(ChangeBounds())
                            addTransition(Fade(Fade.IN))
                        }
                    )

it's pretty much the same code inside of AutoTransition which is the default one if we don't pass a custom transition, but I removed the addTransition(Fade(Fade.OUT)) that seems to be the cause of the crash. And the animations look very similar to the original at least for me.

Priam answered 15/11, 2018 at 17:26 Comment(1)
Thanks a lot. TransitionSet.ORDERING_TOGETHER did the trick for animating the overlay widgets on my google map when the map camera is moved or is idle.Poniard

© 2022 - 2024 — McMap. All rights reserved.