1

I would like to do something like this: https://storage.googleapis.com/spec-host/mio-staging%2Fmio-design%2F1563837804615%2Fassets%2F1XlKhaQFU9aS84ACmF-EDjVKDgI4pPldv%2F02-overflowmenu.mp4

enter image description here

I would like to squeeze a "ViewGroup" as you can see in the video. In the meantime, I want to fade out the content in the ViewGroup at its original position. i.e. not pushing the content to the right.

Any idea how to implement this?

Thanks!

ashakirov
  • 12,112
  • 6
  • 40
  • 40
Bear
  • 5,138
  • 5
  • 50
  • 80

1 Answers1

4

You can do such animations with Transition API. Declare two ViewGroups: first one is horizontal with cut/copy buttons, second is vertical with search/share buttons. Then switch these ViewGroups with TransitionManager. Then just provide Transition which describes how views on first and second ViewGroups appears and disappers.

import androidx.appcompat.app.AppCompatActivity;
import androidx.transition.ChangeBounds;
import androidx.transition.Fade;
import androidx.transition.Scene;
import androidx.transition.Slide;
import androidx.transition.Transition;
import androidx.transition.TransitionManager;
import androidx.transition.TransitionSet;

public class MainActivity extends AppCompatActivity {

    private ViewGroup mSceneRoot;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout);
        mSceneRoot = findViewById(R.id.sceneRoot);

        showPopup1();
    }

    private void showPopup1() {
        ViewGroup popup1 = (ViewGroup) getLayoutInflater().inflate(R.layout.popup_1, mSceneRoot, false);
        popup1.findViewById(R.id.btnGo).setOnClickListener(v -> {
            showPopup2();
        });

        Scene scene = new Scene(mSceneRoot, popup1);
        TransitionManager.go(scene, getTransition(false));
    }

    private void showPopup2() {
        ViewGroup popup2 = (ViewGroup) getLayoutInflater().inflate(R.layout.popup_2, mSceneRoot, false);
        popup2.findViewById(R.id.btnBack).setOnClickListener(v -> {
            showPopup1();
        });

        Scene scene = new Scene(mSceneRoot, popup2);
        TransitionManager.go(scene, getTransition(true));
    }
}

activity_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sceneRoot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="bottom|right"
    android:orientation="vertical"
    android:paddingBottom="150dp"
    android:paddingRight="50dp"
    android:clipToPadding="false"
    android:clipChildren="false"/>

popup_1.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/popup1"
    android:layout_width="250dp"
    android:layout_height="wrap_content"
    android:transitionName="bg"
    app:cardElevation="10dp">

    <Button
        android:id="@+id/btnGo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:text="go"
        android:transitionName="btn_go"/>
</androidx.cardview.widget.CardView>

popup_2.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/popup2"
    android:layout_width="wrap_content"
    android:layout_height="250dp"
    android:transitionName="bg"
    app:cardElevation="10dp">

    <Button
        android:id="@+id/btnBack"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="back"
        android:transitionName="btn_back"/>
</androidx.cardview.widget.CardView>

Last thing here is getTransition method. You can just use AutoTransition, it can handle easy layout changes.

private Transition getTransition(boolean open) {
      return new AutoTransition();
}

Result:

with AutoTransition

Also just for demostration I wrote more complex transition where button on first viewgroup appear/dissapear with Slide animation.

 private Transition getTransition(boolean open) {
        Slide btnGo = new Slide(Gravity.RIGHT);
        btnGo.addTarget("btn_go");

        ChangeBounds bgBounds = new ChangeBounds();
        bgBounds.addTarget("bg");

        Fade btnBack = new Fade();
        btnBack.addTarget("btn_back");

        TransitionSet set = new TransitionSet();
        set.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
        if (open) {
            set.addTransition(btnGo);
            set.addTransition(bgBounds);
            set.addTransition(btnBack);
        } else {
            set.addTransition(btnBack);
            set.addTransition(bgBounds);
            set.addTransition(btnGo);
        }
        return set;
    }

Result:

custom transition

Note that transitionName for background should be same in both layouts. All transition classes here are from androix package so code is backward compatible. But if you need support pre Lollipop devices you should set transitionName via ViewCompat.setTransitionName method.

You can check my another answer with more difficult/custom transition.

ashakirov
  • 12,112
  • 6
  • 40
  • 40
  • Thanks for the detailed answer. I have a follow-up question. So, in your example, the right and bottom bound remains unchanged, while we change the top and left bound. Is that the default? Is there a way to change it if we want? Thanks. – Bear Aug 13 '19 at 10:52
  • 2
    `ChangeBounds` transition changes view bounds from bounds which are in first viewgroup to bounds which are in second viewgroup. In this example I place bottom right corner of both viewgroups into same position. Check `activity_layout.xml` `android:gravity="bottom|right"` param. Thats why it looks like right and botton bounds doesn't change. – ashakirov Aug 13 '19 at 13:56