我将自定义子视图放入ViewPager。我没有任何拦截触摸事件的重要经验,所以这对我来说都是新鲜的。
我已经在SO和其他博客上查看过与此相关的大量问题,但到目前为止,没有一条建议的建议对我有太多帮助。
我的自定义视图有两个彼此重叠的面板。我需要允许用户滑动最前面的面板,而不将这些触摸事件传递给父ViewPager。
我很难理解这一点,因为尽管在我的视图onTouchEvent
中适当地返回false
,我的视图onInterceptTouchEvent
似乎始终被调用{ {1}}。我的理解可能在这里有误,但我认为返回false
应该意味着不应该调用我的观点onTouchEvent
。
以下是我的自定义视图的代码:
package com.example.dm78.viewpagertouchexample;
import android.content.Context;
import android.support.v4.view.GestureDetectorCompat;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MyCustomView extends FrameLayout {
public static final String TAG = MyCustomView.class.getSimpleName();
private GestureDetectorCompat mGestureDetector;
private PanelData mPanelData;
private LinearLayout mFrontPanel;
private float xAdditive;
private float mFrontPanelInitialX = Float.NaN;
private float mDownX = Float.NaN;
private float frontPanelXSwipeThreshold = 100; // arbitrary value
public MyCustomView(Context context, PanelData holder) {
super(context);
mPanelData = holder;
init();
}
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mGestureDetector = new GestureDetectorCompat(getContext(), new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
View view = View.inflate(getContext(), R.layout.my_custom_view, this);
mFrontPanel = (LinearLayout) view.findViewById(R.id.front_panel);
FrameLayout mRearPanel = (FrameLayout) findViewById(R.id.rear_panel);
TextView mFrontTextView = (TextView) findViewById(R.id.front_textView);
TextView mRearTextView = (TextView) findViewById(R.id.rear_textView);
mFrontTextView.setText(mPanelData.frontText);
mRearTextView.setText(mPanelData.rearText);
if (mPanelData.showFrontPanel) {
mFrontPanel.setVisibility(View.VISIBLE);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mFrontPanelInitialX = mFrontPanel.getX();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev) || mGestureDetector.onTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mPanelData.showFrontPanel) {
getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float dX;
long duration;
boolean result = false;
int xDir = 0;
if (mDownX != Float.NaN) {
xDir = (int) Math.abs(event.getRawX() - mDownX);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// for later ACTION_MOVE events, we'll add xAdditive to event.getRawX() to get a dX for animations
xAdditive = mFrontPanel.getX() - event.getRawX();
mDownX = event.getRawX();
result = true;
break;
case MotionEvent.ACTION_MOVE:
// left movement detected
if (xDir < 0) {
// animate panel interactively with new events coming in
// assume we haven't passed the point of no return
dX = event.getRawX() + xAdditive;
duration = 0;
if ((mFrontPanel.getX() + dX) < frontPanelXSwipeThreshold) {
// go ahead and animate the panel away
dX = -mFrontPanel.getWidth();
duration = 200;
}
mFrontPanel.animate().x(dX).setDuration(duration).start();
result = true;
}
break;
case MotionEvent.ACTION_UP:
// test value here is arbitrary
if (xDir < -10) {
int newX;
// if panel has been moved left enough, just animate it away
if (mFrontPanel.getX() < frontPanelXSwipeThreshold) {
newX = -mFrontPanel.getWidth();
}
// otherwise, animate return to initial position
else {
newX = -getContext().getResources().getDisplayMetrics().widthPixels;
}
mFrontPanel.animate().x(newX).setDuration(200).start();
result = true;
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
default:
result = super.onTouchEvent(event) || mGestureDetector.onTouchEvent(event);
}
return result;
}
public static class PanelData {
public String rearText;
public String frontText;
public boolean showFrontPanel;
}
}
目前,在ViewPager中通常不会发生任何事情。我的孩子视图完全忽略了触摸输入。我确定我在这里做了一些愚蠢的事情,但是我对Android SDK的这一部分没有足够的经验来了解它是什么。
我已经在某个博客的建议中抛出GestureDetector.OnGestureListener
,但我还没有找到它的用处,也不知道它对我的启动有何帮助
你能找到我的代码明显错误的东西吗?我甚至走在正确的轨道上吗?
更新2015-09-01:
我发现ViewParent#requestDisallowInterceptTouchEvent(boolean)
方法可能应该在我看来的父母的几个点上调用。似乎onInterceptTouchEvent
可能是设置标志的好地方,onTouchEvent
在ACTION_UP
的情况下可能是取消它的好地方。这显然是不正确的,因为当我滚动到具有用户需要轻扫的前面板的页面时,ViewPager完全停止滚动,并且应用程序没有用户可见的响应。我怎样才能做到这一点?
工作示例app repo:https://gitlab.com/dm78/ViewPagerTouchExample/tree/master
答案 0 :(得分:2)
我的代码存在许多问题。
Math.abs()
的来电。requestDisallowInterceptTouchEvent(boolean)
,以启用/禁用家长吞下我视图中生成的所有事件。在我的视图中使用requestDisallowInterceptTouchEvent(boolean)
,这允许孩子处理事件而不是将视图紧密耦合到显示它的ViewPager
,我认为这是更好的选择,因为这种技术可以应用于(据我所知)任何UI上下文中的任何视图,以获得所需的行为。它不依赖视图的其他元素,了解正在发生的事情的细节,以使解决方案正常运行。
解决上述问题后,只需调整动画如何触发即可实现所需的互动。
我的解决方案仍有一个小问题,但这是另一个问题。
答案 1 :(得分:0)
来自onInterceptTouchEvent(MotionEvent ev)
的文档返回true以从子节点窃取运动事件,并通过onTouchEvent()将它们分派到此ViewGroup。当前目标将收到ACTION_CANCEL事件,此处不会再传递任何消息。
因此,当子视图不应该获取触摸事件时,您希望返回true。默认情况下,MotionEvents从布局堆栈的根目录“传递”到其子视图,直到其中一个没有任何子节点或拦截此事件。
据我了解,您希望在该寻呼机内的某些子视图上执行滑动手势时暂时禁用ViewPager的分页,因此寻呼机不得截取该事件,也不能消耗触摸事件(必须返回false)来自onTouchEvent(MotionEvent ev))。我遇到了类似的问题并this answer帮助了,这可能是解决问题的方法。
答案 2 :(得分:0)
默认情况下,父级在android中的子级之前接收触摸事件。因此,当检测到触摸时,将调用视图寻呼机的onInterceptTouchEvent。
因此,要禁用ViewPager上的触摸,请继承ViewPager,并从onInterceptTouchEvent返回false。例如
public class MyViewPager extends ViewPager {
//constructors n rest of the code here...
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//custom logic here...
return false;
}
}
是的,你是对的,返回false将阻止为该特定视图组调用onTouchEvent。
我希望这很有帮助。干杯:)