Android:垂直ViewPager

有没有办法使一个ViewPager不水平滚动,但垂直?!

您可以使用ViewPager.PageTransformer来给出垂直ViewPager的错觉。 要实现使用垂直滚动而不是水平拖动,必须重写ViewPager的默认触摸事件,并在处理之前交换ViewPager的坐标,例如:

 /** * Uses a combination of a PageTransformer and swapping X & Y coordinates * of touch events to create the illusion of a vertically scrolling ViewPager. * * Requires API 11+ * */ public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { super(context); init(); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { // The majority of the magic happens here setPageTransformer(true, new VerticalPageTransformer()); // The easiest way to get rid of the overscroll drawing that happens on the left and right setOverScrollMode(OVER_SCROLL_NEVER); } private class VerticalPageTransformer implements ViewPager.PageTransformer { @Override public void transformPage(View view, float position) { if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 1) { // [-1,1] view.setAlpha(1); // Counteract the default slide transition view.setTranslationX(view.getWidth() * -position); //set Y position to swipe in from top float yPosition = position * view.getHeight(); view.setTranslationY(yPosition); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } } /** * Swaps the X and Y coordinates of your touch event. */ private MotionEvent swapXY(MotionEvent ev) { float width = getWidth(); float height = getHeight(); float newX = (ev.getY() / height) * width; float newY = (ev.getX() / width) * height; ev.setLocation(newX, newY); return ev; } @Override public boolean onInterceptTouchEvent(MotionEvent ev){ boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); swapXY(ev); // return touch coordinates to original reference frame for any child views return intercepted; } @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(swapXY(ev)); } } 

当然你可以调整这些设置,只要你认为合适。 最后看起来像这样:

VerticalViewPager演示

我有一个解决scheme,分两步为我工作。

  1. onInstantiateItem() ,创build视图并将其旋转-90:

     view.setrotation(-90f) 

    如果您正在使用FragmentPagerAdapter ,那么:

     objFragment.getView().setRoration(-90) 
  2. ViewPager视图旋转90度

     objViewPager.setRotation(90) 

至less可以满足我的要求。

Antoine Merle的垂直视图传呼机实施:

https://github.com/castorflex/VerticalViewPager

对于如何使垂直ViewPager水平工作的人来说,只需要做以下事情:

垂直ViewPager

 public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { super(context); init(); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { setPageTransformer(true, new VerticalPageTransformer()); setOverScrollMode(OVER_SCROLL_NEVER); } private class VerticalPageTransformer implements ViewPager.PageTransformer { @Override public void transformPage(View view, float position) { if (position < -1) { view.setAlpha(0); } else if (position <= 1) { view.setAlpha(1); view.setTranslationX(view.getWidth() * -position); float yPosition = position * view.getHeight(); view.setTranslationY(yPosition); } else { view.setAlpha(0); } } } private MotionEvent swapXY(MotionEvent ev) { float width = getWidth(); float height = getHeight(); float newX = (ev.getY() / height) * width; float newY = (ev.getX() / width) * height; ev.setLocation(newX, newY); return ev; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); swapXY(ev); return intercepted; } @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(swapXY(ev)); } } 

水平ViewPager

 public class HorizontalViewPager extends ViewPager { private GestureDetector xScrollDetector; public HorizontalViewPager(Context context) { super(context); } public HorizontalViewPager(Context context, AttributeSet attrs) { super(context, attrs); xScrollDetector = new GestureDetector(getContext(), new XScrollDetector()); } class XScrollDetector extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return Math.abs(distanceX) > Math.abs(distanceY); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (xScrollDetector.onTouchEvent(ev)) { super.onInterceptTouchEvent(ev); return true; } return super.onInterceptTouchEvent(ev); } } 

有一个垂直ViewPager的新的非官方开源实现:

通知: https //plus.google.com/107777704046743444096/posts/1FTJfrvnY8w

代码: https //github.com/LambergaR/VerticalViewPager/

这个答案略有延伸,帮助我实现了我的要求(垂直视图寻呼机animation类似于在短短 – 在60个字的新闻 )

 public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { super(context); init(); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { // The majority of the magic happens here setPageTransformer(true, new VerticalPageTransformer()); // The easiest way to get rid of the overscroll drawing that happens on the left and right setOverScrollMode(OVER_SCROLL_NEVER); } private class VerticalPageTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.75f; @Override public void transformPage(View view, float position) { 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); // Counteract the default slide transition view.setTranslationX(view.getWidth() * -position); //set Y position to swipe in from top float yPosition = position * view.getHeight(); view.setTranslationY(yPosition); view.setScaleX(1); view.setScaleY(1); } else if (position <= 1) { // [0,1] view.setAlpha(1); // Counteract the default slide transition view.setTranslationX(view.getWidth() * -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); } } } /** * Swaps the X and Y coordinates of your touch event. */ private MotionEvent swapXY(MotionEvent ev) { float width = getWidth(); float height = getHeight(); float newX = (ev.getY() / height) * width; float newY = (ev.getX() / width) * height; ev.setLocation(newX, newY); return ev; } @Override public boolean onInterceptTouchEvent(MotionEvent ev){ boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); swapXY(ev); // return touch coordinates to original reference frame for any child views return intercepted; } @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(swapXY(ev)); } } 

我希望这可能对别人有帮助。

看看这个: https : //github.com/JakeWharton/Android-DirectionalViewPager

或者下面的问题可能会帮助你: 垂直'网页视图'或'ViewPager'

只需改进@Brett和@Salman666的答案即可将坐标(X,Y)正确转换为(Y,X),因为设备显示是矩形的:

 /** * Swaps the X and Y coordinates of your touch event */ @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(swapXY(ev)); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(swapXY(ev)); } private MotionEvent swapXY(MotionEvent ev) { //Get display dimensions float displayWidth=this.getWidth(); float displayHeight=this.getHeight(); //Get current touch position float posX=ev.getX(); float posY=ev.getY(); //Transform (X,Y) into (Y,X) taking display dimensions into account float newPosX=(posY/displayHeight)*displayWidth; float newPosY=(1-posX/displayWidth)*displayHeight; //swap the x and y coords of the touch event ev.setLocation(newPosX, newPosY); return ev; } 

但是,仍然需要做一些事情来提高触摸屏的响应速度。 这个问题可能与@msdark评论@Salman666的答案有关。

有几个声称这样做的开源项目。 他们来了:

  • kaelaela / VerticalViewPager ,2015年10月最新提交,贡献者之一
  • castorflex / VerticalViewPager ,2014年2月最新提交,三位贡献者
  • LambergaR / VerticalViewPager / ,2013年8月最新提交,贡献者之一

这些已被其作者明显弃用:

还有一件事:

  • deskclock应用程序的Android源文件中还有一个名为VerticalViewPager.java的文件,但是它似乎没有正式的支持,因为我找不到它的文档。
 import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { super(context); init(); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { setPageTransformer(true, new VerticalPageTransformer()); setOverScrollMode(OVER_SCROLL_NEVER); } private class VerticalPageTransformer implements PageTransformer { @Override public void transformPage(View view, float position) { int pageWidth = view.getWidth(); int pageHeight = view.getHeight(); if (position < -1) { view.setAlpha(0); } else if (position <= 1) { view.setAlpha(1); view.setTranslationX(pageWidth * -position); float yPosition = position * pageHeight; view.setTranslationY(yPosition); } else { view.setAlpha(0); } } } @Override public boolean onTouchEvent(MotionEvent ev) { ev.setLocation(ev.getY(), ev.getX()); return super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { ev.setLocation(ev.getY(), ev.getX()); return super.onInterceptTouchEvent(ev); } } 

Android源中实际上已经有了一个。 我已经修改它,以更好地工作:

 package com.crnlgcs.sdk.widgets; import android.content.Context; import android.support.v4.view.ViewConfigurationCompat; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; public class VerticalViewPager extends ViewPager { private static final String TAG = "VerticalViewPager"; private static final boolean DEBUG = true; private float mLastMotionX; private float mLastMotionY; private float mTouchSlop; private boolean mVerticalDrag; private boolean mHorizontalDrag; // Vertical transit page transformer private final ViewPager.PageTransformer mPageTransformer = new ViewPager.PageTransformer() { @Override public void transformPage(View view, float position) { final int pageWidth = view.getWidth(); final int pageHeight = view.getHeight(); if (position < -1) { // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 1) { view.setAlpha(1); // Counteract the default slide transition view.setTranslationX(pageWidth * -position); // set Y position to swipe in from top float yPosition = position * pageHeight; view.setTranslationY(yPosition); } else { // This page is way off-screen to the right. view.setAlpha(0); } } }; public VerticalViewPager(Context context) { super(context, null); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); init(); } private void init() { // Make page transit vertical setPageTransformer(true, mPageTransformer); // Get rid of the overscroll drawing that happens on the left and right (the ripple) setOverScrollMode(View.OVER_SCROLL_NEVER); } @Override public boolean onTouchEvent(MotionEvent ev) { final float x = ev.getX(); final float y = ev.getY(); if (DEBUG) Log.v(TAG, "onTouchEvent " + x + ", " + y); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { mLastMotionX = x; mLastMotionY = y; if (!super.onTouchEvent(ev)) return false; return verticalDrag(ev); } case MotionEvent.ACTION_MOVE: { final float xDiff = Math.abs(x - mLastMotionX); final float yDiff = Math.abs(y - mLastMotionY); if (!mHorizontalDrag && !mVerticalDrag) { if (xDiff > mTouchSlop && xDiff > yDiff) { // Swiping left and right mHorizontalDrag = true; } else if (yDiff > mTouchSlop && yDiff > xDiff) { //Swiping up and down mVerticalDrag = true; } } if (mHorizontalDrag) { return super.onTouchEvent(ev); } else if (mVerticalDrag) { return verticalDrag(ev); } } case MotionEvent.ACTION_UP: { if (mHorizontalDrag) { mHorizontalDrag = false; return super.onTouchEvent(ev); } if (mVerticalDrag) { mVerticalDrag = false; return verticalDrag(ev); } } } // Set both flags to false in case user lifted finger in the parent view pager mHorizontalDrag = false; mVerticalDrag = false; return false; } private boolean verticalDrag(MotionEvent ev) { final float x = ev.getX(); final float y = ev.getY(); ev.setLocation(y, x); return super.onTouchEvent(ev); } } 

目前比较活跃的仓库是lsjwzh / RecyclerViewPager 。

  • 最后一次提交12 Aug 2016,16个贡献者,1840颗星
  • 基于RecyclerView

这是一个可垂直滚动的ViewPager实现。很好用RecyclerView和ListView。 国冲/ VerticalViewPager 。

使用ViewPager.PageTransformer使ViewPager垂直滚动,并提供View.OnTouchListener来处理滚动冲突。

 /** * 1.dispatch ACTION_DOWN,ACTION_UP,ACTION_CANCEL to child<br> * 2.hack ACTION_MOVE * * @param v * @param e * @return */ @Override public boolean onTouch(View v, MotionEvent e) { Log.i(TAG, "onTouchEvent " + ", action " + e.getAction() + ", e.rawY " + e.getRawY() + ",lastMotionY " + lastMotionY + ",downY " + downY); switch (e.getAction()) { case MotionEvent.ACTION_DOWN: downY = e.getRawY(); break; case MotionEvent.ACTION_MOVE: if (downY == Float.MIN_VALUE && lastMotionY == Float.MIN_VALUE) { //not start from MOTION_DOWN, the child dispatch this first motion downY = e.getRawY(); break; } float diff = e.getRawY() - (lastMotionY == Float.MIN_VALUE ? downY : lastMotionY); lastMotionY = e.getRawY(); diff = diff / 2; //slow down viewpager scroll Log.e(TAG, "scrollX " + dummyViewPager.getScrollX() + ",basescrollX " + dummyViewPager.getBaseScrollX()); if (dummyViewPager.getScrollX() != dummyViewPager.getBaseScrollX()) { if (fakeDragVp(v, e, diff)) return true; } else { if (ViewCompat.canScrollVertically(v, (-diff) > 0 ? 1 : -1)) { Log.e(TAG, "scroll vertically " + diff + ", move.lastMotionY " + e.getY()); break; } else { dummyViewPager.beginFakeDrag(); fakeDragVp(v, e, diff); adjustDownMotion(v, e); return true; } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (dummyViewPager.isFakeDragging()) { try { dummyViewPager.endFakeDrag(); } catch (Exception e1) { Log.e(TAG, "", e1); } } reset(); break; } return false; } 

垂直ViewPager

这里的其他答案似乎都与他们有一些问题。 即使是推荐的开源项目也没有合并最近的pull请求和错误修复。 然后我发现了一个Google的VerticalViewPager ,他们使用了一些DeskClock应用程序。 我相信使用Google的实现比在这里漂浮的其他答案要多得多。 (Google的版本类似于当前的最佳答案,但更彻底。)

完整的例子

这个例子几乎和我的Basic ViewPager例子完全一样, 只是使用VerticalViewPager类做了一些小的改动。

在这里输入图像说明

XML

为主要活动和每个页面(片段)添加xml布局。 请注意,我们使用VerticalViewPager而不是标准的ViewPager 。 我将包含下面的代码。

activity_main.xml中

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.verticalviewpager.MainActivity"> <com.example.myapp.VerticalViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> 

fragment_one.xml

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/textview" android:textSize="30sp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </RelativeLayout> 

VerticalViewPager的代码不是我的。 它只是一个从谷歌源代码精简版(没有评论)。 看看那个最新的版本。 那里的评论也有助于理解正在发生的事情。

VerticalViewPager.java

 import android.support.v4.view.ViewPager; public class VerticalViewPager extends ViewPager { public VerticalViewPager(Context context) { this(context, null); } public VerticalViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); } @Override public boolean canScrollHorizontally(int direction) { return false; } @Override public boolean canScrollVertically(int direction) { return super.canScrollHorizontally(direction); } private void init() { setPageTransformer(true, new VerticalPageTransformer()); setOverScrollMode(View.OVER_SCROLL_NEVER); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final boolean toIntercept = super.onInterceptTouchEvent(flipXY(ev)); flipXY(ev); return toIntercept; } @Override public boolean onTouchEvent(MotionEvent ev) { final boolean toHandle = super.onTouchEvent(flipXY(ev)); flipXY(ev); return toHandle; } private MotionEvent flipXY(MotionEvent ev) { final float width = getWidth(); final float height = getHeight(); final float x = (ev.getY() / height) * width; final float y = (ev.getX() / width) * height; ev.setLocation(x, y); return ev; } private static final class VerticalPageTransformer implements ViewPager.PageTransformer { @Override public void transformPage(View view, float position) { final int pageWidth = view.getWidth(); final int pageHeight = view.getHeight(); if (position < -1) { view.setAlpha(0); } else if (position <= 1) { view.setAlpha(1); view.setTranslationX(pageWidth * -position); float yPosition = position * pageHeight; view.setTranslationY(yPosition); } else { view.setAlpha(0); } } } } 

MainActivity.java

我只换出了ViewPager VerticalViewPager

 import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; public class MainActivity extends AppCompatActivity { static final int NUMBER_OF_PAGES = 2; MyAdapter mAdapter; VerticalViewPager mPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mAdapter = new MyAdapter(getSupportFragmentManager()); mPager = findViewById(R.id.viewpager); mPager.setAdapter(mAdapter); } public static class MyAdapter extends FragmentPagerAdapter { public MyAdapter(FragmentManager fm) { super(fm); } @Override public int getCount() { return NUMBER_OF_PAGES; } @Override public Fragment getItem(int position) { switch (position) { case 0: return FragmentOne.newInstance(0, Color.WHITE); case 1: // return a different Fragment class here // if you want want a completely different layout return FragmentOne.newInstance(1, Color.CYAN); default: return null; } } } public static class FragmentOne extends Fragment { private static final String MY_NUM_KEY = "num"; private static final String MY_COLOR_KEY = "color"; private int mNum; private int mColor; // You can modify the parameters to pass in whatever you want static FragmentOne newInstance(int num, int color) { FragmentOne f = new FragmentOne(); Bundle args = new Bundle(); args.putInt(MY_NUM_KEY, num); args.putInt(MY_COLOR_KEY, color); f.setArguments(args); return f; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mNum = getArguments() != null ? getArguments().getInt(MY_NUM_KEY) : 0; mColor = getArguments() != null ? getArguments().getInt(MY_COLOR_KEY) : Color.BLACK; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_one, container, false); v.setBackgroundColor(mColor); TextView textView = v.findViewById(R.id.textview); textView.setText("Page " + mNum); return v; } } } 

成品

你应该可以运行应用程序,并在上面的animation中看到结果。

也可以看看

  • 基本的ViewPager示例
  • ViewPager与FragmentPagerAdapter