像Pinterest或Tumblr一样向后滑动

42
有人知道Pinterest或Tumblr是如何实现他们的“滑动返回”方法的吗?
例如,在Pinterest上,您可以在新闻动态中点击一篇文章。然后启动DetailActivity并显示所选帖子的详细信息。然后,您可以按返回按钮返回到新闻动态活动,或者您可以向左滑动(详细信息活动)以返回到新闻动态活动。
视频:http://youtu.be/eVcSCWetnTA 通常情况下,我会使用overridePendingTransition(),但overridePendingTransition()需要动画(类似于R.anim.foo的资源ID)。Pinterest和Tumblr仅在用户进行滑动手势时才开始动画。它们还支持某种“逐帧动画”,根据手指移动来跟踪手指移动的距离,并将过渡动画转换为相应的百分比值。
我知道如何使用“真正的Java”Animation / AnimatorSet对象与FragmentTransaction来动画替换片段。对于片段,我必须覆盖onCreateAnimator(),但我不知道如何使用Activities实现这样的行为。是否有一个onCreateAnimator()(或类似的东西)用于Activities?也不确定如何处理滑动行为,因为它现在没有开始动画,而是更多的一步一步的窗口/活动/片段或其他属性更改...
有什么建议吗?
编辑: 我在YouTube上找到了Pinterest应用程序的视频:http://youtu.be/eVcSCWetnTA 这就是我想要实现的。
我猜Pinterest正在使用片段和onCreateAnimator()来实现“滑动返回”。由于我的应用程序已经在活动中具有片段和子片段,因此如果我可以为活动实现它,那将会更容易。

再说一遍:我知道如何检测滑动手势,这不是我的问题。请观看YouTube视频:http://youtu.be/eVcSCWetnTA


更新: 我创建了一个小库,它的行为并不完全像Pinterest或Tumblr的实现,但对于我的应用程序来说,这似乎是一个好的解决方案:https://github.com/sockeqwe/SwipeBack?source=c


你可以点击返回按钮以返回新闻动态活动,或者向左滑动(详细信息活动)以回到新闻动态活动。- 你确定那不是一个简单的ViewPager吗? - user
不,这不是一个ViewPager,而是一种自定义动画,用于从一个Activity切换到另一个Activity或FragmentTransaction(如果它们使用Fragment)。但是我想为Activity实现类似的功能。对于Fragment,我可以重写onCreateAnimator()方法,但我找不到类似的方法适用于Activity... - sockeqwe
为什么不实现某种手势,然后检测手势的起始点和结束点,并计算是否是向左滑动,然后使用某种动画停止活动? - Armand
我知道如何检测滑动手势。我已经更新了我的问题,并添加了一个链接到一个YouTube视频,展示了我正在寻找的内容。 - sockeqwe
使用NavigationDrawer填充整个屏幕怎么样? - user2606414
我认为那样做不会产生期望的效果。我的意思是,我可以使用SlidingMenu或其他实现方式将整个窗口向左滑动,但我看不到在滑动时让前一个活动出现在屏幕上的方法。 - sockeqwe
11个回答

23
看起来你想要的效果是 Android 开发者网站中 ViewPager 的示例之一。请查看 http://developer.android.com/training/animation/screen-slide.html#depth-page,在“深度页面转换器”部分中有视频和源代码。使用 ViewPager.PageTransformer,您可以决定页面在切换时如何行为。
唯一的区别是样例和你链接的视频似乎左右颠倒,但对于我在问题中链接的YouTube视频应该是一个很好的起点。两个视图上的操作需要交换。如此代码所示(mPager.setPageTransformer的第一个参数应该是reverseDrawingOrder= false)。请注意,中间的两个if部分被交换了,而position变量的处理略有不同以切换侧面。弹性效果留作练习。完成后请分享!
    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);
                    }
            }
    }

如果示例页面消失了,这里是该部分的原始代码:

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

8
更新:已解决该项目的内存使用问题,并将幻灯片回退样式更改为类似于iOS的样式。

enter image description here

我写了一个演示,就像Pinterest和Tumblr一样,你只需扩展BaseActivity,就能获得平稳的滑动返回效果!请查看:https://github.com/chenjishi/SlideActivity,还有截图:enter image description here

看起来Pinterest有不同的实现方式(可能不是基于捕获第一个活动截图?),因为在从横屏切换到竖屏后(在第二个活动中),它仍然可以正常工作。 - sandrstar
2
@sandrstar Pinterest不使用截图,他们所有可滚动页面都是片段,因此他们使用ViewPager向后滑动。Tumblr使用截图,但很容易出现OutOfMemory错误,因为高清截图太大而无法处理,因此Pinterest的方法更好。 - Jishi Chen

7

我找到了一个基于SwipeBack的GitHub项目,类似Pinterest。

这是一个非常好的开源项目,可以解决你的问题。它像你需要的那样,通过按下后退键或轻扫实现返回上一个屏幕。此项目还有选项:

1. 从左向右滑动

2. 从右向左滑动

3. 从底部向上滑动

https://github.com/Issacw0ng/SwipeBackLayout

您还可以从Google Play安装此演示应用程序。

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

附带截图:-

enter image description here

希望这可以帮助您。


这是一个有趣的项目,但目前在4.4.4上存在问题。这是我关于它开启的问题 - Gal Bracha

3

我在15分钟内完成了这个任务,对于一个刚开始接触的人来说还不错。如果你花一些时间,也许能够进行优化。

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;

    }
}

感谢您提供的精彩示例代码。很不错!但是我想要滑动整个Activity / Window。我已经更新了我的问题,并添加了一个链接到一个YouTube视频,演示了我正在寻找的内容。 - sockeqwe
1
哦...那是一个该死的 PageViewer。 - Sherif elKhatib
那可能是可能的,但我认为他们正在以某种特殊方式使用片段。 - sockeqwe
感谢@SherifelKhatib的代码,这个完美地运行了。只需将第二个活动主题设置为<item name="android:windowBackground">@android:color/transparent</item>即可。 - Srikanth Roopa

2

我刚刚使用层级查看器进行了检查。看起来他们正在使用ViewPager与前一个活动的屏幕截图。


ViewPager不会滚动操作栏。我认为这不是Android ViewPager,而是自定义的子类,并在其中进行了一些重度定制。与此同时,Facebook实现了类似的功能,当您在选择带有多张照片的帖子后向下(或向上)滚动时。我敢打赌这两个实现几乎相同。你能想出Facebook在使用什么吗? - doplumi
@domenicop Facebook只是在列表视图上绘制文章详细视图。这一切都在同一个活动中完成。 - Dmitry Ryadnenko
@nickes 它重叠了片段。 - Ignacio Garat

1
我建议按照以下步骤进行:
首先检测用户在设备上所做的手势。您可以参考此链接
我不会从上面的链接中复制相关代码,因为我认为那是被接受的答案。
其次,您可以在此方法中。
public void onSwipeLeft() {
    Toast.makeText(MyActivity.this, "left", Toast.LENGTH_SHORT).show();
}

按照此问题的建议进行以下操作

他们在这里谈论使用动画结束一个活动

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

希望这可以帮助你。

也许我的问题没有表达清楚。我知道如何检测滑动手势,但这不是我想要的。我已经更新了我的问题,并添加了一个链接到一个YouTube视频,展示了我想要的内容。 - sockeqwe

1
我想我自己找到了解决方案:
首先: Pinterest确实使用了一个带有自定义页面转换器的ViewPager,就像@frozenkoi在他的答案中提到的那样。您可以在Pinterest应用程序中看到视图页面的超滚边缘效果。
@Amit Gupta指出了一个让活动滑动的库。它与各种导航抽屉的概念非常相似,并且还将主题设置为半透明。它们滑动布局。但这不是我要找的,因为它会将顶部活动向右滑动,然后简单地调用finish()。但下面的活动不会进行动画处理。
解决方案是(我想这就是Tumblr所做的)使用Animation Objects编写自己的动画,并逐步进行动画处理。 这可以通过ActivityOptions来完成。 我认为这将是解决方案。

ActivityOptions 仅在 API 级别 16 中添加! - Maksim Sorokin
2
是的,这是真的。您可以像Pinterst和Path一样使用ViewPager来实现它。但是,您必须实现自己的ActionBar实现等,因为通常ViewPager不会滑动默认的ActionBar。 - sockeqwe

0

我在目前正在工作的项目中处理了这个问题,并想出了以下代码。也许现在对你来说不相关,但它可能会帮助这篇文章中的新手。 :)

基本上,它是ViewPager的实现,就像你在答案中提到的那样,但我认为这是你问题的最简单和最快速的解决方案。缺点是它只适用于片段(可以轻松更改为对象),如果您想将ActionBar添加到滑动中,您可能最终会创建一个自定义的ActionBar。

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

}

这里有一个实现了另一个ViewPager作为SlidingMenu的活动。(作为额外内容)

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

0

我写了一个项目。它允许您轻松地开发由片段导航的应用程序,就像Pinterest一样。

https://github.com/fengdai/FragmentMaster

也许这不是你想要的答案,但我希望它对其他人有用。


这不是问题的答案。应该将其作为对原帖的评论发布。 - Fr0zenFyr
这是一个问题的解决方案。 - Feng Dai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接