web-dev-qa-db-fra.com

Android - Comment créer une transition d'un élément en vue de liste à une activité entière?

Ce que je veux, c'est que lorsque l'utilisateur clique sur un élément de liste dans un ListView, il se convertit en une activité entière (comme vous pouvez le voir dans l'exemple suivant), mais je n'ai pas pu trouver de tutoriel expliquant cela et, en fait, je le fais ne sais pas comment ce mouvement est appelé.

En d'autres termes, ce que je veux réaliser, c'est:

  1. Augmentez l'élévation de l'élément de liste lorsque vous cliquez dessus (comme vous pouvez le voir dans le gif de droite)

  2. Développez et transformez l'élément de liste en la disposition de fragment/activité suivante qui contient des informations détaillées sur l'élément cliqué

enter image description here

J'ai essayé pas mal de transitions mais sans succès. Quelqu'un peut-il m'aider à accomplir cela?

24
Antonio

Je construis un petit exemple d'application qui fait la transition entre deux activités avec l'effet souhaité: Sample Application

Cependant les transitions dans les gifs fournis sont légèrement différentes. La transition dans le gif sur le côté gauche transforme l'élément de liste dans la zone de contenu de la deuxième activité (la barre d'outils reste en place). Dans le gif à droite, la transition transforme l'élément de liste en écran complet de la deuxième activité. Le code suivant fournit l'effet dans le gif gauche. Cependant, il devrait être possible d'adapter la solution avec des modifications mineures pour réaliser la transition dans le bon gif.

Notez que cela ne fonctionne que sur Lollipop. Cependant, il est possible de se moquer d'un effet différent sur les appareils plus anciens. En outre, le seul but du code fourni est de montrer comment cela pourrait être fait. Ne l'utilisez pas directement dans votre application.

Activité principale:

public class MainActivity extends AppCompatActivity {

    MyAdapter myAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
        ListView listView = (ListView) findViewById(R.id.list_view);

        myAdapter = new MyAdapter(this, 0, DataSet.get());

        listView.setAdapter(myAdapter);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, final View view, final int position, long id) {
                startTransition(view, myAdapter.getItem(position));
            }
        });
    }

    private void startTransition(View view, Element element) {
        Intent i = new Intent(MainActivity.this, DetailActivity.class);
        i.putExtra("ITEM_ID", element.getId());

        Pair<View, String>[] transitionPairs = new Pair[4];
        transitionPairs[0] = Pair.create(findViewById(R.id.toolbar), "toolbar"); // Transition the Toolbar
        transitionPairs[1] = Pair.create(view, "content_area"); // Transition the content_area (This will be the content area on the detail screen)

        // We also want to transition the status and navigation bar barckground. Otherwise they will flicker
        transitionPairs[2] = Pair.create(findViewById(Android.R.id.statusBarBackground), Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME);
        transitionPairs[3] = Pair.create(findViewById(Android.R.id.navigationBarBackground), Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME);
        Bundle b = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this, transitionPairs).toBundle();

        ActivityCompat.startActivity(MainActivity.this, i, b);
    }
}

activity_main.xml:

<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:orientation="vertical">

    <Android.support.v7.widget.Toolbar
        Android:id="@+id/toolbar"
        Android:layout_width="match_parent"
        Android:layout_height="?attr/actionBarSize"
        Android:background="@color/colorPrimary"
        Android:transitionName="toolbar" />

    <ListView
        Android:id="@+id/list_view"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent" />

</LinearLayout>

DétailActivité:

public class DetailActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);

        setSupportActionBar((Toolbar) findViewById(R.id.toolbar));

        long elementId = getIntent().getLongExtra("ITEM_ID", -1);
        Element element = DataSet.find(elementId);


        ((TextView) findViewById(R.id.title)).setText(element.getTitle());
        ((TextView) findViewById(R.id.description)).setText(element.getDescription());

        // if we transition the status and navigation bar we have to wait till everything is available
        TransitionHelper.fixSharedElementTransitionForStatusAndNavigationBar(this);
        // set a custom shared element enter transition
        TransitionHelper.setSharedElementEnterTransition(this, R.transition.detail_activity_shared_element_enter_transition);
    }
}

activity_detail.xml:

<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:orientation="vertical">

    <Android.support.v7.widget.Toolbar
        Android:id="@+id/toolbar"
        Android:layout_width="match_parent"
        Android:layout_height="?attr/actionBarSize"
        Android:background="@color/colorPrimary"
        Android:transitionName="toolbar" />

    <LinearLayout
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:background="#abc"
        Android:orientation="vertical"
        Android:paddingBottom="200dp"
        Android:transitionName="content_area"
        Android:elevation="10dp">

        <TextView
            Android:id="@+id/title"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content" />

        <TextView
            Android:id="@+id/description"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>

detail_activity_shared_element_enter_transition.xml (/ res/transition /):

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:transitionOrdering="together">
    <changeBounds/>
    <changeTransform/>
    <changeClipBounds/>
    <changeImageTransform/>
    <transition class="my.application.transitions.ElevationTransition"/>
</transitionSet>

my.application.transitions.ElevationTransition:

@TargetApi(Build.VERSION_CODES.Lollipop)
public class ElevationTransition extends Transition {

    private static final String PROPNAME_ELEVATION = "my.elevation:transition:elevation";

    public ElevationTransition() {
    }

    public ElevationTransition(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        captureValues(transitionValues);
    }

    @Override
    public void captureEndValues(TransitionValues transitionValues) {
        captureValues(transitionValues);
    }

    private void captureValues(TransitionValues transitionValues) {
        Float elevation = transitionValues.view.getElevation();
        transitionValues.values.put(PROPNAME_ELEVATION, elevation);
    }

    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
        if (startValues == null || endValues == null) {
            return null;
        }

        Float startVal = (Float) startValues.values.get(PROPNAME_ELEVATION);
        Float endVal = (Float) endValues.values.get(PROPNAME_ELEVATION);
        if (startVal == null || endVal == null || startVal.floatValue() == endVal.floatValue()) {
            return null;
        }

        final View view = endValues.view;
        ValueAnimator a = ValueAnimator.ofFloat(startVal, endVal);
        a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                view.setElevation((float)animation.getAnimatedValue());
            }
        });

        return a;
    }
}

TransitionHelper:

public class TransitionHelper {

    public static void fixSharedElementTransitionForStatusAndNavigationBar(final Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Lollipop)
            return;

        final View decor = activity.getWindow().getDecorView();
        if (decor == null)
            return;
        activity.postponeEnterTransition();
        decor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @TargetApi(Build.VERSION_CODES.Lollipop)
            @Override
            public boolean onPreDraw() {
                decor.getViewTreeObserver().removeOnPreDrawListener(this);
                activity.startPostponedEnterTransition();
                return true;
            }
        });
    }

    public static void setSharedElementEnterTransition(final Activity activity, int transition) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Lollipop)
            return;
        activity.getWindow().setSharedElementEnterTransition(TransitionInflater.from(activity).inflateTransition(transition));
    }
}

Alors, quelles sont les différentes parties ici: Nous avons deux activités. Pendant la transition, quatre vues sont transférées entre les activités.

  • Barre d'outils: comme dans le gif gauche, la barre d'outils ne bouge pas avec le reste du contenu.

  • Élément ListView View -> devient la vue de contenu de DetailActivity

  • StatusBar et NavigationBar Contexte: Si nous n'ajoutons pas ces vues à l'ensemble des vues de transition, elles disparaîtront et réapparaîtront pendant la transition. Cela nécessite cependant de retarder la transition d'entrée (voir: TransitionHelper.fixSharedElementTransitionForStatusAndNavigationBar)

Dans le MainActivity, les vues de transition sont ajoutées au bundle utilisé pour démarrer le DetailActivity. De plus, les vues de transition doivent être nommées (transitionName) dans les deux activités. Cela peut être fait dans la mise en page xml ainsi que par programmation.

L'ensemble de transitions par défaut, utilisé lors de la transition de l'élément partagé, affecte différents aspects de la vue (par exemple: limites de la vue - voir 2 ). Cependant, les différences d'élévation d'une vue ne sont pas animées. C'est pourquoi la solution présentée utilise le ElevationTransition personnalisé.

17
Andreas Wenger

essayez ceci .. Material-Animations

blueIconImageView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent i = new Intent(MainActivity.this, SharedElementActivity.class);

        View sharedView = blueIconImageView;
        String transitionName = getString(R.string.blue_name);

        ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, sharedView, transitionName);
        startActivity(i, transitionActivityOptions.toBundle());
    }
});

SharedElements

4
Carlos.V

L'animation dont vous avez besoin s'appelle Transitions d'activité entre les éléments partagés. Par recherche, j'ai constaté que vous devriez:

  1. Mettez votre vue ListView dans une disposition relative
  2. OnClick, gonflez une copie de votre moteur de rendu
  3. Rechercher les coordonnées globales de l'emplacement du rendu par rapport au parent de ListView
  4. Ajoutez le rendu copié à RelativeLayout (parent de ListView)
  5. Animer la listeVoir loin
  6. À la fin de cette animation, animez votre nouveau rendu
  7. Profit!

     public class MainActivity extends Activity {
    
     private RelativeLayout layout;
            private ListView listView;
            private MyRenderer selectedRenderer;
    
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                layout = new RelativeLayout(this);
                setContentView(layout);
                listView = new ListView(this);
                RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(
                        RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
                layout.addView(listView, rlp);
    
                listView.setAdapter(new MyAdapter());
                listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                    @Override
                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    
                        // find out where the clicked view sits in relationship to the
                        // parent container
                        int t = view.getTop() + listView.getTop();
                        int l = view.getLeft() + listView.getLeft();
    
                        // create a copy of the listview and add it to the parent
                        // container
                        // at the same location it was in the listview
                        selectedRenderer = new MyRenderer(view.getContext());
                        RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(view.getWidth(), view
                                .getHeight());
                        rlp.topMargin = t;
                        rlp.leftMargin = l;
                        selectedRenderer.textView.setText(((MyRenderer) view).textView.getText());
                        layout.addView(selectedRenderer, rlp);
                        view.setVisibility(View.INVISIBLE);
    
                        // animate out the listView
                        Animation outAni = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0f,
                                Animation.RELATIVE_TO_SELF, -1f, Animation.RELATIVE_TO_SELF, 0f,
                                Animation.RELATIVE_TO_SELF, 0f);
                        outAni.setDuration(1000);
                        outAni.setFillAfter(true);
                        outAni.setAnimationListener(new Animation.AnimationListener() {
                            @Override
                            public void onAnimationStart(Animation animation) {
                            }
    
                            @Override
                            public void onAnimationRepeat(Animation animation) {
                            }
    
                            @Override
                            public void onAnimationEnd(Animation animation) {
                                ScaleAnimation scaleAni = new ScaleAnimation(1f, 
                                        1f, 1f, 2f, 
                                        Animation.RELATIVE_TO_SELF, 0.5f,
                                        Animation.RELATIVE_TO_SELF, 0.5f);
                                scaleAni.setDuration(400);
                                scaleAni.setFillAfter(true);
                                selectedRenderer.startAnimation(scaleAni);
                            }
                        });
    
                        listView.startAnimation(outAni);
                    }
                });
            }
    
            public class MyAdapter extends BaseAdapter {
                @Override
                public int getCount() {
                    return 10;
                }
    
                @Override
                public String getItem(int position) {
                    return "Hello World " + position;
                }
    
                @Override
                public long getItemId(int position) {
                    return position;
                }
    
                @Override
                public View getView(int position, View convertView, ViewGroup parent) {
                    MyRenderer renderer;
                    if (convertView != null)
                        renderer = (MyRenderer) convertView;
                    else
                        renderer = new MyRenderer(MainActivity.this);
                    renderer.textView.setText(getItem(position));
                    return renderer;
                }
            }
    
            public class MyRenderer extends RelativeLayout {
    
                public TextView textView;
    
                public MyRenderer(Context context) {
                    super(context);
                    setPadding(20, 20, 20, 20);
                    setBackgroundColor(0xFFFF0000);
    
                    RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(
                            RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                    rlp.addRule(CENTER_IN_PARENT);
                    textView = new TextView(context);
                    addView(textView, rlp);
                }
    
            }        }
    
0
Vishavjeet Singh