view.animate() doesn't work the first time, but works subsequent times

Viewed 1741

Summary:

A view starts with visibility = 'GONE'. When the view is set to visible, I want everything below it to move down by the height of that view. It doesn't work the first time, but works every time after that.

Explanation:

I'm using a radio group that has three options for delivery.

If the user clicks the third option, there's a second radio group that shows up with more options.

The second radio group is only visible when the third option of the first Radio Group is selected. When it becomes visible I want everything below it to slide down to make room. I've added view.animate().translationY() calls for each view below the two radio groups. This works to move everything out of the way when the second radio group shows up, and slide them back when its gone. The problem is that this only works the second time around. The first time the radio group appears, the views all just stay in place.

Attempts:

  • SystemClock -- I've tried adding a SystemClock.sleep(1500) call. This just adds a delay to the animation, but it still doesn't work the first time.

  • Handler -- I've tried adding a Handler to handle the animation views, same problem.

  • Start Delay -- I've tried adding a Start Delay as mentioned in this stack-overflow answer and here this one

  • Hard Coding the height -- I've tried hard-coding the distance to move down. I thought that the problem might be that I'm getting the height toMove by calling getHeight() on the radio group after its set to visible. If this was the problem, I though hard coding the height might work, but the same issue persists.

  • Vertical Chain -- I tried attaching all the views in a vertical chain, and adding the radio group to the chain when it becomes visible. The radio group couldn't be included in the chain when visibility was 'GONE', adding it to the chain did nothing. If I added it to the Constraint Layout after the visibility is set to 'VISIBLE', the app crashes.

Screenshots:

This is what happens the first time I click on the radio button: screenshot: radio group shows up, views don't slide down

This is what happens when I click away from the last radio button: screenshot: radio group 2 goes away

This is what happens when I choose "Standard Delivery" after that, and what I want to happen the first time too: screenshot: radio group 2 is set to visible, the other views slide down

Code:

GitHub: This is the whole app GitHub: The specific java file -- radio group switch starts on 109

Snipit:

  --- Switch Statement ---
       ...
       ...  
    case R.id.standard_delivery_radio:

        delivery = STD_DELIVERY;
        deliveryType = "Standard Delivery";
        stdDeliveryGroup.setVisibility(View.VISIBLE);

        break;
     }

 // Move the views down
     if (stdDeliveryGroup.getVisibility() == View.VISIBLE) {
          toMove = stdDeliveryGroup.getHeight(); }

 // Move the views back to starting position
     if (stdDeliveryGroup.getVisibility() == View.GONE) {
          toMove = 0;}

 // Animate the Views
     shippingNumber.animate().translationY(toMove);
     shippingText.animate().translationY(toMove);
     orderButton.animate().translationY(toMove);
     totalText.animate().translationY(toMove);
     totalNumber.animate().translationY(toMove);

Notes:

  • This is happening inside a fragment.
  • The code is inside a switch statement inside the onCreateView method.
  • The fragment is inside the main activity, which has a view pager with tabs.
  • If you want to find the code in the file, its under java/com/example/OrderScreenFragment.java

  • I really have looked around and can't find why this is happening, please don't mark as duplicate or vague. If you need anything clarified, I will be happy to clarify.

2 Answers

Updated When the layout for the radio group that is marked as gone first appears, the height of the radio group has been measured as zero since gone widgets don't have height (or, maybe just not measured since it is gone anyway). So, when you reference its height, toMove becomes zero. Now that the radio group is made visible, Android measures it and lays it out. Unfortunately, since toMove == 0, the radio group does not shift. (Measurement and layout occur after the height is captured with getHeight().)

When the radio group is marked gone again and is then marked as visible, toMove gets the height of the radio group since it was previously measured and those measurements were stored. See "How Android Draws View" for more detail.

To fix this, you will need to explicitly measure the radio group. I have made changes to your onCreateView() below:

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the Layout for this fragment
        View myView = inflater.inflate(R.layout.fragment_order, container, false);
        constraintLayout = (ConstraintLayout) myView.findViewById(R.id.order_screen_layout);
        set = new ConstraintSet();
        set.clone(constraintLayout);
        set.applyTo(constraintLayout);

        // Radio Groups
        RadioGroup deliveryGroup = (RadioGroup) myView.findViewById(R.id.radioGroup);
        stdDeliveryGroup = (RadioGroup) myView.findViewById(R.id.std_delivery_group);

        deliveryGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                // Checked ID is the radio button selected

                switch (checkedId) {
                    case R.id.pickup_radio_button:
                        shipping = 0;
                        delivery = PICKUP;
                        deliveryType = "Pick-Up";
                        updatePrice();
                        stdDeliveryGroup.setVisibility(View.GONE);
                        break;

                    case R.id.free_delivery_radio:
                        shipping = 0;
                        delivery = CBD_DELIVERY;
                        deliveryType = "CBD - Delivery";
                        updatePrice();
                        stdDeliveryGroup.setVisibility(View.GONE);
                        break;

                    case R.id.standard_delivery_radio:
                        // ToDo: Impliment delivery charges if delivering outside of CBD
                        delivery = STD_DELIVERY;
                        deliveryType = "Standard Delivery";
                        stdDeliveryGroup.setVisibility(View.VISIBLE);
//                                SystemClock.sleep(2000);
                        break;
                }

                if (stdDeliveryGroup.getVisibility() == View.VISIBLE) {
                    // Force measurement since view was "gone" at last measurement.
                    if (stdDeliveryGroup.getMeasuredHeight() == 0) {
                        // Only force measurement if the view is gone; otherwise, the normal flow
                        // will work.
                        ViewGroup.LayoutParams lp = stdDeliveryGroup.getLayoutParams();
                        int widthSpec = View.MeasureSpec.makeMeasureSpec(lp.width, View.MeasureSpec.EXACTLY);
                        int heightSpec = View.MeasureSpec.makeMeasureSpec(lp.height, View.MeasureSpec.EXACTLY);
                        stdDeliveryGroup.measure(widthSpec, heightSpec);
                    }
                    toMove = stdDeliveryGroup.getMeasuredHeight();
                }

                if (stdDeliveryGroup.getVisibility() == View.GONE) {
                    toMove = 0;
                }

                shippingNumber.animate().translationY(toMove);
                shippingText.animate().translationY(toMove);
                orderButton.animate().translationY(toMove);
                totalText.animate().translationY(toMove);
                totalNumber.animate().translationY(toMove);
            }
        });

        return myView;
    }

Upon reflection, I realized that the preceding solution would have problems if the size of the radio group is not specified in exact units. An alternative way to set the visibility of the radio group to visible initially. This will cause Android to measure and layout the radio group. We will then use a predraw listener on the ConstraintLayout to abort drawing of the layout and set the visibility to gone. Change the visibility of the radio group in the XML to visible and add the following code to onCreate().

constraintLayout.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        constraintLayout.getViewTreeObserver().removeOnPreDrawListener(this);
        stdDeliveryGroup.setVisibility(View.GONE);
        return false;
    }
});

Another way, and probably the best way, to correct this problem is to rework your layout a little. Constrain the top of the widgets you want to slide down to the bottom of your gone radio group. You can now do the animation using ConstraintSet and TransitionManager.beginDelayedTransition(). See this tutorial for an introduction. Take a look at Visibility Behavior in the ConstraintLayout documentation if you need to make some adjustments to margins.

Solution:

I ended up solving the problem by using visibility = (visible < = > invisible)

Explanation:

Originally the visibility went from (gone < = > visible).

By Setting it to "invisible" when the activity starts up, I'm able to measure the height of the group, but it won't affect the layout in any other way. Thanks to @Cheticamp for pointing me in the right direction. It was indeed not measuring the height of the view first, even though the call to getHeight() came after the view was set to visible.

Neither of the first two answers worked for me, they still did the same thing. For the constraint layout suggestion at the end, I was unable to attach the constraints of the views to the bottom of a view which was set to "gone".

I don't know if having the view be "invisible" is the ideal solution, but it is having the desired effect. If I discover any problems with this method in the future, I'll update here.

Related