Swipe Back like Pinterest or Tumblr
Asked Answered
E

11

42

Does anybody has an idea how Pinterest or Tumblr has implemented there "swipe back" method.

i.e. on Pinterest you can click on a post on the news feed. Than the DetailActivity is started and displays the details for the selected post. Than you can press the back button to return to the news feed activity, or you can swipe (the details activity) to the left to come back to the news feed activity.

Video: http://youtu.be/eVcSCWetnTA

Normally I would use overridePendingTransition(), but overridePendingTransition() takes animations (Resource ids like R.anim.foo). Pinterest and Tumblr start the animation only if the user do a swipe gesture. They also support some kind of "frame by frame animation" according the fingers move. So they track the distance of the finger move and animate the transition to the corresponding percentage value.

I know how to use a "real java" Animation / AnimatorSet Object with FragmentTransaction to animate a fragment replacement. With fragments I have to override onCreateAnimator(), but I have no clue how to implement something like that with Activities. Is there a onCreateAnimator() (or something similar) for Activities? Also not sure how to swipe behaviour, since its not starting the animation right now, but more a step by step property changement of the Window / Activity/ Fragment or whatever ...

Any suggestions?

EDIT: I have found a video of the pinterest app at youtube: http://youtu.be/eVcSCWetnTA Thats what I want to implement.

I guess Pinterest is working with Fragments and onCreateAnimator() to achieve the "swipe back". Since my App has already Fragment and ChildFragments in a activity it would be so much easier for me if I could implement that for Activities.

Once more: I know how to detect swipe gestures and thats not what I'm asking for. Watch the youtube video: http://youtu.be/eVcSCWetnTA


UPDATE: I have created a little library, which has not exactly the same behavior like Pinterest or Tumblrs implementation, however for my apps this seems to me a good solution: https://github.com/sockeqwe/SwipeBack?source=c

Entresol answered 30/9, 2013 at 23:10 Comment(6)
Than you can press the back button to return to the news feed activity, or you can swipe (the details activity) to the left to come back to the news feed activity. - Are you sure that isn't a simple ViewPager?Similarity
No its not a viewpager, its a custom animation for switching from one activity to another or a Fragmenttransaction, if they use Fragment. However I would like to implement something like that for activities. For Fragments I can override onCreateAnimator(), but I could not find a similar method for Activities ...Entresol
Why not implement some form of gesture and then detect the starting and ending point of the gesture and calculate if it was a left swipe and then stop the activity with some kind of animation?See
I know how to detect swipe gestures. I have updated my question and have added a link to a youtube video that demonstrates what I'm looking forEntresol
What about to use a NavigationDrawer by filling it in the whole screen?Archenemy
I don't think that will bring the desired effect. I mean I could use SlidingMenu or another implementatino to slide the whole windoe to the left, but I see no way to let the previous activity to come on screen while swiping.Entresol
E
1

So I guess I found the solution by my self:

First of all: Pinterest indeed uses a ViewPager with a custom Page Transformer like @frozenkoi has mentioned in his answer. You can see the oversroll edge effect of the view pager in the pinterest app.

@Amit Gupta has pointed to library that let the activity slide. Its pretty the same concept like various Navigation drawers does and sets also the theme to translucent. They slide the layout. But thats not exactly what I was looking for, because it slides the top activity to the right and than simply calls finish(). But the underneath activity will not be animated in.

The solution is (and I guess this is was Tumblr does) to write your very own Animation with Animation Objects and animate it step by step. This can be done with ActivityOptions. In my opinion this will be the solution.

Entresol answered 11/10, 2013 at 9:47 Comment(2)
ActivityOptions is added only in API level 16!Autonomic
Yes thats true. You can do it like Pinterst and Path, by using a ViewPager. But than you have to implement your own ActionBar implementation etc. because normally the the ViewPager does not swipe the default actionbar.Entresol
D
23

It seems that the effect you're looking for is one of the samples for ViewPager in the android developer's website.

Check out http://developer.android.com/training/animation/screen-slide.html#depth-page , in the Depth page transformer section. It has a video and source code.

Using a ViewPager.PageTransformer you can decide how the pages behave when switching from one to the next.

The only difference between the sample and the video you linked to is that left-right seems to be inverted, but should be a good starting point for what I saw on the YouTube video linked in the question. The actions on the two views would have to be swaped. As shown in this piece of code (the 1st parameter to mPager.setPageTransformer should be reverseDrawingOrder = false). Note the middle 2 if sections are swaped and the position variable is handled slightly different to switch sides. The bouncy effect is left as an exercise. Please share when you get that!

    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);
                    }
            }
    }

And just in case the page with the sample goes poof, here's that section's original code:

    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);
            }
        }
    }
Diarchy answered 9/10, 2013 at 6:54 Comment(0)
V
8

Update: fixed memory usage problem for this project and changed the slide back style to iOS like.

enter image description here

I wrote a demo exactly like Pinterest and tumblr like,you just extend the BaseActivity and you get a swipe back effect,works smoothly!

check this:https://github.com/chenjishi/SlideActivity

and the screenshot:enter image description here

Vaquero answered 26/3, 2014 at 2:13 Comment(2)
Looks like pinterest have different implementation (may be not based on capturing first activity screenshot?), because after switch from landscape to portrait (in second activity) it still works fine.Handley
@Handley Pinterest don't use the screenshot, all of their scrollable pages are fragments, so they use ViewPager to swipe back.Tumblr use screenshot, but it's very easy to OutOfMemory, because screenshot on high definition is too large to handle, so Pinterest's method more better.Vaquero
S
7

I found a GitHub project that is based on SwipeBack like Pinterest.

It is really a great open source project that should solve your problem. It does as you needed like go to previous screen by pressing back or simple swipe. As this project having option

1. Swipe left to Right

2. Swipe Right to Left

3. Swipe Bottom to top

https://github.com/Issacw0ng/SwipeBackLayout

and also you install this demo application from Google Play.

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

Attached Screenshots:-

enter image description here

Hope this will help you.

Sporophyte answered 9/10, 2013 at 14:55 Comment(1)
It's an interesting project. but currently has problem on 4.4.4. Here is the issue I've opened about itPenguin
F
3

I was able to do this in 15 minutes, it is not bad for a start. If you spend some time, you might be able to optimize it.

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;

    }
}
Feral answered 3/10, 2013 at 14:40 Comment(4)
Thanks for your awesome sample code. Nice! But I want to swipe the whole Activity / Window. I have updated my question and have added a link to a youtube video that demonstates what Im looking forEntresol
oh... Thats a freaking PageViewer.Feral
That could be possible, however I think that they are using Fragments in some special way.Entresol
Thanks for the code @SherifelKhatib this works perfectly.Just set the second activity theme to <item name="android:windowBackground">@android:color/transparent</item>Tammany
F
2

I just checked with hierarchy viewer. It seems like they are using ViewPager with a screenshot of the previous activity.

Fated answered 16/1, 2014 at 19:52 Comment(3)
ViewPager(s) don't scroll the action bar. I think it wasn't the android ViewPager but custom child class with some heavy customization in it. Meanwhile, Facebook has implemented something similar when you scroll down (or up) after having selected a post with multiple photos. I bet the two implementation are almost the same. Can you figure out what Facebook is using?Jereme
@domenicop Facebook just draws article detail view over list view. It all done in the same activity.Fated
@nickes it overlaps fragmentsMetabolize
S
1

I would suggest doing the following:

Firstly detect the gesture that the user is doing in the device. You can refer to this link

I am not going to copy the relevant code from the above link, as I believe it is the accepted answer

Secondly you can in this method

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

Do the following as suggested by this question

They there talk about finishing an activity with an animation

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

Hope this helps you

See answered 3/10, 2013 at 11:58 Comment(1)
Maybe my question was not clear. I know how to detect swipe gestures, thats not what Im looking for. I have updated my question and have added a link to a youtube video that demonstrates what I'm looking forEntresol
E
1

So I guess I found the solution by my self:

First of all: Pinterest indeed uses a ViewPager with a custom Page Transformer like @frozenkoi has mentioned in his answer. You can see the oversroll edge effect of the view pager in the pinterest app.

@Amit Gupta has pointed to library that let the activity slide. Its pretty the same concept like various Navigation drawers does and sets also the theme to translucent. They slide the layout. But thats not exactly what I was looking for, because it slides the top activity to the right and than simply calls finish(). But the underneath activity will not be animated in.

The solution is (and I guess this is was Tumblr does) to write your very own Animation with Animation Objects and animate it step by step. This can be done with ActivityOptions. In my opinion this will be the solution.

Entresol answered 11/10, 2013 at 9:47 Comment(2)
ActivityOptions is added only in API level 16!Autonomic
Yes thats true. You can do it like Pinterst and Path, by using a ViewPager. But than you have to implement your own ActionBar implementation etc. because normally the the ViewPager does not swipe the default actionbar.Entresol
C
0

I wrote a project. It allows you to develop an app navigated by Fragments easily, performs just like Pinterest.

https://github.com/fengdai/FragmentMaster

Maybe it's not the answer what you want. But I hope it's useful to someone else.

Carrico answered 20/6, 2014 at 7:18 Comment(2)
This is not an answer to the question. This should be posted as a comment to OP.Saltigrade
It's a solution for the question.Carrico
Z
0

I was dealing with this one in project I am currently working on and came up with following code. Maybe it's not relevant for you now, but it could help someone new in this post. :)

Basically it's ViewPager implementation as you mention in your answer, but I think it's the simplest and quickest solution to your question. The cons are that it's only for Fragments (could be easily changed for Objects) and if you want to add ActionBar into swipe, you probably end up creating a custom one.

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();
    }
}

}

Here is activity which implements it with another ViewPager as a SlidingMenu. (as 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;
    }

}

}

Zoogeography answered 8/10, 2014 at 18:5 Comment(0)
F
0

This library allows you to open fragments as in iOS and close them using swipes

https://github.com/shikleev/fragula

Fortuna answered 1/3, 2020 at 17:15 Comment(0)
O
0

Navigation library

Here is a library fully integrated with NavComponent.
I'm still working on it, but it's already stable so you can use it in production.

https://github.com/massivemadness/Fragula
Note: it works only with fragments

Outermost answered 30/3, 2022 at 9:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.