According to the Material Design specs the Nav Drawer's width on mobile devices must be
side nav width = screen width - app bar height
How do we implement this on android?
I have two partial solutions. First is the hacky way: in the containing activity I put this code:
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR1) {
final Display display = getWindowManager().getDefaultDisplay();
final Point size = new Point();
display.getSize(size);
final ViewGroup.LayoutParams params = mDrawerFragment.getView().getLayoutParams();
params.width = size.x - getResources().getDimensionPixelSize(
R.dimen.abc_action_bar_default_height_material
);
mFragmentUserList.getView().setLayoutParams(params);
}
This, however, causes a second layout cycle and doesn't work in gingerbread: it is not optimal.
The second solution involves adding a Space between the fragment and the drawerLayout. It however, displaces the shadow and the spot where the user can press to return to the main app. It also crashes when the "hamburguer" icon is pressed. Not optimal either.
Is there a better solution, preferably one that involves styles and xml?
I managed to make a solution using XML style declarations but it is a bit hacky as well. My approach was to use margins instead of applying a set width to avoid writing any code to calculate the layout manually. I've created a basic style rule to highlight how to get this working.
Unfortunately, DrawerLayout currently applies a minimum margin of 64dp. For this approach to work, we need to offset that value with negative margins so we can get the desired width for the navigation drawer. Hopefully this can be resolved in the future (Someone has filed an issue regarding it) so we can just reference the abc_action_bar_default_height_material dimension reference for the margin.
Follow these steps:
Add the following dimension and style definitions:
values/dimens.xml
<!-- Match 56dp default ActionBar height on portrait orientation -->
<dimen name="nav_drawer_margin_offset">-8dp</dimen>
values-land/dimens.xml
<!-- Match 48dp default ActionBar height on landscape orientation -->
<dimen name="nav_drawer_margin_offset">-16dp</dimen>
values/styles.xml
<!-- Nav drawer style to set width specified by Material Design specification -->
<style name="NavDrawer">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_marginRight">#dimen/nav_drawer_margin_offset</item>
</style>
values-sw600dp/styles.xml
<!-- Margin already matches ActionBar height on tablets, just modify width -->
<style name="NavDrawer">
<item name="android:layout_width">320dp</item>
<item name="android:layout_marginRight">0dp</item>
</style>
Once you have added the rules above in your project, you can reference the NavDrawer style in your navigation drawer view:
layout/navigation_drawer.xml (or other appropriate view being used for your navigation drawer)
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/navigation_drawer"
style="#style/NavDrawer"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#FFF"
/>
With the Android Design Support Library it is now really simple to implement navigation drawer including correct sizing. Use the NavigationView and either use its ability to make drawer out of menu resource (example here) or you can just wrap it around the view which you currenty use for showing your drawer list (e.g. ListView, RecyclerView). NavigationView will then take care of the drawer sizing for you.
Here's an example how I use the NavigationView wrapped around ListView:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/navdrawer_layout"
android:fitsSystemWindows="true">
<!-- Layout where content is shown -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include android:id="#+id/toolbar"
layout="#layout/toolbar" />
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/toolbar" />
<!-- Toolbar shadow for pre-lollipop -->
<View style="#style/ToolbarDropshadow"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_below="#id/toolbar" />
</RelativeLayout>
<android.support.design.widget.NavigationView
android:id="#+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start">
<ListView
android:id="#+id/navdrawer_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:dividerHeight="0dp"
android:divider="#null"/>
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>
This way you can use NavigationViews sizing and still use your own drawer list. Though it is much easier to make the drawer list out of menu resource (example here) you can't use custom views for the list items.
After looking for a simpler solution, I found a very clarifying article: Material Navigation Drawer sizing.
Here:
The Nexus 5 screen is a nice 640 x 360 dp (xxhdpi), and an app bar on
it is 56 dp tall. So the nav drawer should be:
[width in dp] = 360dp — 56dp = 304dp
A Nexus 4 sports a 640 x 384 dp (xhdpi) screen
instead. Same 56dp app bar height. Its nav drawer?
[width in dp] = 384dp — 56dp = 328dp
So, how did the Google designers come up with
288dp and 304dp widths, respectively? I have no idea.
Google apps, on
the other hand, seem to agree with my maths. Oh, and you know what the
funniest thing is in all this? The iPhone (which has different screen
heights, but a constant 320 dp width) is marked correctly as having a
264dp nav drawer.
Basically, it shows that some guidelines about the navigation drawer contradict themselves and that you can use the following rule to avoid calculations:
You can basically always use 304dp on -sw360dp
and 320dp on -sw384dp for your navigation drawer, and you'll get it right.
They already updated specs, now navigation drawer width is:
Math.min(screenWidth — actionBarSize, 6 * actionBarSize);
Well I found this very difficult to understand and implement. Unfortunately, Matthew's solution made my nav drawer too wide for landscape, and it seemed contrary to Google's practices, i.e. the nav width is determined by the device's smallest width. In any event it wouldn't work in my case as I disabled the configuration in my manifest. So I decided to changed the nav width dynamically with the following code.
It should be noted my app is just for phones and I settled on 320dp being the maximum width. This is also why I've settled on 56dp toolbar height for both orientations.
Hope it helps someone, and that they can avoid the unnecessary stress it caused me.
navDrawLayout.post(new Runnable()
{
#Override
public void run() {
Resources r = getResources();
DisplayMetrics metrics = new DisplayMetrics();
if(r.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int height = metrics.heightPixels;
float screenWidth = height / r.getDisplayMetrics().density;
float navWidth = (screenWidth - 56);
navWidth = Math.min(navWidth, 320);
int newWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, navWidth, r.getDisplayMetrics());
DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) navDrawLayout.getLayoutParams();
params.width = newWidth;
navDrawLayout.setLayoutParams(params);
}
if(r.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int width = metrics.widthPixels;
float screenWidth = width / r.getDisplayMetrics().density;
float navWidth = screenWidth - 56;
navWidth = Math.min(navWidth, 320);
int newWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, navWidth, r.getDisplayMetrics());
DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) navDrawLayout.getLayoutParams();
params.width = newWidth;
navDrawLayout.setLayoutParams(params);
}
}
}
);
The Navigation Drawer width depends on the device smallest width, orientation and Android version.
Check out this article about sizing Navigation Drawer.
Related
I'm trying to correctly set the height of the view (mapfragment in below example) that normally sits behind the BottomSheet.
I want the bottom sheet to persist in a half-open state with the option to expand to full screen, but not fully collapse. Example of desired functionality on the two right screens of these uber examples. To do this I set the peek height to half the screen width and the mapfragment height to half and that kinda worked
// Get bottom nav height
var navBarHeight = 0
val navBarId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (navBarId > 0) {
navBarHeight = convertDpToPixel(resources.getDimensionPixelSize(navBarId))
}
// Set bottom sheet "persisted" open height
val metrics = resources.displayMetrics
bottomSheetBehavior.peekHeight = metrics.heightPixels / 2
// programmatically set map height
val params = mapFragment?.view?.layoutParams
params?.height = (metrics.heightPixels / 2) - navBarHeight
mapFragment?.view?.layoutParams = params
However, there's an issue as shown in this screenshot - fragment height mismatch - where the "Google" logo and "locate me" button on the map are slightly covered due to some insets not being accounted for.
I'm assuming I have to dynamically account for the insets and bottom nav to get the correct height. This just seems rather error prone though considering the varying sizes of those across devices and versions. I have to imagine there's a better way, no?
Due to limitations of the BottomSheet component, I can't wrap the FrameLayout in a LinearLayout and use a layout_weight solution as it needs to be a direct child of the CoordinatorLayout.
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- I want both of these fragments to fill 50% of this coordinator layout -->
<fragment
android:id="#+id/mapFragment"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="#+id/standardBottomSheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<!--... bottom screen layout ... -->
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Is there something I'm missing here?
public DrawerProfile(Context context) {
super(context);
HeaderImageView = new ImageView(context);
HeaderImageView.setVisibility(VISIBLE);
HeaderImageView.setScaleType(ImageView.ScaleType.CENTER);
HeaderImageView.setImageResource(R.mipmap.drawer_background_image);
addView(HeaderImageView);
}
I want to add an image in Drawer and it should cover the entire area of drawer header. I want to know what should the size of the Image (resolution) be; which will be suitable for every phone with variety of screen resolution. How can I minimize the size of the photo?
Here in this screenshot, The header image is not covering the entire area of Drawer
i recently made an app and did thorough research on almost every Material Design aspect, so i would like to share my experience here, it might help you.
1st go through this wonderful article, it will guide you set up Nav Drawer with every property and views used with it.
Drawer Image should be or usually 16/9 of your Nav Drawer's width.
( HeaderHeight = NavDrawerWidth * 9/16 )
I used an image of 576x324 pixels (pretty clean and nice pic, nearly 27KB) and put it inside drawable-nodpi to avoid auto scaling and memory issues.
I use nav Drawer of width 304dp (mostly you will find it, on google apps, but they have also used 320dp on some apps as well, like Play Music, Hangouts etc).
Height of HeaderImage probably stay the same for almost all Devices except tablets.
For devices till sw-480dp-xxxhdpi use Drawer width 304dp and Header Height of 170dp.
From devices sw-600dp above, use Drawer width 400dp and Header Image height 225dp at least.
This is my drawer_header.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="#dimen/navDrawer_header_height"
android:background="#drawable/img_navdrawer_header"
android:gravity="bottom"
android:orientation="vertical"
android:padding="16dp"
android:theme="#style/ThemeOverlay.AppCompat.Dark" >
</LinearLayout>
And this is how i have used it inside NavigationView
<android.support.design.widget.NavigationView
android:id="#+id/navigation_view"
android:layout_width="#dimen/nav_drawer_width"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="#layout/drawer_header"
app:menu="#menu/drawer" />
Now time to set their boundaries, /res/values/dimens/
<dimen name="nav_drawer_width">304dp</dimen>
<dimen name="navDrawer_header_height">170dp</dimen>
For tablets: /res/values-sw600dp/, /res/values/sw-720dp
<dimen name="nav_drawer_width">400dp</dimen>
<dimen name="navDrawer_header_height">225dp</dimen>
Hope this helps someone.
I use this library for dimension
https://github.com/intuit/sdp
And put header highlight as #dimen/_180sdp.
So 180 dp is the size of header
Looks perfect !
Given HeaderImageView is set to match the width and height of the Drawer, just set the ScaleType to FIT_CENTER then your image will scale to fill the entire Drawer.
Use the code Below to load an image into a Relative or Linear Layout in Navigation Drawer Header
RelativeLayout imgNavHeaderBg = navHeader.findViewById(R.id.headerRelativelayout);
imgNavHeaderBg.post(new Runnable(){
public void run(){
Glide.with("Your Class".this).load("URL").asBitmap().into(new SimpleTarget<Bitmap>(imgNavHeaderBg.getMeasuredWidth(), imgNavHeaderBg.getMeasuredHeight()) {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
Drawable drawable = new BitmapDrawable(getResources(), resource);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
imgNavHeaderBg.setBackground(drawable);
}
}
});
}
});
Rule of thumb for header view height is
HeaderHeight = NavDrawerWidth * 9/16.
So basically it's between 140 to 169dp.
use different dimen file for best result in different screen
hdpi - height = 170dp
xhdpi - height = 180dp
For some reason I can't use a Dialog, so I'm just emulating it with a View hierarchy. I want to make my dialog appear in the center of the screen with a standard Android dialog size.
I found a resource value dialog_min_width_minor, but how do I use it? When I try to do it like this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#color/semi_transparent"
android:layout_width="match_parent"
android:layout_height="match_parent"> <!-- provides the gray semi-transparent background !-->
<FrameLayout
android:id="#+id/dialog_container"
android:layout_gravity="center"
android:background="#android:color/white"
android:layout_height="wrap_content"
android:layout_width="#android:dimen/dialog_min_width_minor"/>
</FrameLayout>
I just get an exception:
Binary XML file line #8: You must supply a layout_width attribute.
I spent a bit of time and came upon this example. You can emulate a Dialog using an Activity with this theme.
Interestingly enough, the AppCompat library has its own definition of these properties, but they aren't lined up with the standard sizes.
For what it's worth, android.R.dimen.dialog_min_width_minor is defined like this in one case:
<item type="dimen" name="dialog_min_width_minor">95%</item>
You can't set this as a width in an xml layout, but you can systematically use it to set the width in your activity/fragment/view. You can do something like this:
// Get the percentage as defined by android.R.dimen.dialog_min_width_minor
Resources resources = context.getResources();
TypedValue typedValue = new TypedValue();
resources.getValue(android.R.dimen.dialog_min_width_minor, typedValue, true);
float percentage = typedValue.getFraction(1, 1);
// Get the width of the display
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int displayWidth = size.x;
// set the width of your view
// view.width = displayWidth * percentage <--pseudocode
I'm using a SlidingPaneLayout in my activity:
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/myslidingpanelayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- menu left -->
<LinearLayout
android:id="#+id/menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#8d305f"
android:orientation="vertical" >
...
</LineareLayout>
<!-- main page right-->
<LinearLayout
android:id="#+id/right_main"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#fff"
android:orientation="vertical" >
...
</LineareLayout>
</android.support.v4.widget.SlidingPaneLayout>
I want the menu to cover 3/4 of the page I want it to work on all the phones so I can't put for example
android:layout_width="300dp"
I want to calculate the screen width and set it to the left pane
Thank for your help
Thanks for you all I found this answer and it works with me:
int width;
int height;
if (android.os.Build.VERSION.SDK_INT >= 13){
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
width = size.x;
height = size.y;
}else {
Display display = getWindowManager().getDefaultDisplay();
width = display.getWidth(); // deprecated
height = display.getHeight(); // deprecated
}
if(width>0&&height>0){
LinearLayout layout = (LinearLayout)findViewById(R.id.menu);
// Gets the layout params that will allow you to resize the layout
LayoutParams params = layout.getLayoutParams();
// Changes the height and width to the specified *pixels*
params.height = height;
params.width = width*3/4;
}
Just looking up the doc for sliding pane, looks like it functions like a linear layout, and can use the
layout_weight
parameter to set a percentage based width since the parent viewgroup is match_parent
In the case of 3/4 = 75% you can
android:layout_weight="0.75"
From the android docs http://developer.android.com/reference/android/support/v4/widget/SlidingPaneLayout.html:
Like LinearLayout, SlidingPaneLayout supports the use of the layout parameter layout_weight on child views to determine how to divide leftover space after measurement is complete. It is only relevant for width. When views do not overlap weight behaves as it does in a LinearLayout.
When views do overlap, weight on a slideable pane indicates that the pane should be sized to fill all available space in the closed state. Weight on a pane that becomes covered indicates that the pane should be sized to fill all available space except a small minimum strip that the user may use to grab the slideable view and pull it back over into a closed state.
And from the LinearLayout docs http://developer.android.com/guide/topics/ui/layout/linear.html#Weight
Note: You will end up setting the layout_width parameter to 0dp since the view group will actually use the weight to lay the children out
Apart from Selecsosi's answer, which is correct, there is also this view I wrote to always display the second item as a pane (ignoring the default show-side-by-side-if-the-fit behaviour). It can, as the name shows, wrap around the sliding view.
You can implement the behaviour you're after by either using a lot of #dimen resources and switching them based on swXXXdp-(port|land) or just setting the sliding view's width at runtime (something I'm reasonably certain you can do with the default layout as well).
I have a RelativeLayout which holds an ImageView and an ImageButton. The ImageView serves as an container for a background image. Now I'm trying to set the button at a fixed position so that it always appears on the same position on the background image.
Here is the layout file I'm using:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/relativeLayout1" android:layout_height="match_parent"
android:layout_width="match_parent" android:gravity="center_horizontal">
<ImageView android:src="#drawable/bg_1" android:id="#+id/imgView"
android:adjustViewBounds="true" android:layout_height="match_parent"
android:layout_width="match_parent" />
<ImageButton android:layout_width="120dp"
android:background="#drawable/button_1" android:layout_height="30dp"
android:id="#+id/imgButton" android:layout_marginLeft="100dp"
android:layout_marginTop="170dp" />
</RelativeLayout>
As you can see I've tried positioning the button with it's left-/top-margin using dp as unit, but this doesn't work. Since the background image is beeing scaled down/up, the position would have to be dynamic in some kind of way.
I understand that absolute positioning, with pixel-values for x-/y-position, is something that won't work on Android, like it is explained here. I still need to solve this and am not sure how.
Would I have to calculate the values for left-/top-margin (not sure how that would be) and then set them with something like this?
final float density = getResources().getDisplayMetrics().density;
int width = (int)((float)120 * density);
int height = (int)((float)120 * density);
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(width, height);
rlp.leftMargin = newMargin;
rlp.topMargin = newTopMargin;
ImageButton imgButton = (ImageButton) findViewById(R.id.imgButton);
imgButton.setLayoutParams(rlp);
Hope I didn't forget something ...
//EDIT:
I was thinking, the reason for the issue might be, that the scaled image has different "borders", depending on the screen size.
With an image at a 1:1.6 ratio on a HVGA screen I have black bars on the left and right, whereas on a WVGA screen the bars are on the left. Considering I'm using the default scaling. I will look into it and post again, if necessaray...
Why scaling happens? Because of different dpi on different devices? Do you have different drawabled for different dpi settings? If it isn't just dpi issue and you want to scale that background image freely then you can't do the job using standard layouts. You should implement a custom one.