Android导航抽屉不会将onTouchEvent传递给Activity

时间:2014-03-23 11:26:25

标签: android navigation-drawer

我有一个使用Android Activity的{​​{1}}。 当仅使用NavigationDrawer s(像往常一样)时,一切都很完美。 但现在我想在我的应用程序的其他活动中使用此fragment,对于其中一些活动, 我不希望主视图为drawer

问题
问题是,fragment本身的onTouchEvent()(以及孩子的activity} onItemClickedListener()未被调用,因为ListView消耗它。 当然,我希望它被称为:)
毋庸置疑,我希望答案很简单(甚至是XML),希望不要扩展drawer类(除非那是当然的)。

更多信息
Activity的主要布局非常简单,基本上是DrawerListView位于其顶部(在XML下面)。
DrawerLayout有一个Drawer,因为它是fragment(用于片段导航),当然还有childViewListView

我已经看到很多关于(不完全)类似问题的问题,而且经常回答是在Drawer和父视图({1}}上使用onInterceptTouch()requestDisallowInterceptTouchEvent()({ {1}}的主要内容)甚至DrawerLayout(返回False)在抽屉的Activity上。
似乎没有什么可以做到的。

I read this link
似乎在某处使用拦截方法似乎就是答案。但是怎么样?

如果您需要任何代码,请与我们联系。但这是一个非常基本的代码/布局。
谢谢!

6 个答案:

答案 0 :(得分:15)

显然答案有点简单,虽然它确实会让你扩展DrawerLayout并做一些思考,也许会导致一些奇怪的结果(使用LAST 例如,我还没有看到任何,但)。

无论如何,向后看的相关问题有助于理解问题(稍后会解释第一个问题):
1. DrawerLayout prevents call of MainActivity.onTouchEvent()
2. How can I requestDisallowTouchEvents on Android DrawerLayout
3. Set drag margin for Android Navigation Drawer

<强>答案
首先,请注意我在这里举了很多例子。如果你只想要最好的(对我而言),请跳到最后一个 其次,如果某人有足够的声誉,请对第一个链接的问题发表评论并给出这个答案的链接(它可以帮助那个人)。

示例1
好吧,基本上,只需扩展Android的DrawerLayout并将onTouchEvent()替换为:

@Override
public boolean onTouchEvent(MotionEvent arg0) {
    super.onTouchEvent(arg0);
    return false;
}

此解决方案将执行任何操作,但不会在幻灯片上打开抽屉,只会打开菜单点击等。此外,它会在抽屉打开时转发点击次数 例如,触摸它之外不会关闭它,而是点击后面的任何内容(例如ListView)。不要再努力......

示例2
现在,让我们捕捉开放的OR可见情况,返回true(并在Drawer处消耗动作)。

@Override
public boolean onTouchEvent(MotionEvent arg0) {
    super.onTouchEvent(arg0);

    if(isDrawerOpen(findViewById(R.id.list_slidermenu)) || 
            isDrawerVisible(findViewById(R.id.list_slidermenu))){
        return true;
    }

    return false;
}

此解决方案更好,因为当抽屉打开或甚至可见时(滑动开始...),它可以防止抽屉后面的咔嗒声。但触摸滑动它仍然无法正常工作。

示例3
好吧,让我们分开案例吧。在抽屉的边距内触摸(MotionEvent.ACTION_DOWN)(谷歌在触摸时希望滑动抽屉的区域) 将导致返回True以使用该操作,而其他人将转发该事件(返回False)。

@Override
public boolean onTouchEvent(MotionEvent arg0) {
    super.onTouchEvent(arg0);
    float edge = 30;//that's for a left drawer obviously. Use <parentWidth - 30> for the right one.
    View mDrawerListView = findViewById(R.id.drawer_listview);

    if(isDrawerOpen(mDrawerListView) || 
            isDrawerVisible(mDrawerListView)){
        return true;
    } else if(arg0.getAction() == MotionEvent.ACTION_DOWN && arg0.getX() > edge){
        return false;
    }

    return true;
}

请注意,我使用了30dp。这就是我发现的边缘(虽然在其中一个链接中据说是20 ....)。

嗯,根据Android的说法,下一个例子当然是决定什么是这个边缘(参见上面的代码)值。我们不想 使用可能改变的数字或其他。

新问题
所以现在第一个链接应该派上用场。它“破解”抽屉代码以获得抽屉边缘/ megin数。但是,它对我不起作用,因为找不到那些确切的字段名称 我运行mDrawerLayout.getClass()。getField()返回所有字段,但没有任何运气找到我们想要的东西。任何人?

最后一个示例 - 完整代码
好的,看看第3个例子,在理解了我做了什么后,我们可以通过扩展onFinishInflate()方法并将其保存为全局变量来加快速度。 这个CustomDrawerLayout供以后使用。我们也可以将第一个“if”放在第二个“if”中以节省更多的工作。好的,这里是:

View mDrawerListView;
...

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    mDrawerListView = findViewById(R.id.drawer_listview);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    super.onTouchEvent(event);

    if(event.getX() > 30 && event.getAction() == MotionEvent.ACTION_DOWN){
        if(isDrawerOpen(mDrawerListView) || isDrawerVisible(mDrawerListView)){
            return true;
        } else{
            return false;
        }
    }

    return true;
}

现在就是这样!希望它能帮助未来的人,呵呵......

答案 1 :(得分:1)

在处理同样的问题时,我受到guy_m's answer的启发,并将他的建议归结为以下解决方案。

同样,它等于扩展DrawerLayout并覆盖onInterceptTouchEvent()。逻辑很简单:

  • 每当触摸事件发生关闭抽屉视图(可滑动部分)时,我们返回false。然后我们的DrawerLayout在处理事件时不在游戏中 - 事件由我们在相应位置将放入 DrawerLayout的任何视图处理。

  • 另一方面,当事件在抽屉视图内发生时,我们委托super.onInterceptTouchEvent()来决定如何处理事件。这样抽屉就会像以前那样在触摸手势上自动滑入和滑出。

以下代码示例适用于DrawerLayout,其抽屉视图位于右侧(android:gravity =“right”)。应该很明显如何修改它以涵盖左侧抽屉的情况。

public class CustomDrawerLayout extends DrawerLayout
{
  @Override
  public boolean onInterceptTouchEvent( MotionEvent event )
  {
    final View drawerView = getChildAt( 1 );
    final ViewConfiguration config = ViewConfiguration.get( getContext() );
    // Calculate the area on the right border of the screen on which
    // the DrawerLayout should *always* intercept touch events.
    // In case the drawer is closed, we still want the DrawerLayout
    // to respond to touch/drag gestures there and reopen the drawer!
    final int rightBoundary = getWidth() - 2 * config.getScaledTouchSlop();

    // If the drawer is opened and the event happened
    // on its surface, or if the event happened on the
    // right border of the layout, then we let DrawerLayout
    // decide if it wants to intercept (and properly handle)
    // the event.
    // Otherwise we disallow DrawerLayout to intercept (return false),
    // thereby letting its child views handle the event.
    return ( isDrawerOpen( drawerView ) && drawerView.getLeft() <= event.getX()
            || rightBoundary <= event.getX() )
            && super.onInterceptTouchEvent( event );
  }
}

答案 2 :(得分:1)

有了这些答案,我仍然遇到一些麻烦。我可以将motionEvent恢复到活动状态,但我会按片段或屏幕上的所有内容丢失onClick侦听器的答案。因此,我找到了使一切正常工作的另一种方法(从活动覆盖OntouchEvent时获取答案,并对onClick Listener进行回答)

扩展DrawerLayout并覆盖此方法:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if(super.onInterceptTouchEvent(ev)) return true;
    else {
        Activity activity = AppContext.getCurrentActivity();
        return activity.onTouchEvent(ev);
    }
}

如果抽屉需要运动事件,请让它处理。如果没有,则将事件自己传递给活动。 (AppContext.getCurrentActivity是您当前活动的东西,例如,您可以将活动作为对薄板布局OnCreate的弱引用)

用这种方法的好处是,您不必关心边缘,也不必关心起始或结束。而且您也不关心它是打开还是关闭。一切正常。

答案 3 :(得分:0)

我有一个解决方案:

在屏幕布局上设置OnTouchListener(通常是DrawerLayout的第一个子视图)并将TouchEvent传输到自定义GestureDetector

所以,你可以在里面做自己的事情。另一个重要的事情是:如果您要覆盖onSingleTapUp()或其他内容,则应return true onDown() MotionEvent以确保您可以让其余onSingleTapUp()进行private class MyGestureListener implements GestureDetector.OnGestureListener{ @Override public boolean onDown(MotionEvent e) { return true; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { // do your own things return true; } @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; } } 1}}工作。

 mGestureDetector=new GestureDetector(this, new MyGestureListener());

    layout_content.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            return  mGestureDetector.onTouchEvent(event);
        }
    });

并设置它:

android:fitSystemWindows="true"

答案 4 :(得分:0)

要添加到guy_m的答案,这是我从右侧打开的抽屉的实现,包括构造函数,以便在布局编辑器中可以查看,并且还考虑用户从边缘点滑过的时间:

public class CustomDrawerLayout extends DrawerLayout {
View mDrawerListView;
float edge;
int holddown = 0;
static final String TAG = CustomDrawerLayout.class.getSimpleName();

public CustomDrawerLayout(@NonNull Context context) {
    super(context);
    setscreendimensionvals(context);
}

public CustomDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    setscreendimensionvals(context);
}

public CustomDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setscreendimensionvals(context);
}

private void setscreendimensionvals(Context context){
    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
    /*((Activity) context).getWindowManager()
            .getDefaultDisplay()
            .getMetrics(displayMetrics); */
    int width = displayMetrics.widthPixels;
    float density = displayMetrics.density;
    edge = width - (30 * density); // 30 is the edge of the screen where the navigation drawer comes out
    Log.d(TAG,"edge: " + edge);
    Log.d(TAG,"width: " + width);
}
@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    mDrawerListView = findViewById(R.id.drawerconstraint_overworld);
}

@Override
public boolean onTouchEvent(MotionEvent event){
    super.onTouchEvent(event); // need to add action up and a local variable to detect when lifted finger
    //Log.d(TAG,"point: " + event.getX());
    if(event.getX() >= edge && (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE)){
        holddown = 1;
        //Log.d(TAG,"hold down");
    }

    if(event.getAction() == MotionEvent.ACTION_UP){
        holddown = 0;
        //Log.d(TAG,"hold up");
    }

    if(holddown == 1){
        return  true;
    }else{
        if(isDrawerOpen(mDrawerListView) || isDrawerVisible(mDrawerListView)){
            return true;
        } else{
            return false;
        }
    }
}

}

答案 5 :(得分:0)

对于可能不幸遇到这种问题的人来说,我会用我自己的问题案例和解决方案添加到其他人的答案中,希望更少的人会面临这个头疼的噩梦。

应注意的是,我的解释很可能适用于任何可滑动视图其父视图 DrawerLayout(例如,此解决方案仅适用于 DrawerLayout 的子视图) ,但为了清楚起见,我将重温我的经验和我的辛劳。


就我而言,我需要在 MaterialCalendarView 中有一个 DrawerLayout (3rd-party CalendarView on steroids),右侧有一个 NavigationView(即 "android:gravity"="end") .实施视图层次结构后不久,我意识到我的 NavigationViewMaterialCalendarView 的滑动事件之间存在冲突。

本质上,当我开始向向右滑动MaterialCalendarView以便滑动回下个月时,我结束了触发 DrawerLayout'的触摸事件拦截器并关闭所述 DrawerLayout 而不是滑动到上个月。< /p>

所以,解决方案应该很简单,不是吗?在 onTouchListener 上设置一个 MaterialCalendarView,调用 requestDisallowInterceptTouchEvent(),然后就结束了——类似于在视图托管活动中:

calendar.setOnTouchListener { _, motionEvent ->
            when(motionEvent.action) {
                MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
                    drawerLayout.requestDisallowInterceptTouchEvent(true)
                }
            }
            true
        }

...你应该准备好了吗?

好吧,我在这里回答的事实足以推断出事实并非如此,而且我的 onTouchListener 没有像其他人一样被触发。< /p>

在广泛搜索此线程并尝试遵循每个人的建议之后,我发现所提供的解决方案对于那些只想“排除”视图不被 {{1} } 的触摸事件拦截器。一些想法完全瘫痪了我的触摸事件基础设施,而其他想法只是给了我更多相同的行为。我遇到了障碍,不知道该怎么办。

然后,顿悟。


我意识到,由于我在编写自定义视图方面缺乏经验,我错过了一个显而易见的事情:我需要做的只是找出 DrawerLayout 的位置,获取其坐标,然后查看是否有任何触摸事件是为了调用正确的实现(无论是 Activity 还是默认的 MaterialCalendarView 一个)!而且,当然,因为在前者中,DrawerLayout 禁用 onTouchListener 对触摸事件的拦截,这意味着只有 DrawerLayout 可以处理滑动!原来如此简单!

然后快速学习 MaterialCalendarViews,阅读 MotionEvent 到底是什么,以及崩溃之间的泥泞,我终于编写了自定义 Rect它仅使用 Activity 实现响应我在 DrawerLayout 上的滑动,而忽略了外部的滑动,选择了 MaterialCalendarView 触摸拦截器:

DrawerLayout

这真的不是火箭科学,但我认为值得展示,因为它对我来说是新的和出乎意料的(毫无疑问还有很多其他人还没有在这里冒险)。尽管如此,我相信这种实现可能会忽略嵌入在 class EventCalendarDrawerLayout : DrawerLayout { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) lateinit var calendar: MaterialCalendarView lateinit var drawer: View override fun onFinishInflate() { super.onFinishInflate() drawer = getChildAt(1) calendar = findViewById(R.id.event_calendar) } override fun onInterceptTouchEvent(event: MotionEvent): Boolean { val rect = Rect() calendar.getGlobalVisibleRect(rect) // get the calendar rect positions // respond to proper motions and forward events contained inside the calendar's rect only if((event.action == MotionEvent.ACTION_MOVE || event.action == MotionEvent.ACTION_DOWN) && rect.contains(event.x.roundToInt(), event.y.roundToInt())) { return (context as Activity).onTouchEvent(event) } // otherwise return the default intercept touch event response return super.onInterceptTouchEvent(event) } } 中的尽可能多的视图。