【问题标题】:Android Navigation Drawer Doesn't Pass onTouchEvent to ActivityAndroid Navigation Drawer 不会将 onTouchEvent 传递给 Activity
【发布时间】:2014-03-23 11:26:25
【问题描述】:

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

问题
问题是,activity 本身的onTouchEvent()(以及孩子ListViewonItemClickedListener())没有被调用,因为drawer 消耗了它。 当然,我希望它被称为:)
不用说,我希望答案很简单(甚至是 XML 答案),希望不是通过扩展 Drawer 类(当然,除非它需要这样做)。

更多信息
Activity 的主布局非常简单,基本上是一个ListView 和上面的DrawerLayout(下面是XML)。
Drawer 有一个fragment,因为它是childView(用于片段导航),当然还有ListView 用于Drawer 项目。

我看到很多关于(不完全是)类似问题的问题,常见的答案是在DrawerLayout 和父视图(Activity 的主要内容)上使用onInterceptTouch()requestDisallowInterceptTouchEvent() ) 甚至是抽屉的ListView 上的onTouchEvent()(返回False)。
似乎没有什么能解决问题。

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

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

【问题讨论】:

    标签: android navigation-drawer


    【解决方案1】:

    显然答案有些简单,虽然它确实让你扩展 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
    现在,让我们捕捉打开的或可见的情况,以返回 true(并在抽屉中使用操作)。

    @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)(Google 打算在触摸时滑动抽屉的区域) 将导致返回 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”放在第二个中,以节省更多工作。好的,这里是:

    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;
    }
    

    现在就是这样!希望以后能对自己以外的人有所帮助,呵呵....

    【讨论】:

    • 不错的一个。我想知道是否有更清洁的解决方案(导航抽屉可以释放它不使用的触摸事件,不是吗...)
    • 好吧,我想你可以想出一个。您必须阅读在 Android 中如何转发触摸事件(这可能会很棘手),然后覆盖 Drawer 的 onTouchEvent() 或 onInterceptTouchEvent()。
    • 我不记得为什么它对我不起作用,但我想它有太多副作用......这里有两个链接可以给你一个想法:link 1,@ 987654325@,以及一个解释触摸工作原理的链接how touch works
    • 感谢您的指点。对于我的需要,我可以在onTouchEvent 中使用单行代码:只需执行return isDrawerVisible(mDrawerListView)。 (显然super.onTouchEvent(event) 是该方法的唯一另一行。)
    • @carthurs 是的,它更简单,但有副作用,我将其余代码放在了目的:)
    【解决方案2】:

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

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

    • 每当触摸事件发生抽屉视图(可滑动部分)时,我们都会返回 false。然后,我们的 DrawerLayout 在处理事件时就出局了——事件由我们将 放入 DrawerLayout 相应位置的任何视图来处理。

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

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

    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 );
      }
    }
    

    【讨论】:

      【解决方案3】:

      有了这些答案,我还是遇到了一些麻烦。我可以让motionEvent回到活动中,但是我通过片段或屏幕上的所有内容丢失了onClick侦听器的答案。所以我找到了另一种让一切正常工作的方法(从活动中覆盖 OntouchEvent 时得到答案,并回答 onClick 监听器)

      扩展 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 的弱引用附加)

      这种方式的好处是,您不关心边缘,也不关心开始还是结束。你也不在乎它是打开还是关闭。一切正常。

      【讨论】:

        【解决方案4】:

        我有一个解决方案:

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

        因此,您可以在其中做自己的事情。更重要的一件事:如果你想覆盖 onSingleTapUp() 或其他东西,你应该在 onDown() 中使用 return true 以确保你可以得到其余的 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;
            }
        
        
        }
        

        并设置它:

         mGestureDetector=new GestureDetector(this, new MyGestureListener());
        
            layout_content.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
        
                    return  mGestureDetector.onTouchEvent(event);
                }
            });
        

        【讨论】:

          【解决方案5】:

          为了补充 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;
                  }
              }
          }
          

          }

          【讨论】:

            【解决方案6】:

            对于任何可能不幸遇到像这个问题一样持久的问题的人,我将用我自己的问题案例和解决方案添加到其他人的答案中,希望更少的人会面临这个令人头疼的噩梦。

            需要注意的是,我的解释很可能适用于其父级为 DrawerLayout 的任何可滑动视图(例如,此解决方案仅适用于 DrawerLayout 的子级视图),但我会为了清晰起见,请重温我的经验和辛勤工作。


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

            本质上,发生的事情是每当我开始将MaterialCalendarView 滑动到滑动回到下个月时,我就结束了触发DrawerLayout'的触摸事件拦截器并关闭说DrawerLayout而不是滑动到上个月

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

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

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

            嗯,我在这里回答的事实足以推断出情况并非如此,并且我的onTouchListener 没有像其他人一样被触发。

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

            然后,顿悟。


            我意识到,由于我没有编写自定义视图的经验,我错过了显而易见的事情:我需要做的只是找出 MaterialCalendarView 的位置,获取它的坐标,看看里面是否有任何触摸事件为了调用正确的实现(无论是活动还是默认DrawerLayout)!当然,因为在前者中,onTouchListenerdisables 拦截由DrawerLayout 的触摸事件,这意味着只有MaterialCalendarView 可以处理滑动!就是这么简单!

            然后快速了解MotionEvents,阅读Rect 到底是什么,以及崩溃之间的泥泞,我终于编写了自定义DrawerLayout,它响应了我的滑动MaterialCalendarView 仅使用 Activity 实现并忽略外部的,选择 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)
                }
            }
            

            这确实不是火箭科学,但我认为这是值得展示的东西,因为它对我来说是新的和出乎意料的(毫无疑问还有许多其他人还没有在这里冒险)。尽管如此,我相信这个实现可以忽略嵌入在DrawerLayouts 中的尽可能多的视图。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-01-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-10-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多