onInterceptTouchEvent
残念ながら、前述の理由により、SlidingUpPanelLayout のメソッドに依存することはできません。子ビューのonTouchEvent
メソッドが を返すtrue
と、onInterceptTouchEvent
は呼び出されなくなります。
私の解決策は少し複雑ですが、探しているもの (と思います) を正確に達成することができます。1 回のタッチ/ドラッグ イベントにより、パネルが所定の位置にドラッグされ、所定の位置に配置されると、子ビューのスクロールが続行されます。同様に、下にドラッグする場合、単一のタッチ/ドラッグ イベントで子ビューをスクロールできます。スクロールが完了すると、パネルを下にドラッグし始めます。
2015-04-12更新 SlidingUpPanelLayout コードのバージョン 3.0.0 に更新しました。また、ScrollViews だけでなく、ListViews も考慮します。
1)res/
SlidingUpPanel のライブラリ プロジェクトのフォルダーで、 を開き、
追加しattrs.xml
ます。
<attr name="scrollView" format="reference" />
これを使用して、パネルが所定の位置にドラッグされると、タッチ イベントを奪う単一の子ビューを識別します。レイアウト xml ファイルで、次を追加できます。
sothree:scrollView="@+id/myScrollView"
または、scrollView の ID が何であれ。sothree:dragView
また、ビュー全体をドラッグできるように、ID を宣言しないようにしてください。
残りの手順はすべてSlidingUpPanelLayout.java
...
2)
次の変数を宣言します。
View mScrollView;
int mScrollViewResId = -1;
boolean isChildHandlingTouch = false;
float mPrevMotionX;
float mPrevMotionY;
3)コンストラクターで、が設定された直後にmDragViewResId
、次の行を追加します。
mScrollViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_scrollView, -1);
4)
にonFinishInflate
、次のコードを追加します。
if (mScrollViewResId != -1) {
mScrollView = findViewById(mScrollViewResId);
}
5)
次のメソッドを追加します。
private boolean isScrollViewUnder(int x, int y) {
if (mScrollView == null)
return false;
int[] viewLocation = new int[2];
mScrollView.getLocationOnScreen(viewLocation);
int[] parentLocation = new int[2];
this.getLocationOnScreen(parentLocation);
int screenX = parentLocation[0] + x;
int screenY = parentLocation[1] + y;
return screenX >= viewLocation[0] &&
screenX < viewLocation[0] + mScrollView.getWidth() &&
screenY >= viewLocation[1] &&
screenY < viewLocation[1] + mScrollView.getHeight();
}
6)を
取り外しonInterceptTouchEvent
ます。
7)次のように
変更onTouchEvent
します。
public boolean onTouchEvent(MotionEvent ev) {
if (!isEnabled() || !isTouchEnabled()) {
return super.onTouchEvent(ev);
}
try {
mDragHelper.processTouchEvent(ev);
final int action = ev.getAction();
boolean wantTouchEvents = false;
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_UP: {
final float x = ev.getX();
final float y = ev.getY();
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
View dragView = mDragView != null ? mDragView : mSlideableView;
if (dx * dx + dy * dy < slop * slop &&
isDragViewUnder((int) x, (int) y) &&
!isScrollViewUnder((int) x, (int) y)) {
dragView.playSoundEffect(SoundEffectConstants.CLICK);
if ((PanelState.EXPANDED != mSlideState) && (PanelState.ANCHORED != mSlideState)) {
setPanelState(PanelState.ANCHORED);
} else {
setPanelState(PanelState.COLLAPSED);
}
break;
}
break;
}
}
return wantTouchEvents;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
8)
次のメソッドを追加します。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Identify if we want to handle the touch event in this class.
// We do this here because we want to be able to handle the case
// where a child begins handling a touch event, but then the
// parent takes over. If we rely on onInterceptTouchEvent, we
// lose control of the touch as soon as the child handles the event.
if (mScrollView == null)
return super.dispatchTouchEvent(ev);
final int action = MotionEventCompat.getActionMasked(ev);
final float x = ev.getX();
final float y = ev.getY();
if (action == MotionEvent.ACTION_DOWN) {
// Go ahead and have the drag helper attempt to intercept
// the touch event. If it won't be dragging, we'll cancel it later.
mDragHelper.shouldInterceptTouchEvent(ev);
mInitialMotionX = mPrevMotionX = x;
mInitialMotionY = mPrevMotionY = y;
isChildHandlingTouch = false;
} else if (action == MotionEvent.ACTION_MOVE) {
float dx = x - mPrevMotionX;
float dy = y - mPrevMotionY;
mPrevMotionX = x;
mPrevMotionY = y;
// If the scroll view isn't under the touch, pass the
// event along to the dragView.
if (!isScrollViewUnder((int) x, (int) y))
return this.onTouchEvent(ev);
// Which direction (up or down) is the drag moving?
if (dy > 0) { // DOWN
// Is the child less than fully scrolled?
// Then let the child handle it.
if (isScrollViewScrolling()) {
isChildHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
// Was the child handling the touch previously?
// Then we need to rejigger things so that the
// drag panel gets a proper down event.
if (isChildHandlingTouch) {
// Send an 'UP' event to the child.
MotionEvent up = MotionEvent.obtain(ev);
up.setAction(MotionEvent.ACTION_UP);
super.dispatchTouchEvent(up);
up.recycle();
// Send a 'DOWN' event to the panel. (We'll cheat
// and hijack this one)
ev.setAction(MotionEvent.ACTION_DOWN);
}
isChildHandlingTouch = false;
return this.onTouchEvent(ev);
} else if (dy < 0) { // UP
// Is the panel less than fully expanded?
// Then we'll handle the drag here.
if (mSlideOffset < 1.0f) {
isChildHandlingTouch = false;
return this.onTouchEvent(ev);
}
// Was the panel handling the touch previously?
// Then we need to rejigger things so that the
// child gets a proper down event.
if (!isChildHandlingTouch) {
mDragHelper.cancel();
ev.setAction(MotionEvent.ACTION_DOWN);
}
isChildHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
} else if ((action == MotionEvent.ACTION_CANCEL) ||
(action == MotionEvent.ACTION_UP)) {
if (!isChildHandlingTouch) {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
if ((mIsUsingDragViewTouchEvents) && (dx * dx + dy * dy < slop * slop))
return super.dispatchTouchEvent(ev);
return this.onTouchEvent(ev);
}
}
// In all other cases, just let the default behavior take over.
return super.dispatchTouchEvent(ev);
}
9)次のメソッドを追加して、scrollView がまだスクロールしているかどうかを判断します。ScrollView と ListView の両方のケースを処理します。
/**
* Computes the scroll position of the the scrollView, if set.
* @return
*/
private boolean isScrollViewScrolling() {
if (mScrollView == null)
return false;
// ScrollViews are scrolling when getScrollY() is a value greater than 0.
if (mScrollView instanceof ScrollView) {
return (mScrollView.getScrollY() > 0);
}
// ListViews are scrolling if the first child is not displayed, or if the first child has an offset > 0
else if (mScrollView instanceof ListView) {
ListView lv = (ListView) mScrollView;
if (lv.getFirstVisiblePosition() > 0)
return true;
View v = lv.getChildAt(0);
int top = (v == null) ? (0) : (-v.getTop() + lv.getFirstVisiblePosition() * lv.getHeight());
return top > 0;
}
return false;
}
10) (オプション) 次のメソッドを追加して、実行時に scrollView を設定できるようにします (つまり、パネルにフラグメントを配置し、フラグメントの子にスクロールする ScrollView/ListView がある場合)。
public void setScrollView(View scrollView) {
mScrollView = scrollView;
}
これで、このクラス内からタッチ イベントの処理を完全に管理できるようになりました。mScrollView
パネルを上にドラッグしていて、完全に所定の位置にスライドする場合は、ドラッグをキャンセルしてから、子で新しいタッチをスプーフィングします。子をスクロールして一番上に到達すると、子の「up」イベントをスプーフィングし、ドラッグの新しいタッチをスプーフィングします。これにより、他の子ウィジェットでのタップ イベントも許可されます。
既知
の問題 私たちがスプーフィングしている「up」/「down」イベントは、scrollView の子要素で意図せずにクリック イベントをトリガーする可能性があります。