【问题标题】:How to show the DrawerLayout when sliding from left to right, no matter where?无论在哪里,从左向右滑动时如何显示DrawerLayout?
【发布时间】:2016-08-11 18:03:23
【问题描述】:

背景

google 引入了 DrawerLayout,当您点击操作栏的“向上”按钮时,它会在屏幕左侧区域显示一个菜单。

由于 actionBarSherlock 尚不支持该库,因此已经有一种方法可以使用 this project 来克服它。

它已经在许多应用上提供了变体:currents、gmail、hangouts、youtube...

问题

在“currents”应用(和 youtube)上,当用户从左向右滑动(最左侧)页面时,会出现 DrawerLayout,无论手指从何处开始触摸 .

我怎样才能达到同样的效果?也许我应该使用onInterceptTouchEvent

除了this link(也可以和this one)之外,没有太多关于可以做什么很酷的事情的文档和教程。他们说(在“让用户快速浏览”部分)左侧大约 20dp 用于此功能,但我可以看到“电流”适用于更大的区域。

这个库似乎还没有完全完成,所以布局 xml 文件甚至无法在可视化 UI 编辑器中显示...


编辑:该库似乎是开源的。代码可在:

.../android-sdk\sources\android-18\android\support\v4\widget\DrawerLayout.java
.../android-sdk\sources\android-18\android\support\v4\widget\SlidingPaneLayout.java
.../android-sdk\sources\android-18\android\support\v4\app\ActionBarDrawerToggle.java

现在的问题是如何让它像我写的那样工作,这样它就可以像在 youtube 上一样工作,允许我们自定义它的外观以及从哪里允许滚动它。

【问题讨论】:

  • 我不确定我是否理解正确,我也在使用新的导航抽屉,并且它始终可以在没有任何额外设置的情况下工作。我正在做一个项目:ABS + Navigation Drawer + Fragments + GMaps,从整个屏幕的最左侧或最右侧滑动右侧或左侧抽屉没有问题。您是否将 xml 中的抽屉声明为 recommended
  • 我正在使用我发布的内容。你是说我不需要看这个项目,只需使用谷歌的东西吗?它与 ABS 一起工作是否正常,是否允许我自定义触发导航抽屉滚动的距离(例如从屏幕中间)?如果是这样,如何?他们的示例应用程序只允许在您开始触摸屏幕最左侧时滚动...
  • 好吧,我绝对确定 ABS + 新的导航抽屉集成没有任何问题,没有任何额外的帮助,如您发布的项目。至于滑动抽屉出现时被识别的屏幕区域非常薄,我不确定是否可以修改,例如滑动库可以(无,边缘,整个屏幕)。您提供的示例(youtube)无效,它不使用导航抽屉,我会说他们使用滑动菜单库。 Google+ 确实使用了新的导航抽屉,如果您尝试,要滑动的屏幕区域与左边框相同。
  • youtube 确实有一个导航抽屉。也许它看起来不一样,但它有效。也许你应该更新应用程序。或者,您可以通过 google 试用“currents”。无论如何,我的问题是如何使其可定制,以便我可以控制触发抽屉导航滚动的位置?
  • 好的,我找到了答案。不幸的是,这是不可能的。距离边缘仅 20dp:Give the user a quick peek If the user touches the very left edge of the screen (within 20 dp from the left), have the drawer peek out as soon as the finger makes contact with the display. This promotes accidental discovery and provides richer feedback.here。在这种情况下,您需要有另一种方法来确定您想要的区域中的触摸并使用mDrawerLayout.openDrawer(mDrawer) 触发抽屉

标签: android slidingmenu drawerlayout


【解决方案1】:

SlidingMenu 是我找到的最好的滑动库,非常好的库。
可以设置getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN)全屏投屏。

【讨论】:

  • 我希望自定义允许的区域。另外,这个库是否支持 actionBarSherlock?
  • 是的!它与 actionbarsherlock 库集成,正如我在回答中所说,您可以使用 setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN),它控制是否可以通过滑动手势打开 SlidingMenu
  • 其他自定义选项有mSlidingMenu.setShadowWidthRes()mSlidingMenu.setShadowDrawable()mSlidingMenu.setBehindWidthRes()mSlidingMenu.setFadeDegree() 等。我已经尝试了 github 上几乎所有的滑动菜单库。 SlidingMenu 是迄今为止最好的
  • 那么我能做些什么,如果(例如)我希望当手指触摸屏幕的一半左侧区域时,它会显示抽屉,但如果它位于屏幕的右半部分屏幕,它不会?
  • 我还希望能够自定义抽屉以获得我想要的任何视图。它是否像我展示的库一样支持它?
【解决方案2】:

问题在于 DrawerLayout 使用 ViewDragHelper,它的 default EDGE_SIZE 为 20dp,用于计算 mEdgeSize,如下所示:

mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);

这是一个将 mEdgeSize 设置为显示宽度百分比的函数:

public static void setDrawerLeftEdgeSize(Activity activity, DrawerLayout drawerLayout, float displayWidthPercentage) {
    if (activity == null || drawerLayout == null)
        return;

    try {
        // find ViewDragHelper and set it accessible
        Field leftDraggerField = drawerLayout.getClass().getDeclaredField("mLeftDragger");
        leftDraggerField.setAccessible(true);
        ViewDragHelper leftDragger = (ViewDragHelper) leftDraggerField.get(drawerLayout);
        // find edgesize and set is accessible
        Field edgeSizeField = leftDragger.getClass().getDeclaredField("mEdgeSize");
        edgeSizeField.setAccessible(true);
        int edgeSize = edgeSizeField.getInt(leftDragger);
        // set new edgesize
        Point displaySize = new Point();
        activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
        edgeSizeField.setInt(leftDragger, Math.max(edgeSize, (int) (displaySize.x * displayWidthPercentage)));
    } catch (NoSuchFieldException e) {
        // ignore
    } catch (IllegalArgumentException e) {
        // ignore
    } catch (IllegalAccessException e) {
        // ignore
    }
}

因此,假设您希望左边缘的 30% 对滑动事件做出反应并打开导航抽屉,然后只需调用:

mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
setDrawerLeftEdgeSize(this, mDrawerLayout, 0.3f);

【讨论】:

  • 它仍然以一种古怪的方式工作。尝试将大小设置为 0.5f ,然后只长按左侧。它仍然会触发显示抽屉,并以非常快速的方式显示它。顺便说一句,您使用了太高的 API 来获取屏幕的宽度。相反,您可以使用:activity.getResources().getDisplayMetrics().widthPixels
  • 我在这里尝试了 0.7f 和长按。动画速度对我来说仍然“很好”(我在 S3 上)。但是,我猜动画的持续时间是固定的,因此要在相同的时间内为抽屉设置更多动画 - 它只需要移动得更快。注意:我在最近的 API 级别上使用了“getSize(..)”。
  • 长按不应触发显示滑动抽屉。抽屉的移动以一种奇怪的方式移动,我很难解释。我也有 SGS3 。关于 getSize(),你应该在保存时启用 Lint 检查,这样它会警告你使用太新的函数/类。清单中的 targetSdk 应该是最新的(现在是 19 个),但 minSdk 可以是您希望支持的任何内容(8 或 9 覆盖几乎 100%)。
  • 是的,长按应该可以看到抽屉——这里提到了例如plus.google.com/+AdamWPowell/posts/8j2GVw72i1E。或者,您的意思是它应该只在单击边缘时才可以偷看?是的,我故意设置了“minSdkVersion=14”。如今,与市场份额 IMO 相比,设置 API 8(或 9)会产生太多的兼容性问题。
【解决方案3】:

我尝试更改默认 EDGE_SIZE 解决方案,也遇到了长按问题。 最后,通过计算X偏移量,找到了覆盖dispatchTouchEvent并根据滑动方向打开抽屉。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            startX = ev.getX();
            startY = ev.getY();
            break;
        case MotionEvent.ACTION_UP:
            endX = ev.getX();
            endY = ev.getY();

            float sensitivity = 5;
            // From left to right
            if (endX - startX >= sensitivity) {
                if (mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) {
                    mDrawerLayout.closeDrawer(Gravity.RIGHT);
                } else {
                    mDrawerLayout.openDrawer(Gravity.LEFT);
                }
            }

            // From right to left
            if (startX - endX >= sensitivity) {
                if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
                    mDrawerLayout.closeDrawer(Gravity.LEFT);
                } else {
                    mDrawerLayout.openDrawer(Gravity.RIGHT);
                }
            }

            break;
    }

【讨论】:

    【解决方案4】:

    通过他人的帮助:(first)(second)

    我发现使用反射可以修改默认的 20dp-screen-edge 以使抽屉菜单滑动

    声明内容和抽屉后,您可以这样做:

    public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    private ActionBarDrawerToggle mDrawerToggle;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerList = (ListView) findViewById(R.id.left_drawer);
    
        // set a custom shadow that overlays the main content when the drawer opens
        mDrawerLayout.setDrawerShadow(R.drawable.your_drawer_shadow, GravityCompat.START);
        // set up the drawer's list view with items and click listener
        mDrawerList.setAdapter(new ArrayAdapter<String>(this,
                R.layout.your_drawer_list, yourItems));
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
    
        // enable ActionBar app icon to behave as action to toggle nav drawer
        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
    
        Field mDragger = null;
        try {
            mDragger = mDrawerLayout.getClass().getDeclaredField(
                    "mLeftDragger"); //mRightDragger for right obviously
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        mDragger.setAccessible(true);
        ViewDragHelper draggerObj = null;
        try {
            draggerObj = (ViewDragHelper) mDragger
                    .get(mDrawerLayout);
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
        Field mEdgeSize = null;
        try {
            mEdgeSize = draggerObj.getClass().getDeclaredField(
                    "mEdgeSize");
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        mEdgeSize.setAccessible(true);
        int edge = 0;
        try {
            edge = mEdgeSize.getInt(draggerObj);
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
        try {
            mEdgeSize.setInt(draggerObj, edge * 5); //optimal value as for me, you may set any constant in dp
            //You can set it even to the value you want like mEdgeSize.setInt(draggerObj, 150); for 150dp
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
        // ActionBarDrawerToggle ties together the the proper interactions
        // between the sliding drawer and the action bar app icon
        mDrawerToggle = new ActionBarDrawerToggle(
                this,                  /* host Activity */
                mDrawerLayout,         /* DrawerLayout object */
                R.drawable.ic_drawer,  /* nav drawer image to replace 'Up' caret */
                R.string.drawer_open,  /* "open drawer" description for accessibility */
                R.string.drawer_close  /* "close drawer" description for accessibility */
                ) {
            public void onDrawerClosed(View view) {
    
            }
    
            public void onDrawerOpened(View drawerView) {
    
            }
        };
        mDrawerLayout.setDrawerListener(mDrawerToggle);
    
    }
    }
    

    嗯,它对我有用!

    【讨论】:

    • 干得好,但是:1.使用反射是不是有点冒险?扩展类并使用已经存在的东西是不可能的吗?也许只是复制它的代码(代码在哪里?)? 2.它不能很好地工作,因为它在内容完全显示之前停止滚动方式。 3.你可以将整个新代码放在一个try-catch中,因为如果一个失败了,其余的也会失败
    • 好吧,1. 是的,只要您没有正确捕获所有异常,这是有风险的,以防某些事情无法按预期工作,您需要准备好 B 计划(这将在这种情况下默认为 20-dp,或者只是主页按钮)。扩展和覆盖方法会很棒,问题是,我们没有完全访问它的权限,而不是开源的。 :( 2. 我也注意到,它在所有滚动后显示了一些额外的空间 3. 你可以试试,但是你会失去内存中的分配空间,初始化你将在 try{} 部分中使用的所有变量,虽然不是很大的一部分。
    • 1.实际上它是开源的。我已经更新了问题以包含文件的路径。 2.必须有更好的方法。 3. 在这个小样本中,您不需要变量,以防它在任何步骤中失败。
    • 此示例代码直接跳转到我的 kitkat HTC One 上的 NoSuchFieldException。
    【解决方案5】:

    您可以将它与导航抽屉一起使用

    DrawerLayout mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
    Field mDragger = mDrawerLayout.getClass().getDeclaredField(
        "mLeftDragger");//mRightDragger or mLeftDragger based on Drawer Gravity
    mDragger.setAccessible(true);
    ViewDragHelper draggerObj = (ViewDragHelper) mDragger
        .get(mDrawerLayout);
    
    Field mEdgeSize = draggerObj.getClass().getDeclaredField(
        "mEdgeSize");
    mEdgeSize.setAccessible(true);
    int edge = mEdgeSize.getInt(draggerObj);
    
    mEdgeSize.setInt(draggerObj, edge * 3); 
    

    【讨论】:

    • 它有效,但它很古怪。如果我将其更改为 30 而不是 3 ,它的工作原理真的很奇怪。
    • 感谢您的提示!我在下面的单独答案中对其进行了一些改进。
    【解决方案6】:

    首先,我不确定您所说的 ActionBarSherlock 不支持 DrawerLayout 是什么意思。您可以非常愉快地使用 DrawerLayout 和 ActionBarSherlock,我有使用这种组合的应用程序。您需要更改支持 jar 版本,但我发现 ActionbarSherlock 对此没有问题。

    以通常的方式实现 DrawerLayout。然后在您的片段中实现 onTouchEvent 或 interceptTouchEvent ,当类型是从左向右滑动的“移动”类型事件时,您可以在 DrawerLayout 上调用 openDrawer 方法。您需要为手指移动的距离添加处理,以确保打开抽屉的调用不会太敏感。

    【讨论】:

    • 请展示如何实现它的示例代码。请注意,我不想只打开抽屉。我希望像我提到的应用程序那样移动它,如果用户停止触摸,它会决定是否应该向后滚动或滚动以显示抽屉。
    • 此功能与 DrawerLayout 的功能不同。不想使用抽屉布局有什么特别的原因吗?尝试实现自己的自定义抽屉将使您的生活变得更加困难。有关如何解决此问题的一些信息,请查看 Cyril Mottier 关于他如何在 Prixing 中实现抽屉的这一系列帖子。 cyrilmottier.com/2012/05/22/…
    • 我没有说我不想使用它。事实上,我问过如何使用它并具有选择从哪里开始拖动的功能。
    【解决方案7】:

    当我们覆盖默认边缘侧时,重叠部分的屏幕停止工作,我在 viewpager 中使用了上面的代码,但现在当我从右向左滚动而不是进入下一个视图页面时,它保持不变。解决方案是什么?我用过setDrawerLeftEdgeSize(this,mDrawerLayout,distance );

    public static void setDrawerLeftEdgeSize(Activity activity,
                DrawerLayout drawerLayout, float displayWidthPercentage) {
            if (activity == null || drawerLayout == null)
                return;
    
            try {
                // find ViewDragHelper and set it accessible
                Field leftDraggerField = drawerLayout.getClass().getDeclaredField(
                        "mLeftDragger");
                leftDraggerField.setAccessible(true);
                ViewDragHelper leftDragger = (ViewDragHelper) leftDraggerField
                        .get(drawerLayout);
                // find edgesize and set is accessible
                Field edgeSizeField = leftDragger.getClass().getDeclaredField(
                        "mEdgeSize");
                edgeSizeField.setAccessible(true);
                int edgeSize = edgeSizeField.getInt(leftDragger);
                // set new edgesize
                Point displaySize = new Point();
                activity.getWindowManager().getDefaultDisplay()
                        .getSize(displaySize);
                edgeSizeField.setInt(leftDragger, Math.max(edgeSize,
                        (int) (displaySize.x * displayWidthPercentage)));
            } catch (NoSuchFieldException e) {
                // ignore
            } catch (IllegalArgumentException e) {
                // ignore
            } catch (IllegalAccessException e) {
                // ignore
            }
        }
    

    同上..

    【讨论】:

      【解决方案8】:

      使用这个项目SlidingMenu,您可以实现该效果并与 ActionBarSherlock 集成。

      要从左向右滑动菜单,只需在创建菜单时自定义菜单并添加:

      slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN);
      

      希望这会有所帮助。

      【讨论】:

      • 我不明白。我在哪里可以设置允许拖动 DrawerLayout 的区域大小?另外,我认为这是一个完全不同的库。
      • 该区域是屏幕的边缘,只需在边缘附近或从屏幕外部滑动,它就像一个魅力。它确实是另一个库,在谷歌引入 DrawerLayout 之前我一直在使用这个库,它非常好。你说你的库还没有完成,所以我只给你提供了一个替代方案。
      • 我想自定义它,例如在 current 和 youtube 上。
      猜你喜欢
      • 2010-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-17
      • 1970-01-01
      • 2011-03-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多