I have successfully implemented a NavigationDrawer for my application.
My app displays a drawer that opens on the left of the screen.
My problem is I need to add a button on the left. That button might be clicked or swiped to open the left drawer. That I can do.
But the button is supposed to look like it's a part of the drawer that would overflow into the screen.
That means the button should slide simultaneously as the drawer opens and closes.
CLOSED STATE :
OPENING STATE
I tried adding the button into the left drawer's layout, but it seems you can't make stuff appear outside of its boundaries, and the drawer will always get completely hidden when you close it.
Now I'm trying adding it to add a button to the main DrawerLayout and make it align to the right of the left drawer... But no luck... It looks like a DrawerLayout can't have more than two children...
Any help will be appreciated.
I'm using the Support library (v4)
[EDIT]
And I am supporting API level 8... So can't use ImageView.setTranslationX or View.OnAttachStateChangeListener
It's quite tricky.
I think this is similar to what is done to the drawer handle in new Google Maps app. Don't take my word, not sure. :)
I have written toggle that stays on the edge of the Activity content view.
When the DrawerLayout is dragged, I translated the view on the x-axis by the amount of the minimal child (which is DrawerLayout content view) minus the shadow (if any). Since the shadow casted + content view of the DrawerLayout gives the full measured width of the entire drawer.
I quickly multiply the slided offset and the minimal child and find the x translation.
[Edit: Code has been removed for readability and it has been moved to the link provided below]
In your activity:
mDrawerToggle = new DrawerLayoutEdgeToggle(
this,
mDrawerLayout,
R.drawable.ic_launcher,
R.drawable.ic_launcher,
Gravity.LEFT,
true) {
#Override
public void onDrawerClosed(View view) {
super.onDrawerClosed(view); //must call super
}
#Override
public void onDrawerOpened(View view) {
super.onDrawerOpened(view); //must call super
}
#Override
public void onDrawerSlide(View view, float slideOffset) {
super.onDrawerSlide(view, slideOffset); //must call super
}
};
mDrawerLayout.setDrawerListener(mDrawerToggle);
Creativity boost:
You can add more cosmetics like distance from ActionBar which you can set as margin to the handle.
Also you can mimic "single-zipper effect" by moving the handle up/down along left/right just by translating on the Y axis. :)
Edit: Its available on my GitHub here
Edit 2: For those that can't get the handle appear at the beginning just use mDrawerToggle.setVerticalPostionOffset(0)
I found a way of doing this pretty easily thanks to the Aniqroid library written by Mobistry (SO pseudo)
I use the SlidingTray class which is a copy of android SlidingDrawer but lets you position the drawer to any corner of the screen.
I declared the element via xml
<com.sileria.android.view.SlidingTray
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer"
android:layout_width="match_parent"
android:layout_height="270dp"
android:content="#+id/content"
android:handle="#+id/handle" >
<ImageView
android:id="#+id/handle"
android:layout_width="320dp"
android:layout_height="24dp"
android:background="#drawable/tray_btn" />
<LinearLayout
android:id="#+id/content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="fill_vertical"
android:orientation="vertical" >
</LinearLayout>
</com.sileria.android.view.SlidingTray>
And just had to call
Kit.init(this);
in my main Activity before inflating the layout.
I thank aniqroid devs!
https://code.google.com/p/aniqroid/
Related
I have been adding a navigation drawer to one of my apps, and I started to wonder whether or not it would be better to switch from using a ListView to multiple TextViews for the navigation drawer list items. Looking at the Google Design Guidelines on Navigation Drawer content (specifically the section on 'Scrolling'), I noticed that it may look nicer with multiple TextViews.
At the moment, I am using a ListView and ImageView in my navigation drawer (it looks a little like this. However, when I scroll in my navigation drawer (I do this by turning my device landscape as there are not enough items in my list yet), only the ListView scrolls, and the ImageView stays as it is. I want it to be able to scoll more like this, where the ImageView is also scrolled with the ListView.
Additionally, I found that my ListView in my navigation drawer does not have the ripple effects as shown in this image although other ListViews in my other Activitys and Fragments do.
What are the issues I am facing and how could I go about resolving these?
Update:
In Google's I/O App (2014), there seems to be a LinearLayout at the bottom of the navigation drawer layout which I think is responsible for the list of items shown. Could someone explain how this would work?
only the ListView scrolls, and the ImageView stays as it is
It sounds like your drawer contains an ImageView at the top and then a ListView follows. With this configuration only the ListView will scroll (because it's the only view that's scrollable).
You need to add the ImageView as a header which is always at the beginning of the list. As one of the comments suggested, do listView.addHeaderView.
there seems to be a LinearLayout at the bottom of the navigation
drawer layout which I think is responsible for the list of items
shown. Could someone explain how this would work?
They use the LinearLayout as a container to hold all the TextViews:
private void createNavDrawerItems() {
mDrawerItemsListContainer = (ViewGroup) findViewById(R.id.navdrawer_items_list);
...
int i = 0;
for (int itemId : mNavDrawerItems) {
mNavDrawerItemViews[i] = makeNavDrawerItem(itemId, mDrawerItemsListContainer);
mDrawerItemsListContainer.addView(mNavDrawerItemViews[i]);
++i;
}
}
I believe the reason they use a LinearLayout and inflate all the items programmatically is to be able to use separator items easily:
private View makeNavDrawerItem(final int itemId, ViewGroup container) {
...
if (itemId == NAVDRAWER_ITEM_SEPARATOR) {
layoutToInflate = R.layout.navdrawer_separator;
} else if (itemId == NAVDRAWER_ITEM_SEPARATOR_SPECIAL) {
layoutToInflate = R.layout.navdrawer_separator;
} else {
layoutToInflate = R.layout.navdrawer_item;
}
...
return view;
}
In a ListView you'd have to create a separate item type and use the divider's layout there, which could possibly get more cumbersome.
At first glance, however, this code just seems to be re-inventing the wheel as all of this is possible with a ListView.
As of 29th May 2015 (after Google I/O 2015), you can use the Android Design Support Library to add a NavigationView to your app(s). The Android Developer Blogspot article states the following:
Navigation View
The navigation drawer can be an important focal point for identity and navigation within your app and consistency in the design here can make a considerable difference in how easy your app is to navigate, particularly for first time users. NavigationView makes this easier by providing the framework you need for the navigation drawer as well as the ability to inflate your navigation items through a menu resource.
...
You can then start using the Design library with a single new dependency:
compile 'com.android.support:design:22.2.0'
...
The Design library, AppCompat, and all of the Android Support Library are important tools in providing the building blocks needed to build a modern, great looking Android app without building everything from scratch.
Implementing scrollable Navigation Drawer using android.support.v4.widget.DrawerLayout and NavigationView could be even simpler than it is described at: http://android-developers.blogspot.ru/2015/05/android-design-support-library.html
That article suggests adding each element of your application's Navigation Drawer as a Menu Item. This is cool and definitely a way to go for most of developers.
But what if you already has a Navigation Drawer implemented inside e.g. Linear Layout?
It appears that you can easily make your old good layout scrollable: just set it as a "app:headerLayout" of the NavigationView. No more changes are needed!
So, in a final solution you will have:
A layout of your Activity, similar to the above blog post, but without an "app:menu="#menu/drawer" attribute e.g. this:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- your content layout -->
<android.support.design.widget.NavigationView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="#layout/drawer_header"
/>
</android.support.v4.widget.DrawerLayout>
And a layout for all your old Drawer content in the "drawer_header.xml" file, migrated without any changes to this scrollable Drawer, E.g. this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:choiceMode="singleChoice"
android:orientation="vertical">
<TextView
android:id="#+id/myFirstButton"
android:onClick="onMyFirstButtonClick"
android:text="#string/my_first_button_title"/>
<TextView
android:id="#+id/goToTheTopButton"
android:onClick="onGoToTheTopButtonClick"
android:text="#string/go_to_the_top_title"/>
<View style="#style/Divider"/>
<!-- Some other "menu items" -->
</LinearLayout>
For full working example see this activity layout: https://github.com/andstatus/andstatus/blob/master/app/src/main/res/layout/timeline.xml and this commit, where I migrated to a scrollable Navigation Drawer: https://github.com/andstatus/andstatus/commit/a80b299de714bdd65cacb138ffb31adc3ea23a98
I have implemented a SwipeRefreshLayout on my ListView, and it is working, but not how I want it to.
First of all, the refresh triggers before I lift my finger, which doesn't feel right because it moves the content back to the top even though my finger is still sitting in swiped down position. Also the distance to trigger is really short and setDistanceToTriggerSync from the docs is not available to me for some reason.
second, I'd like some sort of view the be displayed in the gap when my list view is pulled down, like a text that tells the user "swipe down to refresh" or a an animation (like the dancing ghost in snap chat). How do I set this View
Here's what I have
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/dashboardRootLayout"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/lvDashboard"
android:background="#ffffff"
/>
Why isn't the setDistanceToTriggerSync available?
Anyway the default behavior of the SwipeRefreshLayout does not have that extra view, it just has a special progress bar and a listener for refreshing and showing or not the progress of refresh. Also it can change the look of the actionbar with the refresh message.
Possible solution:
If you want that special view, of the top of my head I can think of having the list being moved down with animation and creating or putting VISIBLE the view you want to show on the top of the SwipeRefreshLayout.
SwipeRefreshLayout swipeRefreshLayout=(SwipeRefreshLayout)findViewById(R.id.dashboardRootLayout);
swipeRefreshLayout.setOnRefreshListener(new OnRefreshListener()
{
#Override
public void onRefresh()
{
// do animation
// set the view you want to show VISIBLE
}
});
You could actually implement all of this by yourself without the SwipeRefreshLayout since you don't want the default behavior.
}
I've been trying to move my code across to the DrawerLayout as suggested by android here as SlidingDrawer is deprecated.
My problem is that so far DrawerLayout seems to be either very badly implemented, has unhelpful error messages (no defensive programming) and/or isn't explained well enough in the documentation.
the isDrawerOpen() method is described here:
public boolean isDrawerOpen (View drawer)
Check if the given drawer view is currently in an open state. To be
considered "open" the drawer must have settled into its fully visible
state. To check for partial visibility use
isDrawerVisible(android.view.View).
Parameters: drawer - Drawer view to check
Returns: true if the given drawer view is in an open state
Each of the methods isDrawerOpen(View drawer), openDrawer(View drawer) and closeDrawer(View drawer) don't work when passed: The DrawerLayout in question or either of it's children. I have no idea what I'm supposed to feed into these methods to allow them to function. Can someone let me know?
See below for entire problem description with implementation.
I have a layout like so:
<android.support.v4.widget.DrawerLayout
android:id="#+id/mainmenuPanel"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/dualPane"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="#+id/menuPane"
android:layout_width="300dp"
android:layout_height="match_parent" />
</android.support.v4.widget.DrawerLayout>
And in my code I have the following method hooked up to a button:
protected void onCreate(Bundle savedInstanceState) {
...
mMenuPanel = (DrawerLayout) findViewById(R.id.mainmenuPanel);
....
}
public boolean isDrawerOpen() {
if(mMenuPanel != null) {
return mMenuPanel.isDrawerOpen(mMenuPanel);
}
return false;
}
However if you give it itself as an argument (which would be redundant in the extreme) you get the following error:
E/AndroidRuntime(11241): java.lang.ClassCastException:
android.widget.FrameLayout$LayoutParams cannot be cast to
android.support.v4.widget.DrawerLayout$LayoutParams
(Which as you'll notice, doesn't give you any information about what you did wrong. It is instead a symtom of the problem).
Other answers here or here are either incomprehensible or very closely tied to the question without much explanation. Even so I've tried adding a faux LinearLayout or one the DrawerLayouts children and each give this error:
E/AndroidRuntime(11424): java.lang.IllegalArgumentException:
View android.widget.FrameLayout{420f5ea8 V.E..... ........
0,0-800,1172 #7f060038 app:id/menuPane} is not a drawer
Can anyone explain what these methods actually need to have passed to them to work?
And, the answer:
The second child (aka, the "Drawer") is what needs to be passed to the methods. My problem was that by the time I had figured that out I'd reduced the layout to as simple as possible implementation to test - I'd removed the gravity from the "drawer". Without a gravity, you get the above completely unrelated error messages.
I can confirm I got the code to work using the following setup:
mMenuPanel.isDrawerOpen(findViewById(R.id.drawer));
and with the layout:
<android.support.v4.widget.DrawerLayout
android:id="#+id/mainmenuPanel"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="#+id/drawer"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="start"/> <!-- This line was the problem!!!!!!!-->
</android.support.v4.widget.DrawerLayout>
if you want to avoid references to views or layouts in order to call isDrawerOpen, another way can be applied.
In this case you have to indicate the Gravity:
mDrawerLayout.isDrawerOpen(Gravity.START);
Being mDrawerLayout a reference to your DrawerLayout.
as you said, don't forget android:layout_gravity="start"
In your Activity if you have a reference to the DrawerLayout and in this case the FrameLayout with the id R.id.menuPane you can also do...
DrawerLayout mMenuPanel = (DrawerLayout) findViewById(R.id.mainmenuPanel);
FrameLayout mMenuPane = (FrameLayout) findViewById(R.id.menuPane);
mMenuPanel.isDrawerOpen(mMenuPane);
Basically you have to pass in a reference to the view being used as the drawer within the DrawerLayout, which will always be mMenuPanel.getChildAt(1) anyways.
If you are using the drawer layout from the support library as we can see from your question that you are, there is also the method:
closeDrawers()
This will close all open drawers (which is usually just the one).
I'm trying to implement the navigation drawer pattern based on my app. I downloaded the sample code from here and i ran it and 90 % of the times the drawer works ok, but sometimes the drawer gets stuck when i try to open it. I have a way of replicating the situation but it doesn't always work. What i do is:
1- Run the sample code as is.
2- Put your finger on the left edge to get the drawer peek
3- Let go of the finger and press it on the main fragment
4- Try to open the drawer as usual
Sometimes the drawer gets stuck on the peek mode no matter how much you swipe your finger to the right to open the drawer more. Has anyone had / fixed this issue?
I faced a similar issue as mentioned by you. I had a list view inside a relative layout (FILL_PARENT). Whenever the content in the list view is less and when I dragged in the area outside the list view, the navigation drawer got struck. Setting android:clickable="true" for the relative layout resolved the problem. Hope this may help.
To clarify on Viji's answer, if you are using something like the navigation drawer example provided:
<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- As the main content view, the view below consumes the entire
space available using match_parent in both dimensions. -->
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- android:layout_gravity="start" tells DrawerLayout to treat
this as a sliding drawer on the left side for left-to-right
languages and on the right side for right-to-left languages.
The drawer is given a fixed width in dp and extends the full height of
the container. A solid background is used for contrast
with the content view. -->
<ListView
android:id="#+id/left_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#111"/>
</android.support.v4.widget.DrawerLayout>
Adding android:clickable="true" to the FrameLayout seems to fix the problem.
Two soluctions.
setting android:clickable
copy the source code of drawerlayout and delete peekDrawer function to disabled this feature
Let me explain why the reason of this bug.
DrawerLayout three states
STATE_IDLE, STATE_DRAGGING, STATE_SETTLING
ACTION_DOWN-> onEdgeTouched will be triggered if it is on the edge, DrawerLayout will trigger peekDrawer after 120ms
PeekDrawer actually changes the state of DrawerLayout to STATE_SETTLING, and then lets the Drawer scroll to the specified location. Then set the state to IDLE.
ACTION_MOVE->
If the current state is DRAGGING, drag captureView
If the current state is not DRAGGING, it will try to execute tryCaptureViewForDrag to reset the state to DRAGGING.
And at the same time, it will also determine whether a new edge gesture is triggered (emphasis !!)
If a new edge gesture was dete, it will invoke onEdgeDragStared
and DrawerLayout will go to captureView to captrue the drawer
How the recuurent this bug?
First tap the edge to invoke a 120ms delay function peekDrawer
Before the peekDrawer was invoked, trigger onEdgeDragStared let the drawer layout capture the drawer, the dragState will be seted to STATE_DRAGGING
After 120ms, If your finger are still in the area that drawerLayout want to peek to , the dragState will be set to SETTLING
Before the drawer peek to the destination, move your finger fast out of that area before the peek end, the drawer will not follow your finger because the state is SETTLING, after SETTLING the state will be set to IDLE
Now your finger is totally out of the drawer and the state is IDLE , so you can’t drag the view anymore
So, the solution is to stop the peekDrawer
actually, the drawerlayout has fixed this problem.
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (action) {
case MotionEvent.ACTION_MOVE: {
// If we cross the touch slop, don't perform the delayed peek for an edge touch.
if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
mLeftCallback.removeCallbacks();
mRightCallback.removeCallbacks();
}
break;
}
}
return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
}
If the child is clickable, the child will consume the event, the onInterceptTouchEvent will invoke many times. and remove the peekDraw while move.
I want to create a drawer functionality, similar to the Facebook app, with two overlaying fragments.
The main layout looks as following:
<merge
xmlns:android="http://schemas.android.com/apk/res/android" >
<fragment
android:id="#+id/menu"
android:name="ch.simon.drawertest.MenuFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="80dp" />
<fragment
android:id="#+id/home"
android:name="ch.simon.drawertest.HomeScreenFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</merge>
The home Fragment contains a button in the top left corner that opens the drawer. Opening happens with a TranslateAnimation inside the HomeScreenFragment.
public void open(){
translateTo(canScrollXBy());
}
public void close(){
translateTo(0);
}
private void translateTo(int x){
Log.e(TAG, "Translate from " +mTranslated +" to " +x);
TranslateAnimation anim = new TranslateAnimation(mTranslated, x, 0, 0);
anim.setDuration(500);
anim.setFillAfter(true);
mRootView.startAnimation(anim);
mTranslated = x;
}
The opening and closing works fine, but I observe 2 unexpected behaviours:
Even when the drawer is closed and the HomeScreenFragment completely covers the MenuFragment, the MenuFragment still receives click events.
When the drawer is open and the Button from the HomeScreenFragment is located in the top right corner of the screen, it still receives the click events as if it was still on the left side. Meaning even if the Button is on the right side, to tap it, I need to tap the left side of the screen.
For this, you can use the library SlidindMenu to do this. It works like a charm. Or you can inspire you from the code of this library.