8

background

google has introduced the DrawerLayout, which shows a menu on the left area of the screen when you click on the "up" button of the action bar.

because the library isn't supported yet on actionBarSherlock, there is already a way to overcome it using this project .

it already has variants on many apps: currents, gmail, hangouts, youtube...

the question

on the "currents" app (and in youtube) , when the user slides the (most-left) page from left to right , the DrawerLayout appears, no matter where the finger has started the touch .

how can i achieve the same effect? maybe i should use onInterceptTouchEvent ?

there isn't much documentation and tutorials of what cool things can be done , other than this link (ok and this one too) . they say (in the part of "Give the user a quick peek") that about 20dp on the left is used for this functionality, but i can see that "currents" work with much larger area.

it seems the library is still not quite finished, and so the layout xml file cannot even be shown in the visual UI editor...


EDIT: it seems that the library is open sourced. code is available on :

.../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

now the question is how to make it work as i've written, so that it would work like on youtube , allowing us to customize how it looks and from where to allow scrolling it.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • I'm not sure if I understood correctly, I am using new Navigation drawer also, and It works always without any extra setting. I'm working on a project with: ABS + Navigation Drawer + Fragments + GMaps and I've had no problems sliding right or left drawer from most-left or most-right side of the whole screen. Are you declaring the drawers in the xml as [recommended](http://developer.android.com/training/implementing-navigation/nav-drawer.html)? – unmultimedio Jul 29 '13 at 19:05
  • i'm using what i've posted. are you saying i don't need to look at this project, and just use what google has? does it work fine with ABS , and does it allow me to customize how far it will trigger scrolling of the navigation drawer (for example from the middle of the screen) ? if so, how? the sample app that they have only allows scrolling when you start touching on the most-left side of the screen... – android developer Jul 29 '13 at 19:15
  • Well, Im absolutely sure ABS + new navigation drawer integrate without any problem, without any additional help like the project that you posted. As far as the area of screen that gets recognized when sliding for the drawer to appear, is quite thin, and I'm not sure if can be modified such as sliding library can (none, edge, whole screen). Example you give (youtube) is not valid, It does not use nav drawer, I would say they use sliding menu lib. Google+ does use the new nav drawer, and if you try, area of the screen to slide is same thin left border. – unmultimedio Jul 29 '13 at 19:17
  • youtube does have a navigation drawer. maybe it doesn't look the same, but it works. maybe you should update the app. alternatively, you can try out "currents" by google. anyway, my question was how can i make it customizable, so that i can control where to trigger the navigation drawer scrolling? – android developer Jul 29 '13 at 19:20
  • Ok I've found the answer. unfortunately is not possible. Just 20dp from the edge: `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.` Look at [here](http://developer.android.com/design/patterns/navigation-drawer.html). In that case you need to have another way to determine the touch in the area you wnt and trigger the drawer by using `mDrawerLayout.openDrawer(mDrawer)` – unmultimedio Jul 29 '13 at 19:23
  • @khale912 yes that's what i've written. anyway, if they say we should give this space, it means that it is customizable, no? isn't is part of the guidelines? to allow at least this width ? 20dp is so very thin, and i don't want to have a whole screen scrolling trigger... – android developer Jul 29 '13 at 19:25
  • i don't want to open the drawer. i want to scroll it with the touch of the user, so that only when fully scrolled to the right and stop touching the screen, it shows the entire drawer, but if you are in the middle of scrolling, show only a part of it. please, just try out "youtube" or "currents" if you can't understand what i mean. – android developer Jul 29 '13 at 19:31
  • I have checked both of them, they use whole screen to slide the left menu, and this menu is **behind** the view (you can tell by the shadow) and the content slides to the right. They perfectly can be using slidingmenu library. Look, if they were using new navigation drawer, the left menu(drawer) should be **over** the content, leaving a shadow between drawer and content and without sliding the content to the right, just as G+ left drawer does. I'd bet both currents and youtube use slidingmenu library and they slide using `getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN)` – unmultimedio Jul 29 '13 at 19:42
  • so no customization even though the text imply about it? too bad. can you think of a way to overcome this? this was the whole point of my question and i thought that it is possible, and even gave some ideas. even the IDE itself can't show the layout right. – android developer Jul 29 '13 at 19:47
  • i also can't find which class has the function getSlidingMenu() ... – android developer Jul 29 '13 at 20:00
  • `getSlidingMenu` is from slidingmenu jfeinstein library (3rd party), btw, i already got the answer to customize the default 20dp, I'll post it in a couple of minutes while I try myself some details. – unmultimedio Jul 29 '13 at 20:04
  • Seems to be like google does not want this 20dp to be changed that's why they don't give access to this property easily. But there's a way to change it, check the answer I just posted. Already tried, works. – unmultimedio Jul 29 '13 at 20:54
  • the answer doesn't work as expected. plus i've noticed it doesn't have the same effect of scrolling as on youtube. on youtube, the right area is above the left drawer and as you scroll, it shows a part of the drawer. using the normal library, it's the opposite. i wonder why the navigation drawer isn't open source. – android developer Jul 30 '13 at 13:34
  • If you really want the youtube or currents effect, consider to implement [jfeinstein library](https://github.com/jfeinstein10/SlidingMenu)... It's awesome, I've tried and it works smoothly, only issue is that it's a bit tricky when you want to use maps as main content/fragment... that's why I changed to official nav drawer – unmultimedio Jul 30 '13 at 15:27

8 Answers8

7

SlidingMenu is best sliding library I've ever found, It's very good library.
You can set getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN) enabling fling for all screen.

Sadegh
  • 2,669
  • 1
  • 23
  • 26
  • i wish to customize the area that is allowed. also, does this library support actionBarSherlock? – android developer Jul 23 '13 at 16:36
  • Yes! It's integrated with actionbarsherlock library, as i say in my answer your can `setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN), which controls whether the SlidingMenu can be opened with a swipe gesture – Sadegh Jul 23 '13 at 16:53
  • Other customization option are `mSlidingMenu.setShadowWidthRes()` , `mSlidingMenu.setShadowDrawable()`, `mSlidingMenu.setBehindWidthRes()`, `mSlidingMenu.setFadeDegree()` an the like. I've tried almost all of sliding menu libraries on github. [SlidingMenu](https://github.com/jfeinstein10/SlidingMenu) is best by far – Sadegh Jul 23 '13 at 16:58
  • so what can i do so that if (for example) i want that when the finger touches on the left area of the half the screen it would show the drawer, yet if it's on the right half of the screen, it won't? – android developer Jul 23 '13 at 17:06
  • i also wish to be able to customize the drawer to have any views i want. does it support it like the library i've shown? – android developer Jul 23 '13 at 17:07
  • what can i do in order to make it have the ability to slide from half the screen but not more than that ? also , is it possible to make it look as if the right area is on top and it just moves it aside and shows the drawer, like on youtube? – android developer Jul 30 '13 at 13:53
  • For half the screen, I think you should calculate screen size and `DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); int width = metrics.widthPixels;` and then `mSlidingMenu..setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN);` and `mSlidingMenu.setTouchmodeMarginThreshold(width/2);` – Sadegh Jul 30 '13 at 14:40
  • and if you don't want to move `ActionBar` set this: `setSlidingActionBarEnabled(false);` – Sadegh Jul 30 '13 at 14:43
  • i think i will use this library instead of the one that google provides, even though i think it is possible somehow to use theirs. it's too bad they don't have javaDocs (API) . thank you. – android developer Jul 31 '13 at 12:14
4

The problem is that DrawerLayout uses ViewDragHelper which has a default EDGE_SIZE of 20dp which is used to calculate the mEdgeSize like this:

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

Here is a function which sets mEdgeSize to a percentage of the display width:

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

So, let's say you want 30% of your left edge to react to slide events and open the Navigation Drawer, then simply call:

mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
setDrawerLeftEdgeSize(this, mDrawerLayout, 0.3f);
Espen Riskedal
  • 1,425
  • 15
  • 28
  • it still works in a wacky way. try to set the size to 0.5f , and then only long click on the left side. it will still trigger showing the drawer, and it shows it in a very fast way. BTW, you've used a too-high API for just getting the width of the screen. instead, you can use : activity.getResources().getDisplayMetrics().widthPixels – android developer Nov 16 '13 at 11:13
  • I tried with 0.7f here and the long click. The animation speed is still "fine" for me (I'm on an S3). But, I guess the duration of the animation is fixed, and therefore to animate the drawer that much more in the same matter of time - it will simply have to move faster. Note: I used the 'getSize(..)' on purpose as I'm on a recent API level. – Espen Riskedal Nov 18 '13 at 16:02
  • long clicking should not trigger showing the sliding drawer. the movement of the drawer moves in a weird way, and it's hard for me to explain. I also have SGS3 . about getSize(), you should enable Lint checks on save, so that it will warn you about using too-new functions/classes . the targetSdk in the manifest should be the latest (19 by now) yet the minSdk can be whatever you wish to support (8 or 9 covers nearly 100% ). – android developer Nov 18 '13 at 16:13
  • Yes long click should peek out the drawer - it is mentioned here for example https://plus.google.com/+AdamWPowell/posts/8j2GVw72i1E. Or, do you mean it should only peek when clicking _on the edge_? And yes, I've set 'minSdkVersion=14' on purpose. These days setting API 8 (or 9) generates too much compatibility pain compared to the market share IMO. – Espen Riskedal Nov 18 '13 at 17:10
3

I tried changing default EDGE_SIZE solution, and encountered the long clicking problem too. Finally, Ifound override dispatchTouchEvent and open drawer depending the sliding direction by calculating X offset.

@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;
    }
seauniv
  • 31
  • 1
2

By using others' help: (first) (second)

I found that using reflections it's achievable to modify the defult 20dp-screen-edge for the drawer menu to slide

After declaring content and drawers, you can do this:

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

}
}

Well, It works for me man!

Community
  • 1
  • 1
unmultimedio
  • 1,224
  • 2
  • 13
  • 39
  • 1
    good job , but: 1.isn't using reflection a bit risky? is it impossible to extend the class and use what is already there? maybe just copy its code (where is the code anyway?)? 2. it doesn't work well as it stops scrolling way before the content is fully shown. 3.you could put the entire new code in a single try-catch, since if one failed, the rest will also fail – android developer Jul 29 '13 at 21:53
  • well, 1. yes it's risky as long as you don't catch properly all the exceptions, in case something doesn't work as expected, you need to be prepared with a B plan (that would be default 20-dp in this case, or just the home button). It would be awesome to extend and override methods, issue is, we don't have full acces to it, not opensource. :( 2. I noticed that as well, It shows some extra withe space after all scrolling 3. You could try that, but you would be losing assigment space in memory initializing all variables you are going to use inside of the try{} part, not a huge part though. – unmultimedio Jul 30 '13 at 15:25
  • 1. actually it is open source. i've updated the question to include the path to the files. 2. there must be a better way. 3. in this small sample, you don't need the variables in case it failed in any of the steps. – android developer Jul 30 '13 at 18:25
  • This sample code jumps straight to the NoSuchFieldException on my kitkat HTC One. – Timothy Miller Nov 08 '14 at 21:34
2

You can use this with Navigation Drawer

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); 
moallemi
  • 2,448
  • 2
  • 25
  • 28
1

Firstly I am not sure what you mean about ActionBarSherlock not supporting DrawerLayout. You can work with the DrawerLayout and ActionBarSherlock quite happily, I have apps that are using this combination. You will need to change the support jar version but I have found that ActionbarSherlock does not have a problem with this.

Implement DrawerLayout in the usual manner. Then in your fragment implement onTouchEvent or interceptTouchEvent and when the type is a 'move' type event sliding from left to right you can call the openDrawer method on the DrawerLayout. You will need to add handling for the distance the finger travels to make sure the call to open the drawer is not too sensitive.

Phil H
  • 897
  • 4
  • 10
  • please show a sample code of how to achieve it. do note that i don't wish to just open the drawer. i wish to move it like on the apps i've mentioned, and if the user stop touching, it decides if it should scrll back or scroll to show the drawer. – android developer Jul 25 '13 at 10:08
  • This functionality is different to the functionality of the DrawerLayout. Is there a particular reason for not wanting to use the Drawer Layout? You are going to make life a lot more difficult for yourself trying to implement your own custom drawer. For some information regarding how to approach this take a look at this series of posts from Cyril Mottier on how he implemented the drawer in Prixing. http://cyrilmottier.com/2012/05/22/the-making-of-prixing-fly-in-app-menu-part-1/ – Phil H Jul 25 '13 at 11:02
  • i didn't say i don't wish to use it. in fact, i asked how to use it AND have this functionality of choosing where to start the dragging. – android developer Jul 27 '13 at 15:49
1

When we override the default edge side our screen of that portion on which it overlaps stops working, I have used the above code in viewpager but now when i scroll to the left from right instead of coming next view page it remains constant. What will be the solution for it? I have used 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
        }
    }

same as above..

Durgesh Kumar
  • 935
  • 10
  • 17
0

Using this project SlidingMenu you can achieve that effect and integrate with ActionBarSherlock.

To slide the menu from left to right just customize the menu when creating it and add:

slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN);

Hope this helps.

Mikel
  • 1,581
  • 17
  • 35
  • i don't understand. where can i set here the area size that will allow to drag the DrawerLayout ? also, i think it's a totally different library. – android developer Jul 17 '13 at 12:58
  • The area is the margin of the screen, just swipe near the edge or from outside of screen and it works like a charm. It is indeed another library, I have been using this one since before google has introduced the DrawerLayout and it is very good. You said your library is still not quite finished, so I only provided you with an alternative. – Mikel Jul 17 '13 at 13:28
  • i want to customize it, for example like on current and youtube. – android developer Jul 17 '13 at 13:52