Desliza hacia atrás como Pinterest o Tumblr


¿Alguien tiene una idea de cómo Pinterest o Tumblr ha implementado allí el método "deslizar hacia atrás".

Es decir, en Pinterest puede hacer clic en una publicación en el feed de noticias. Que el DetailActivity se inicia y muestra los detalles de la entrada seleccionada. Luego, puede presionar el botón atrás para volver a la actividad del feed de noticias, o puede deslizar (la actividad de detalles) hacia la izquierda para volver a la actividad del feed de noticias.

Video: http://youtu.be/eVcSCWetnTA

Normalmente lo haría use overridePendingTransition(), pero overridePendingTransition() toma animaciones (id de recursos como R.anim.foo). Pinterest y Tumblr inician la animación solo si el usuario hace un gesto de deslizamiento. También admiten algún tipo de "animación cuadro a cuadro" según el movimiento de los dedos. Por lo tanto, rastrean la distancia del movimiento del dedo y animan la transición al valor porcentual correspondiente.

Sé cómo usar un objeto Animation / AnimatorSet "real java" con FragmentTransaction para animar un reemplazo de fragmento. Con fragmentos tengo que anular onCreateAnimator(), pero no tengo idea de cómo implementar algo así con Actividades. ¿Existe un onCreateAnimator() (o algo similar) para las Actividades? Tampoco estoy seguro de cómo deslizar el comportamiento, ya que no está iniciando la animación en este momento, sino más bien un cambio de propiedad paso a paso de la Ventana / Actividad/ Fragmento o lo que sea ...

Alguna sugerencia?

EDITAR: He encontrado un video de la aplicación pinterest en youtube: http://youtu.be/eVcSCWetnTA Eso es lo que quiero implementar.

Supongo que Pinterest está trabajando con Fragmentos y onCreateAnimator() para lograr el "deslizar hacia atrás". Dado que mi Aplicación ya tiene Fragmentos y fragmentos secundarios en una actividad, sería mucho más fácil para mí si pudiera implementarlos para Actividades.

Una vez más: sé cómo detectar gestos de deslizamiento y eso no es lo que estoy pidiendo. Ver el vídeo de youtube: http://youtu.be/eVcSCWetnTA


ACTUALIZAR: He creado una pequeña biblioteca, que no tiene exactamente lo mismo comportamiento como la implementación de Pinterest o Tumblrs, sin embargo, para mis aplicaciones esto me parece una buena solución: https://github.com/sockeqwe/SwipeBack?source=c

Author: sockeqwe, 2013-10-01

9 answers

Parece que el efecto que estás buscando es uno de los ejemplos para ViewPager en el sitio web del desarrollador de Android.

Echa un vistazo http://developer.android.com/training/animation/screen-slide.html#depth-page , en la sección Transformador de página de profundidad. Tiene un video y código fuente.

Usando un ViewPager .PageTransformer puede decidir cómo se comportan las páginas al cambiar de una a otra.

La única diferencia entre la ejemplo y el video al que enlazaste es que izquierda-derecha parece estar invertido, pero debería ser un buen punto de partida para lo que vi en el video de YouTube vinculado en la pregunta. Habría que cambiar las medidas sobre las dos opiniones. Como se muestra en este fragmento de código (el 1er parámetro a mPager.setPageTransformer debe ser reverseDrawingOrder = false). Tenga en cuenta que las 2 if secciones centrales se intercambian y la variable position se maneja ligeramente diferente para cambiar de lado. El efecto de rebote se deja como un ejercicio. Por favor comparta cuando ¡lo entiendes!

    package com.example.android.animationsdemo;

    import android.support.v4.view.ViewPager;
    import android.view.View;

    public class SinkPageTransformer implements ViewPager.PageTransformer {
            private static float MIN_SCALE = 0.75f;

            public void transformPage(View view, float position) {
                    int pageWidth = view.getWidth();

                    if (position < -1) { // [-Infinity,-1)
                            // This page is way off-screen to the left.
                            view.setAlpha(0);

                    } else if (position <= 0) { // [-1,0]
                            // Fade the page out.
                            view.setAlpha(1 + position);

                            // Counteract the default slide transition
                            view.setTranslationX(pageWidth * -position);

                            // Scale the page down (between MIN_SCALE and 1)
                            float scaleFactor = MIN_SCALE
                                            + (1 - MIN_SCALE) * (1 - Math.abs(position));
                            view.setScaleX(scaleFactor);
                            view.setScaleY(scaleFactor);

                    } else if (position <= 1) { // (0,1]
                            // Use the default slide transition when moving to the left page
                            view.setAlpha(1);
                            view.setTranslationX(0);
                            view.setScaleX(1);
                            view.setScaleY(1);

                    } else { // (1,+Infinity]
                            // This page is way off-screen to the right.
                            view.setAlpha(0);
                    }
            }
    }

Y en caso de que la página con la muestra vaya poof, aquí está el código original de esa sección:

    public class DepthPageTransformer implements ViewPager.PageTransformer {
        private static float MIN_SCALE = 0.75f;

        public void transformPage(View view, float position) {
            int pageWidth = view.getWidth();

            if (position < -1) { // [-Infinity,-1)
                // This page is way off-screen to the left.
                view.setAlpha(0);

            } else if (position <= 0) { // [-1,0]
                // Use the default slide transition when moving to the left page
                view.setAlpha(1);
                view.setTranslationX(0);
                view.setScaleX(1);
                view.setScaleY(1);

            } else if (position <= 1) { // (0,1]
                // Fade the page out.
                view.setAlpha(1 - position);

                // Counteract the default slide transition
                view.setTranslationX(pageWidth * -position);

                // Scale the page down (between MIN_SCALE and 1)
                float scaleFactor = MIN_SCALE
                        + (1 - MIN_SCALE) * (1 - Math.abs(position));
                view.setScaleX(scaleFactor);
                view.setScaleY(scaleFactor);

            } else { // (1,+Infinity]
                // This page is way off-screen to the right.
                view.setAlpha(0);
            }
        }
    }
 19
Author: frozenkoi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-09 08:12:30

Actualización: solucionado el problema de uso de memoria para este proyecto y cambiado el estilo de deslizamiento a iOS como.

introduzca la descripción de la imagen aquí

Escribí una demo exactamente como Pinterest y tumblr como, solo se extiende la BaseActivity y se obtiene un efecto de deslizamiento hacia atrás, funciona sin problemas!

Comprobar esto:https://github.com/chenjishi/SlideActivity

Y la captura de pantalla: introduzca la descripción de la imagen aquí

 8
Author: Jishi Chen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-15 06:34:35

Encontré un proyecto de GitHub que se basa en SwipeBack como Pinterest.

Es realmente un gran proyecto de código abierto que debería resolver su problema. Hace lo que necesita, como ir a la pantalla anterior presionando atrás o simplemente deslizar. Como este proyecto tiene opción

1. Desliza de izquierda a derecha

2. Desliza de derecha a izquierda

3. Desliza la parte inferior hacia arriba

Https://github.com/Issacw0ng/SwipeBackLayout

Y también se instala esta aplicación de demostración de Google Play.

Https://play.google.com/store/apps/details?id=me.imid.swipebacklayout.demo

Capturas de pantalla adjuntas: -

introduzca la descripción de la imagen aquí

Espero que esto te ayude.

 7
Author: Amit Gupta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-09 14:55:48

Pude hacer esto en 15 minutos, no está mal para empezar. Si pasas algún tiempo, es posible que puedas optimizarlo.

package mobi.sherif.activitydrag;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout.LayoutParams;

public class MainActivity extends Activity {
    private static final double PERCENT_OF_SCROLL_OF_ACTIVITY_TO_FINISH = 0.3;
    View mView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mView = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
        setContentView(mView);
    }

    private boolean isDragging = false;
    int startX;
    int currentX;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.v("sherif", isDragging?"YES":"NO" + ": " + event.getX());
        if(!isDragging) {
            if(event.getAction() == MotionEvent.ACTION_DOWN && event.getX()<24) {
                isDragging = true;
                startX = (int) event.getX();
                currentX = 0;
                return true;
            }
            return super.onTouchEvent(event);
        }
        switch(event.getAction()) {
        case MotionEvent.ACTION_MOVE:
            currentX = (int) event.getX() - startX;
            LayoutParams params = (LayoutParams) mView.getLayoutParams();
            params.leftMargin = currentX;
            params.rightMargin = -1 * currentX;
            mView.requestLayout();
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            isDragging = false;
            double currentPercent1 = (double) currentX / mView.getWidth();
            float currentPercent = (float) currentPercent1;
            if(currentX > PERCENT_OF_SCROLL_OF_ACTIVITY_TO_FINISH * mView.getWidth()) {
                AnimationSet animation = new AnimationSet(false);
                Animation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 1.0f - currentPercent, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
                anim.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
                anim.setInterpolator(new LinearInterpolator());
                anim.setStartTime(AnimationUtils.currentAnimationTimeMillis());
                animation.addAnimation(anim);
                anim = new AlphaAnimation(1.0f, 0.5f);
                anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
                anim.setInterpolator(new LinearInterpolator());
                anim.setStartTime(AnimationUtils.currentAnimationTimeMillis());
                animation.addAnimation(anim);
                animation.setFillAfter(true);
                animation.setAnimationListener(new AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {}
                    @Override
                    public void onAnimationRepeat(Animation animation) {}
                    @Override
                    public void onAnimationEnd(Animation animation) {
                        finish();
                    }
                });
                mView.startAnimation(animation);
            }
            else {
                AnimationSet animation = new AnimationSet(false);
                Animation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f -1 * currentPercent, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
                anim.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
                anim.setInterpolator(new LinearInterpolator());
                anim.setStartTime(AnimationUtils.currentAnimationTimeMillis());
                animation.addAnimation(anim);
                animation.setFillAfter(true);
                animation.setAnimationListener(new AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {}
                    @Override
                    public void onAnimationRepeat(Animation animation) {}
                    @Override
                    public void onAnimationEnd(Animation animation) {
                        LayoutParams params = (LayoutParams) mView.getLayoutParams();
                        params.leftMargin = 0;
                        params.rightMargin = 0;
                        mView.requestLayout();
                        mView.clearAnimation();
                    }
                });
                mView.startAnimation(animation);
            }
            break;
        }
        return true;

    }
}
 3
Author: Sherif elKhatib,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-03 14:40:17

Acabo de comprobar con el visor de jerarquía. Parece que están usando ViewPager con una captura de pantalla de la actividad anterior.

 2
Author: Dmitry Ryadnenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-16 19:52:37

Yo sugeriría hacer lo siguiente:

En primer lugar detectar el gesto que el usuario está haciendo en el dispositivo. Puede consultar este enlace

No voy a copiar el código relevante del enlace anterior, ya que creo que es la respuesta aceptada

En segundo lugar se puede en este método

public void onSwipeLeft() {
    Toast.makeText(MyActivity.this, "left", Toast.LENGTH_SHORT).show();
}

Haga lo siguiente como se sugiere en esta pregunta

Allí hablan de terminar una actividad con una animación

Look into doing it through a theme. You can define enter exit animations for activities or the entire application

Espero que esto ayude usted

 1
Author: Armand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:32:26

Así que supongo que encontré la solución por mí mismo:

En primer lugar: Pinterest de hecho utiliza un ViewPager con un transformador de página personalizado como @frozenkoi ha mencionado en su respuesta. Puede ver el efecto borde de sobreroll del buscapersonas de vista en la aplicación pinterest.

@Amit Gupta ha apuntado a la biblioteca que permite que la actividad se deslice. Su bastante el mismo concepto como varios cajones de navegación hace y establece también el tema a translúcido. Deslizan el diseño. Pero eso no es exactamente lo que estaba buscando, porque desliza la actividad superior a la derecha y simplemente llama a finish(). Pero la actividad inferior no se animará.

La solución es (y supongo que esto es lo que hace Tumblr) escribir su propia Animación con Objetos de Animación y animarla paso a paso. Esto se puede hacer con ActivityOptions. En mi opinión esta será la solución.

 1
Author: sockeqwe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-11 09:47:51

Escribí un proyecto. Le permite desarrollar una aplicación navegada por Fragmentos fácilmente, funciona igual que Pinterest.

Https://github.com/fengdai/FragmentMaster

Tal vez no es la respuesta lo que quieres. Pero espero que sea útil para alguien más.

 0
Author: Feng Dai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-20 07:18:42

Estaba lidiando con este en el proyecto en el que estoy trabajando actualmente y se me ocurrió el siguiente código. Tal vez no sea relevante para ti ahora, pero podría ayudar a alguien nuevo en este post. :)

Básicamente es la implementación de ViewPager como mencionas en tu respuesta, pero creo que es la solución más simple y rápida a tu pregunta. Las desventajas son que es solo para Fragmentos (podría cambiarse fácilmente para Objetos) y si desea agregar ActionBar en swipe, probablemente termine creando un uno personalizado.

public class DoubleViewPager extends FrameLayout implements ViewPager.OnPageChangeListener {

/**
 * Represents number of objects in DelegateViewPager. In others words it stands for one main content
 * window and one content detail window
 */
private static final int SCREEN_COUNT = 2;

private static final int CONTENT_SCREEN = 0;
private static final int DETAIL_SCREEN  = 1;


private DelegateViewPager delegateViewPager;
private SparseArray<Fragment> activeScreens = new SparseArray<Fragment>(SCREEN_COUNT) ;
private DelegateAdapter adapter;

public DoubleViewPager(Context context) {
    this(context, null);
}

public DoubleViewPager(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public DoubleViewPager(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    delegateViewPager = new DelegateViewPager(context);
    delegateViewPager.setId(R.id.main_page_id);
    delegateViewPager.setOverScrollMode(ViewPager.OVER_SCROLL_NEVER);
    final FrameLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
    addView(delegateViewPager, params);
}

/**
 * Create a new PagerAdapter and set content fragment as a first object in ViewPager;
 * @param fragment Fragment you want to use as a main content
 * @param fm FragmentManager required for ViewPager transactions
 */
public void initialize(final Fragment fragment, final FragmentManager fm) {
    adapter = new DelegateAdapter(fm);
    delegateViewPager.setAdapter(adapter);
    activeScreens.put(CONTENT_SCREEN, fragment);
    adapter.notifyDataSetChanged();
}

/**
 * Adds fragment to stack and set it as current selected item. Basically it the same thing as calling
 * startActivity() with some transitions effects
 * @param fragment Fragment you want go into
 */
public void openDetailScreen(Fragment fragment) {
    activeScreens.put(DETAIL_SCREEN, fragment);
    adapter.notifyDataSetChanged();
    delegateViewPager.setCurrentItem(1, true);
}

public void hideDetailScreen() {
    delegateViewPager.setCurrentItem(CONTENT_SCREEN);
    if (activeScreens.get(DETAIL_SCREEN) != null) {
        activeScreens.remove(DETAIL_SCREEN);
        adapter.notifyDataSetChanged();
    }
}

@Override
public void onPageScrolled(int i, float v, int i2) {
    // unused
}

@Override
public void onPageSelected(int i) {
    if (i == CONTENT_SCREEN) hideDetailScreen();
}

@Override
public void onPageScrollStateChanged(int i) {
    // unused
}



private class DelegateViewPager extends ViewPager {

    public DelegateViewPager(Context context) {
        super(context);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return getCurrentItem() != CONTENT_SCREEN && super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return getCurrentItem() != CONTENT_SCREEN && super.onTouchEvent(event);
    }
}

private final class DelegateAdapter extends FragmentPagerAdapter {

    public DelegateAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int i) {
        return activeScreens.get(i);
    }

    @Override
    public int getCount() {
        return activeScreens.size();
    }
}

}

Aquí está la actividad que la implementa con otro ViewPager como un SlidingMenu. (como extra)

public class DoubleViewPagerActivity extends FragmentActivity {

DoubleViewPager doubleViewPager;

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

    doubleViewPager = (DoubleViewPager) findViewById(R.id.doublePager);
    doubleViewPager.initialize(new MainContentFragment(), getSupportFragmentManager());
}


public static final class MainContentFragment extends Fragment {

    public MainContentFragment() {

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
        final View view = inflater.inflate(R.layout.fragment_doublepager_content_window, parent, false);
        final ViewPager pager = (ViewPager) view.findViewById(R.id.contentPager);
        pager.setAdapter(new SimpleMenuAdapter(getChildFragmentManager()));
        pager.setOffscreenPageLimit(2);
        pager.setCurrentItem(1);
        return view;
    }

}

public static final class SimpleMenuAdapter extends FragmentPagerAdapter {

    public SimpleMenuAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int i) {
        return DoubleViewPagerActivity.PagerFragment.instance(i);
    }

    @Override
    public int getCount() {
        return 3;
    }

    @Override
    public float getPageWidth(int position) {
        switch (position) {
            case 0:
            case 2:
                return 0.7f;
        }
        return super.getPageWidth(position);
    }
}

public static final class PagerFragment extends Fragment {

    public static PagerFragment instance(int position) {
        final PagerFragment fr = new PagerFragment();
        Bundle args = new Bundle();
        args.putInt("position", position);
        fr.setArguments(args);
        return fr;
    }

    public PagerFragment() {

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
        final FrameLayout fl = new FrameLayout(getActivity());
        fl.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        int position = getArguments().getInt("position");
        switch (position) {
            case 0:
                fl.setBackgroundColor(Color.RED);
                break;
            case 1:
                fl.setBackgroundColor(Color.GREEN);
                initListView(fl);
                break;
            case 2:
                fl.setBackgroundColor(Color.BLUE);
                break;
        }
        return fl;
    }

    private void initListView(FrameLayout fl) {
        int max = 50;
        final ArrayList<String> items = new ArrayList<String>(max);
        for (int i = 1; i <= max; i++) {
            items.add("Items " + i);
        }

        ListView listView = new ListView(getActivity());
        fl.addView(listView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
        listView.setAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_1, items));

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                ((DoubleViewPagerActivity) getActivity()).doubleViewPager.openDetailScreen(new DetailFragment());
            }
        });
    }
}

public final static class DetailFragment extends Fragment {

    public DetailFragment() {

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
        FrameLayout l = new FrameLayout(getActivity());
        l.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        l.setBackgroundColor(getResources().getColor(android.R.color.holo_purple));
        return l;
    }

}

}

 0
Author: Michal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-08 18:05:53