水平垂直都可滑動的HorizontalVerticalViewPager實現
自定義一個可以在垂直方向上滑動的ViewPager,同時支持水平和垂直方向手動切換和自動切換(根據手勢判斷),垂直方向的實現通過PageTransformer來完成,自動切換功能則涉及到手勢的相關內容。以下是內容簡介:
垂直滑動的ViewPager實現思路
PageTransformer的使用
手勢事件的分發和處理
垂直滑動的ViewPager實現思路
Android的兼容包support-v4提供的ViewPager默認是水平滑動切換的,但是,想要實現垂直滑動,可行的方案還是不少的,下面舉幾個例子:
- 直接拷貝ViewPager源碼,把所有涉及到的水平控制的參數,調整為垂直;
- 旋轉ViewPager的item的角度;
- 設置頁面切換動畫PageTransformer;
當然,還有很多其他的方式來實現,我這里使用的就是比較簡單的PageTransformer,簡單,是因為只需要給水平的ViewPager設置一個PageTransformer就能實現,不用自定義ViewPager。
PageTransformer的使用
關于PageTransformer首先要知道的是,這是一個控制ViewPager頁面滑動動畫的一個類,要給頁面設置各種滑入和滑出的動畫,需要實現PageTransformer接口,實現里面的transformPage(View page,int position)方法,做動畫相關的操作。
先來看看官方的Demo:
對應的代碼如下:
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static final 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);
}
}
}
對應的PageTransformer實現:
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA +
(scaleFactor - MIN_SCALE) /
(1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
關于Demo的具體解讀,參見官網示例。
下面是垂直滑動效果:
對應的PageTransformer實現:
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) {
if (isVertical) {
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
swapXY(ev); // return touch coordinates to original reference frame for any child views
return intercepted;
} else {
return super.onInterceptTouchEvent(ev);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (isVertical) {
return super.onTouchEvent(swapXY(ev));
} else {
return super.onTouchEvent(ev);
}
}
效果如下:
到這里,一個垂直滾動的ViewPager已經實現了。
記住,別忘了把實現的PageTransformer設置給ViewPager:
setPageTransformer(true, new HorizontalVerticalPageTransformer());
關于PageTransformer更多的內容,還可以看看CSDN博客專家們的解讀:
PageTransformer實現個性的ViewPager切換動畫
手勢事件的分發和處理
現在,我要做的是,既可以水平滑動又可以垂直滑動,完全根據手指滑動的方向自動的切換滑動方向,通過按鈕手動切換的這里就不細說了,相信看完自動切換的細節,手動的也就不是什么問題了。
這里的手勢,監聽的是ViewPager的item里面的ImageView控件的,所以,實現一個OnGestureListener,然后,在ImageView的touch事件交給GestureDetector來處理,看看OnGestureListener的代碼:
private class GestureListener implements GestureDetector.OnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
Log.i(getClass().getName(), "onDown-----" + getActionName(e.getAction()));
return false;
}
@Override
public void onShowPress(MotionEvent e) {
Log.i(getClass().getName(), "onShowPress-----" + getActionName(e.getAction()));
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.i(getClass().getName(), "onSingleTapUp-----" + getActionName(e.getAction()));
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.i(getClass().getName(), "onScroll-----"
+ getActionName(e2.getAction()) + ",(" + e1.getX() + "," + e1.getY() + ") ,("
+ e2.getX() + "," + e2.getY() + ")");
return false;
}
@Override
public void onLongPress(MotionEvent e) {
Log.i(getClass().getName(), "onLongPress-----" + getActionName(e.getAction()));
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.i(getClass().getName(),
"onFling-----" + getActionName(e2.getAction()) + ",(" + e1.getX() + "," + e1.getY() + ") ,("
+ e2.getX() + "," + e2.getY() + ")");
float detly_x = e1.getX() - e2.getX();
float detly_y = e1.getY() - e2.getY();
if (Math.abs(detly_x) > Math.abs(detly_y)) {// 水平方向滑動
int offsetPosition = 0;
if (e1.getX() - e2.getX() > FLING_MIN_DISTANCE) {
offsetPosition = 1;
Log.i(getClass().getName(), "onFling----- 向左滑動");
} else if (e2.getX() - e1.getX() > FLING_MIN_DISTANCE) {
offsetPosition = -1;
Log.i(getClass().getName(), "onFling----- 向右滑動");
}
mOnTouchListener.onHorizontalFling(offsetPosition);
} else {// 垂直方向滑動
int offsetPosition = 0;
if (e1.getY() - e2.getY() > FLING_MIN_DISTANCE) {
offsetPosition = 1;
Log.i(getClass().getName(), "onFling----- 向上滑動");
} else if (e2.getY() - e1.getY() > FLING_MIN_DISTANCE) {
offsetPosition = -1;
Log.i(getClass().getName(), "onFling----- 向下滑動");
}
mOnTouchListener.onVerticalFling(offsetPosition);
}
return false;
}
private String getActionName(int action) {
String name = "";
switch (action) {
case MotionEvent.ACTION_DOWN: {
name = "ACTION_DOWN";
break;
}
case MotionEvent.ACTION_MOVE: {
name = "ACTION_MOVE";
break;
}
case MotionEvent.ACTION_UP: {
name = "ACTION_UP";
break;
}
default:
break;
}
return name;
}
}
設置給ImageView:
final GestureDetector detector = new GestureDetector(context, new GestureListener());
iv.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
detector.onTouchEvent(event);
return true;
}
});
當然,PageTransformer也要進行改造:
private class HorizontalVerticalPageTransformer implements PageTransformer {
private static final float MIN_SCALE = 0.75f;
@Override
public void transformPage(View page, float position) {
if (isVertical) {
if (position < -1) {
page.setAlpha(0);
} else if (position <= 1) {
page.setAlpha(1);
// Counteract the default slide transition
float xPosition = page.getWidth() * -position;
page.setTranslationX(xPosition);
//set Y position to swipe in from top
float yPosition = position * page.getHeight();
page.setTranslationY(yPosition);
} else {
page.setAlpha(0);
}
} else {
int pageWidth = page.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
page.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
page.setAlpha(1);
page.setTranslationX(0);
page.setScaleX(1);
page.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
page.setAlpha(1 - position);
// Counteract the default slide transition
page.setTranslationX(pageWidth * -position);
page.setTranslationY(0);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
page.setScaleX(scaleFactor);
page.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
page.setAlpha(0);
}
}
}
}
再看看效果是怎么樣的吧:
只是一個簡單的Demo,還有一些瑕疵,有空再整理,這里暫時奉上不完善的源碼:
運行后,點擊主頁右下角FloatingActionButton即可。