安卓系统: 垂直浏览器

有没有办法使 ViewPager不是水平滚动,而是垂直滚动? !

106123 次浏览

我有一个两步就能解决问题的解决方案。

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

    view.setRotation(-90f)
    

    如果你使用的是 FragmentPagerAdapter,那么:

    objFragment.getView().setRotation(-90)
    
  2. Rotate ViewPager view by 90 degree:

    objViewPager.setRotation(90)
    

Works like a charm at least for my requirement.

您可以使用 页面转换器来给出一个垂直 ViewPager的错觉。为了实现垂直滚动,而不是水平拖动,你必须覆盖 ViewPager的默认触摸事件,并在处理它们之前交换 MotionEvent的坐标,例如:

/**
* 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 demo

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

只是对 @ 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的回答的评论有关。

一个轻微的扩展 这个答案帮助我实现了我的要求(垂直视图寻呼机动画类似于在 短片-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));
}
}

我希望这能帮到其他人。

实际上在 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);
}
}

对于那些正在苦苦思索如何让垂直 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));
}


}

水平浏览器

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 实现。可以很好地与回收视图和 ListView.国冲/垂直查看页面一起工作。

使用 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;
}

垂直浏览器

这里的其他答案似乎都有一些问题。甚至推荐的开源项目也没有将最近的请求与 bug 修复合并。然后我发现了一个来自谷歌的 VerticalViewPager,他们使用了一些桌面时钟应用程序。我更相信使用谷歌的实现比其他的答案漂浮在这里更多。(谷歌的版本与当前最受欢迎的答案类似,但更为全面。)

完整例子

这个示例与我的 基本查看页面示例几乎完全相同,只是为了使用 VerticalViewPager类做了一些小的更改。

enter image description here

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>

片段 _ 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的密码不是我的。它只是从 Google 源代码中精简出来的版本(没有注释)。看看那个最新的版本。那里的评论也有助于理解正在发生的事情。

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

我只把 VerticalViewPager换成了 ViewPager

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

结束了

你应该能够运行应用程序,并在上面的动画中看到结果。

参见

即使是我在交换 X 和 Y 使 ViewPager 垂直化时也面临同样的问题。一点也不顺利。

这是因为它过去只有当滑动角度小于7.125度 = arctan (1/8) ,而拦截和14度 = arctan (1/4)触摸时才能工作; 就像原来的 ViewPager 水平一样,当滑动角度小于26.565度 = arctan (1/2) ,而拦截和45度 = arctan (1)触摸时才能工作。

这就是为什么我复制了 Android support-core-ui 的代码,并将值移动到变量中,然后使用反射进行乘法。

请在 < a href = “ https://github.com/deep akmishra/verticalviewpager”rel = “ nofollow norefrer”> https://github.com/deepakmishra/verticalviewpager 找到代码和 README 以及 VerticalViewPager,网址: http://github.com/deep akmishra/VerticalViewPager/blob/master/app/src/main/java/com/viewpager/VerticalViewPager.java”rel = “ nofollow norefrer”> https://github.com/deepakmishra/VerticalViewPager/blob/master/app/src/main/java/com/viewpager/VerticalViewPager.java

更好的方法是将视图页面旋转90度,并使用 straintLayout 来调整位置。

我最终创建了一个新的 DirectionalViewPager,它可以垂直或水平滚动,因为我在这里看到的所有解决方案都有缺陷:

  1. 现有的 VerticalViewPager 实现(由 castorflex 和 LambergaR 实现)

    • 它们基于非常古老的支持库版本。
  2. 坐标交换变换技巧

    • 超滚动条仍然从左/右边缘显示。
    • 页面抛出不能正常工作,因为即使有坐标交换,VelocityTracker.computeCurrentVelocity仍然计算与 X 轴的速度,可能是因为这在内部使用一个本机调用,忽略了坐标交换。
  3. 查看旋转

    • 也需要旋转每个子视图的黑客。
    • 如果你想读取其他东西的坐标,你必须交换坐标轴。