【问题标题】:Swipe Back like Pinterest or Tumblr像 Pinterest 或 Tumblr 一样向后滑动
【发布时间】:2013-10-06 23:25:48
【问题描述】:

有没有人知道 Pinterest 或 Tumblr 是如何在那里实现“向后滑动”方法的。

即在 Pinterest 上,您可以点击新闻提要上的帖子。然后DetailActivity 启动并显示所选帖子的详细信息。您可以按后退按钮返回新闻提要活动,也可以向左滑动(详细信息活动)返回新闻提要活动。

视频:http://youtu.be/eVcSCWetnTA

通常我会使用overridePendingTransition(),但overridePendingTransition() 需要动画(资源ID,如R.anim.foo)。只有当用户进行滑动手势时,Pinterest 和 Tumblr 才会启动动画。它们还支持根据手指移动的某种“逐帧动画”。因此它们会跟踪手指移动的距离,并将过渡动画设置为相应的百分比值。

我知道如何使用带有FragmentTransaction 的“真正的java”动画/AnimatorSet 对象来为片段替换设置动画。使用片段我必须覆盖onCreateAnimator(),但我不知道如何使用活动实现类似的东西。是否有活动的onCreateAnimator()(或类似的东西)?也不确定如何滑动行为,因为它现在没有启动动画,而是逐步更改 Window / Activity / Fragment 或其他任何东西的属性......

有什么建议吗?

编辑: 我在 youtube 上找到了 pinterest 应用程序的视频:http://youtu.be/eVcSCWetnTA 这就是我想要实现的。

我猜 Pinterest 正在使用 Fragments 和 onCreateAnimator() 来实现“向后滑动”。 由于我的应用程序已经在 Activity 中包含 Fragment 和 ChildFragments,如果我可以为 Activity 实现它,对我来说会容易得多。

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


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

【问题讨论】:

  • 您可以按后退按钮返回新闻提要活动,也可以向左滑动(详细信息活动)返回新闻提要活动。 - 你确定这不是简单的ViewPager
  • 不,它不是viewpager,它是一个自定义动画,用于从一个活动切换到另一个活动或Fragmenttransaction,如果他们使用Fragment。但是,我想为活动实施类似的东西。对于 Fragments,我可以覆盖 onCreateAnimator(),但我找不到用于活动的类似方法 ...
  • 为什么不实现某种形式的手势,然后检测手势的起点和终点并计算它是否是向左滑动,然后用某种动画停止活动?
  • 我知道如何检测滑动手势。我更新了我的问题,并添加了一个指向 youtube 视频的链接,该视频演示了我正在寻找的内容
  • 把NavigationDrawer填满整个屏幕怎么办?

标签: android swipe


【解决方案1】:

看来你要找的效果是android开发者网站上ViewPager的样本之一。

查看http://developer.android.com/training/animation/screen-slide.html#depth-page,在深度页面转换器部分。它有一个视频和源代码。

使用ViewPager.PageTransformer,您可以决定从一个页面切换到另一个页面时页面的行为方式。

示例和您链接到的视频之间的唯一区别是左右似乎是颠倒的,但应该是我在问题中链接的YouTube video 上看到的一个很好的起点。必须交换对两个视图的操作。如这段代码所示(mPager.setPageTransformer 的第一个参数应该是reverseDrawingOrder = false)。请注意,中间的 2 个 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);
            }
        }
    }

【讨论】:

    【解决方案2】:

    更新: 修复了该项目的内存使用问题,并将滑动返回样式更改为 iOS 之类。

    我写了一个和 Pinterest 和 tumblr 完全一样的演示,你只需扩展 BaseActivity 就可以得到滑动效果,运行流畅!

    检查这个:https://github.com/chenjishi/SlideActivity

    还有截图:

    【讨论】:

    • 看起来 pinterest 有不同的实现(可能不是基于捕获第一个活动截图?),因为从横向切换到纵向后(在第二个活动中)它仍然可以正常工作。
    • @sandrstar Pinterest 不使用截图,他们所有的可滚动页面都是片段,所以他们使用 ViewPager 向后滑动。Tumblr 使用截图,但是很容易 OutOfMemory,因为截图是高清的太大而无法处理,所以 Pinterest 的方法更好。
    【解决方案3】:

    我发现了一个像 Pinterest 这样基于 SwipeBack 的 GitHub 项目。

    这确实是一个很棒的开源项目,应该可以解决您的问题。它可以根据需要执行,例如通过按返回或简单滑动来转到上一个屏幕。由于这个项目有选项

    1.从左向右滑动

    2。从右向左滑动

    3.从底部滑动到顶部

    https://github.com/Issacw0ng/SwipeBackLayout

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

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

    附加截图:-

    希望这会对你有所帮助。

    【讨论】:

    • 这是一个有趣的项目。但目前在 4.4.4 上有问题。这是我打开的issue
    【解决方案4】:

    我能够在 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;
    
        }
    }
    

    【讨论】:

    • 感谢您提供的精彩示例代码。好的!但我想刷整个活动/窗口。我更新了我的问题,并添加了一个指向 youtube 视频的链接,该视频演示了我在寻找什么
    • 哦...那是一个可怕的 PageViewer。
    • 这是可能的,但我认为他们正在以某种特殊方式使用 Fragments。
    • 感谢@SherifelKhatib 的代码,这非常有效。只需将第二个活动主题设置为 ​​@android:color/transparent
    【解决方案5】:

    我刚刚检查了层次结构查看器。似乎他们正在使用 ViewPager 和上一个活动的屏幕截图。

    【讨论】:

    • ViewPager(s) 不滚动操作栏。我认为这不是 android ViewPager 而是自定义子类,其中包含一些大量自定义。同时,当您在选择包含多张照片的帖子后向下(或向上)滚动时,Facebook 已经实现了类似的功能。我敢打赌这两种实现几乎相同。你能弄清楚 Facebook 使用的是什么吗?
    • @domenicop Facebook 只是在列表视图上绘制文章详细视图。这一切都在同一个活动中完成。
    • @nickes 它与片段重叠
    【解决方案6】:

    我建议做以下事情:

    首先检测用户在设备中所做的手势。可以参考this link

    我不会从上面的链接中复制相关代码,因为我相信这是公认的答案

    其次你可以在这个方法中

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

    按照this question的建议执行以下操作

    他们在那里谈论用动画完成一项活动

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

    希望对你有帮助

    【讨论】:

    • 也许我的问题不清楚。我知道如何检测滑动手势,这不是我要找的。我更新了我的问题,并添加了一个指向 youtube 视频的链接,该视频演示了我正在寻找的内容
    【解决方案7】:

    所以我想我自己找到了解决方案:

    首先: Pinterest 确实使用了带有自定义页面转换器的 ViewPager,就像@frozenkoi 在他的回答中提到的那样。您可以在 pinterest 应用中看到 view pager 的过度滚动边缘效果。

    @Amit Gupta 指出了让活动滑动的库。它与各种导航抽屉的概念非常相似,并且还将主题设置为半透明。他们滑动布局。但这并不完全是我想要的,因为它将顶部活动向右滑动,而不是简单地调用finish()。但是下面的活动不会被动画化。

    解决方案是(我猜这是 Tumblr 所做的)用动画对象编写您自己的动画,并一步一步地为它制作动画。 这可以通过ActivityOptions 完成。 我认为这将是解决方案。

    【讨论】:

    • ActivityOptions 仅在 API 级别 16 中添加!
    • 是的,没错。您可以使用 ViewPager 像 Pinterst 和 Path 一样进行操作。但是您必须实现自己的 ActionBar 实现等,因为通常 ViewPager 不会滑动默认操作栏。
    【解决方案8】:

    我写了一个项目。它使您可以轻松开发由 Fragments 导航的应用程序,就像 Pinterest 一样执行。

    https://github.com/fengdai/FragmentMaster

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

    【讨论】:

    • 这不是问题的答案。这应该作为对 OP 的评论发布。
    • 这是一个问题的解决方案。
    【解决方案9】:

    我正在处理我目前正在从事的项目中的这个,并提出了以下代码。也许它现在与您无关,但它可以帮助这篇文章中的新人。 :)

    基本上它是您在回答中提到的 ViewPager 实现,但我认为这是解决您问题的最简单和最快的解决方案。缺点是它只适用于 Fragments(可以很容易地为 Objects 更改),如果你想将 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;
        }
    
    }
    

    }

    【讨论】:

      【解决方案10】:

      此库允许您像在 iOS 中一样打开片段并使用滑动关闭它们

      https://github.com/shikleev/fragula

      【讨论】:

        猜你喜欢
        • 2022-01-05
        • 1970-01-01
        • 2013-05-02
        • 2014-10-16
        • 2020-06-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多