Использование Android SlidingPaneLayout с ViewPager
Я пытаюсь использовать SlidingPaneLayout с ViewPager, вот так
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SlidingPaneLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scientific_graph_slidingPaneLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--
The first child view becomes the left pane.
-->
<ListView
android:id="@+id/left_pane"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left" />
<!--
The second child becomes the right (content) pane.
-->
<android.support.v4.view.ViewPager
android:id="@+id/scientific_graph_viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
</android.support.v4.widget.SlidingPaneLayout>
SlidingPaneLayout скользит, когда я тяну от левого края; Тем не менее, я не могу заставить ViewPager скользить, когда я берусь за правый край. Когда я вытягиваю с правого края, он очень слабо скользит, а затем отскакивает назад.
Возможно ли это сделать? Есть лучший способ сделать это?
Я обнаружил, что, перемещая палец вверх и влево, я могу провести пейджер просмотра.
2 ответа
Основной причиной является реализация #onInterceptTouchEvent. Более старая реализация SlidingPaneLayout выполняла вызов #canScroll, который проверял, может ли сенсорная цель прокручиваться, и, если это так, прокручивал сенсорную цель вместо того, чтобы сдвигать панель. Самая последняя реализация выглядит так, как будто она всегда перехватывает событие движения, когда порог перетаскивания превышает наклон, за исключением случая, когда перетаскивание X превышает наклон, а перетаскивание Y превышает перетаскивание X (как отмечено OP).
Одним из решений этой проблемы является копирование SlidingPaneLayout и внесение нескольких изменений, чтобы заставить это работать. Эти изменения:
Измените регистр ACTION_MOVE в #onInterceptTouchEvent, чтобы также проверить #canScroll,
if (adx > slop && ady > adx || canScroll(this, false, Math.round(x - mInitialMotionX), Math.round(x), Math.round(y))) { ... }
Измените окончательную проверку в #canScroll для особого случая ViewPager. Эта модификация также может быть выполнена в подклассе путем переопределения #canScroll, так как она не имеет доступа к частному состоянию.
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { ... /* special case ViewPagers, which don't properly implement the scrolling interface */ return checkV && (ViewCompat.canScrollHorizontally(v, -dx) || ((v instanceof ViewPager) && canViewPagerScrollHorizontally((ViewPager) v, -dx))) } boolean canViewPagerScrollHorizontally(ViewPager p, int dx) { return !(dx < 0 && p.getCurrentItem() <= 0 || 0 < dx && p.getAdapter().getCount() - 1 <= p.getCurrentItem()); }
Вероятно, есть более элегантный способ сделать это, исправив ViewDragHelper, но это то, что Google должен решить в будущем обновлении пакета поддержки. Хаки выше должны заставить макет работать с ViewPager'ами (и другими горизонтально прокручиваемыми контейнерами?).
Основываясь на решении @Brien Colwell, я написал собственный подкласс SlidingPaneLayout, который обрабатывает это для вас, а также добавляет смахивание краев, так что когда пользователь прокручивает далеко вправо, ему не нужно прокручивать весь путь назад. налево, чтобы открыть панель.
Поскольку это подкласс SlidingPaneLayout, вам не нужно изменять какие-либо ссылки в Java, просто убедитесь, что вы создаете экземпляр этого класса (обычно в вашем XML).
package com.ryanharter.android.view;
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.widget.SlidingPaneLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
/**
* SlidingPaneLayout that, if closed, checks if children can scroll before it intercepts
* touch events. This allows it to contain horizontally scrollable children without
* intercepting all of their touches.
*
* To handle cases where the user is scrolled very far to the right, but should still be
* able to open the pane without the need to scroll all the way back to the start, this
* view also adds edge touch detection, so it will intercept edge swipes to open the pane.
*/
public class PagerEnabledSlidingPaneLayout extends SlidingPaneLayout {
private float mInitialMotionX;
private float mInitialMotionY;
private float mEdgeSlop;
public PagerEnabledSlidingPaneLayout(Context context) {
this(context, null);
}
public PagerEnabledSlidingPaneLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PagerEnabledSlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
ViewConfiguration config = ViewConfiguration.get(context);
mEdgeSlop = config.getScaledEdgeSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (MotionEventCompat.getActionMasked(ev)) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = ev.getX();
mInitialMotionY = ev.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
final float x = ev.getX();
final float y = ev.getY();
// The user should always be able to "close" the pane, so we only check
// for child scrollability if the pane is currently closed.
if (mInitialMotionX > mEdgeSlop && !isOpen() && canScroll(this, false,
Math.round(x - mInitialMotionX), Math.round(x), Math.round(y))) {
// How do we set super.mIsUnableToDrag = true?
// send the parent a cancel event
MotionEvent cancelEvent = MotionEvent.obtain(ev);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
return super.onInterceptTouchEvent(cancelEvent);
}
}
}
return super.onInterceptTouchEvent(ev);
}
}