I am trying to implement auto-hiding feature for my toolbar using CoordinateLayout. But I also want to tweaks it a bit, so that the hide/show only works after I scroll past my imageview.
I am currently able to "turn on/off" the hide/show availability for the toolbar via AppBarLayout.LayoutParams. So right now it won't hide the toolbar if I scroll within the imageview's height and will hide if I scroll pass the imageview. The only problem is, if I scroll the layout very fast and instantly pull my finger before it pass the imageview's height the toolbar won't hide. In my opinion it is because the scrolling listener's function is called before the parameter of the toolbar's show/hide is changed to enable.
Here's the code:
ArticleActivity.java
public class ArticleActivity extends AppCompatActivity {
private Toolbar toolbar;
private ImageView imageView;
private NestedScrollView scrollView;
private Matrix matrix;
private int imageHeight = 0;
private float scale = 0;
private boolean hideable = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_article);
imageView = (ImageView) findViewById(R.id.imageView);
scrollView = (NestedScrollView) findViewById(R.id.scrollView);
toolbar = (Toolbar) findViewById(R.id.toolbar);
matrix = new Matrix();
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
scale = (float) displayMetrics.widthPixels / ((float) imageView.getDrawable().getIntrinsicWidth());
matrix.postScale(scale, scale);
imageView.setImageMatrix(matrix);
if(imageView.getViewTreeObserver().isAlive()) {
imageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
imageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
imageHeight = imageView.getHeight();
}
});
}
if(scrollView.getViewTreeObserver().isAlive()) {
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
int scrollY = scrollView.getScrollY();
if(scrollY <= imageHeight) {
matrix.setTranslate(0, scrollY / 4);
matrix.postScale(scale, scale);
imageView.setImageMatrix(matrix);
if(hideable) {
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(0);
toolbar.setLayoutParams(params);
hideable = false;
}
} else {
if(!hideable) {
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
// 1 = scroll, 4 = enterAlways, 16 = snap
params.setScrollFlags(21);
toolbar.setLayoutParams(params);
hideable = true;
}
}
}
});
}
}}
activity_article.xml:
<android.support.design.widget.CoordinatorLayout 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.support.design.widget.AppBarLayout
android:id="#+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="matrix"
android:src="#drawable/download"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/imageView"
android:text="What is Lorem Ipsum?"/>
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>
What should I do to fix this? Or is there another way to achieve this? Thank you
I am trying to achieve a similar behavior to that of Telegram, on the settings page, that is, there is a CircleImage that when scrolling up goes to the left of the Topbar title, and when scrolling down goes to the middle of the expanded AppBarLayout.
I was basing my work on this example:
https://github.com/saulmm/CoordinatorBehaviorExample
But in this case the original coder is recreating the Topbar twice. I dont want to do that, the default behavior of the topbar is what I need and also I want to take advantage of the hamburger menu and the options menu that come out of the box.
This is my view hierarchy:
DrawerLayout
|
|---CoordinatorLayout
|--AppBarLayout
| |-CollapsingToolbarLayout
| |-ImageView (backdrop image)
| |-Toolbar
|--NestedScrollView
|--ImageView (circleimage avatar)
As you can see I cannot make the Toolbar layout a sibling of my CircleImage so I cannot bind them together on the layoutDependsOn method. I tried binding to the AppBarLayout basing my code off the one on the github repo but to be honest I cannot make much sense of what's happening in the original code.
My behavior was implemented in much the same manner as Saul's. The biggest difference is that I like to put a non-visible view like a Space where I wanted the circle image to end up, then use that view's bounds to determine how to move & size the circle image.
public class CollapsingImageBehavior extends CoordinatorLayout.Behavior<View> {
private final static int X = 0;
private final static int Y = 1;
private final static int WIDTH = 2;
private final static int HEIGHT = 3;
private int mTargetId;
private int[] mView;
private int[] mTarget;
public CollapsingImageBehavior() {
}
public CollapsingImageBehavior(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CollapsingImageBehavior);
mTargetId = a.getResourceId(R.styleable.CollapsingImageBehavior_collapsedTarget, 0);
a.recycle();
}
if (mTargetId == 0) {
throw new IllegalStateException("collapsedTarget attribute not specified on view for behavior");
}
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
setup(parent, child);
AppBarLayout appBarLayout = (AppBarLayout) dependency;
int range = appBarLayout.getTotalScrollRange();
float factor = -appBarLayout.getY() / range;
int left = mView[X] + (int) (factor * (mTarget[X] - mView[X]));
int top = mView[Y] + (int) (factor * (mTarget[Y] - mView[Y]));
int width = mView[WIDTH] + (int) (factor * (mTarget[WIDTH] - mView[WIDTH]));
int height = mView[HEIGHT] + (int) (factor * (mTarget[HEIGHT] - mView[HEIGHT]));
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
lp.width = width;
lp.height = height;
child.setLayoutParams(lp);
child.setX(left);
child.setY(top);
return true;
}
private void setup(CoordinatorLayout parent, View child) {
if (mView != null) return;
mView = new int[4];
mTarget = new int[4];
mView[X] = (int) child.getX();
mView[Y] = (int) child.getY();
mView[WIDTH] = child.getWidth();
mView[HEIGHT] = child.getHeight();
View target = parent.findViewById(mTargetId);
if (target == null) {
throw new IllegalStateException("target view not found");
}
mTarget[WIDTH] += target.getWidth();
mTarget[HEIGHT] += target.getHeight();
View view = target;
while (view != parent) {
mTarget[X] += (int) view.getX();
mTarget[Y] += (int) view.getY();
view = (View) view.getParent();
}
}
}
And here's the layout. One important thing I found out is that the circle image view needed to have an elevation set so that it would lay out atop the toolbar in collapsed mode, otherwise it would be behind the toolbar and not shown.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.krislarson.customcoordinatorlayoutbehavior.ScrollingActivity">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="280dp"
android:minHeight="108dp"
android:fitsSystemWindows="true"
app:title="Abby"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleGravity="center_horizontal"
app:expandedTitleMarginTop="140dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="#+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/sunset"
app:layout_collapseMode="parallax"
android:scaleType="centerCrop"/>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="#style/AppTheme.PopupOverlay">
<Space
android:id="#+id/circle_collapsed_target"
android:layout_width="40dp"
android:layout_height="40dp"/>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_scrolling"/>
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/circle_image_view"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="#drawable/abby"
android:layout_marginTop="220dp"
android:layout_gravity="top|center_horizontal"
android:elevation="8dp"
app:border_color="#android:color/black"
app:border_width="2dp"
app:collapsedTarget="#id/circle_collapsed_target"
app:layout_behavior="com.krislarson.customcoordinatorlayoutbehavior.CollapsingImageBehavior"/>
</android.support.design.widget.CoordinatorLayout>
You can see the entire demo project at https://github.com/klarson2/CustomCoordinatorLayoutBehavior
One possibility would be to create a custom view for your ToolBar and hide the red dot in the ToolBar if it is expanded and show an ImageView with the red dot instead (which is hidden when the toolbar is collapsed).
You can see how to add a custom view to a ToolBar at this answer: https://stackoverflow.com/a/27859966/5052976
After doing this just create a ImageView that is visible when the ToolBar is expanded.
final CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsingToolbarLayout);
AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appBarLayout);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
boolean isShow = false;
int scrollRange = -1;
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (scrollRange == -1) {
scrollRange = appBarLayout.getTotalScrollRange();
}
if (scrollRange + verticalOffset == 0) {
//show toolbar dot and hide imageview dot
isShow = true;
} else if(isShow) {
//hide toolbar dot and show imageview dot
isShow = false;
}
}
});
Unfortunately I can't test this right now but I think it should work ;-)
I am creating an app with a recyclerview. And above the RV I have an image, which should get smaller, when i scroll. This works, but the RV scrolls also. I want that first the image gets smaller and then the recyclerview starts scrolling. But how can I do this? Here is my XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/b"
android:id="#+id/test_photo"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="test"/>
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:layout_anchor="#+id/test_photo"
android:background="#color/colorPrimary"
app:layout_anchorGravity="bottom|start">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/colorWhite"
android:textSize="30sp"
android:text="username"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/user_view_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
And this is the code to resize the image:
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
float state = 0.0f;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
Log.e("Y",Integer.toString(dy));
state+=dy;
LinearLayout img = (LinearLayout) findViewById(R.id.test_photo);
Log.e("STATE", Float.toString(state));
if(state >= 500){
img.getLayoutParams().height = minWidth;
img.getLayoutParams().width = minWidth;
img.requestLayout();
}
if(state <= 0){
img.getLayoutParams().height = imgHeight;
img.getLayoutParams().width = imgHeight;
img.requestLayout();
}
if(state > 0 && state < 500){
//up
img.getLayoutParams().height = (int)(imgHeight - ((float)(imgHeight-minWidth)/500)*state);
img.getLayoutParams().width = (int)(imgHeight - ((float)(imgHeight-minWidth)/500)*state);
img.requestLayout();
}
}
});
Thanks for the help!
EDIT:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="320dp"
android:fitsSystemWindows="true"
android:theme="#style/AppTheme.AppBarOverlay">
<com.obware.alifsto.HelpClasses.CollapsingImageLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:minHeight="108dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="#+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="#drawable/sunset" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
<ImageView
android:id="#+id/avatar"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="96dp"
android:src="#drawable/logo_blau_weiss"
android:transitionName="#string/transition_userview_image"/>
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="48dp"
android:text="Title"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold" />
<TextView
android:id="#+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="24dp"
android:text="Subtitle "
android:transitionName="#string/transition_userview_username"
android:textAppearance="?android:attr/textAppearanceMedium" />
</com.obware.alifsto.HelpClasses.CollapsingImageLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/user_interface_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
</android.support.v7.widget.RecyclerView>
The way you want to do this is with CoordinatorLayout and AppBarLayout and use all that Material Design scrolling goodness.
So essentially what you do is create a specialized layout similar to CollapsingToolbarLayout. For my demo, I used code from that class as inspiration to get my collapsing image layout to work.
What makes it work is adding the layout as a direct child of AppBarLayout, then creating an AppBarLayout.OnOffsetChangeListener and registering it with the AppBarLayout. When you do this, you will get notifications when the user scrolls and the layout is scrolled up.
Another big part of this is setting a minimum height. AppBarLayout uses the minimum height to determine when to stop scrolling your layout, leaving you with a collapsed layout area.
Here's a code excerpt:
class OnOffsetChangedListener implements AppBarLayout.OnOffsetChangedListener {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
final int scrollRange = appBarLayout.getTotalScrollRange();
float offsetFactor = (float) (-verticalOffset) / (float) scrollRange;
Log.d(TAG, "onOffsetChanged(), offsetFactor = " + offsetFactor);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
if (child instanceof Toolbar) {
if (getHeight() - insetTop + verticalOffset >= child.getHeight()) {
offsetHelper.setTopAndBottomOffset(-verticalOffset); // pin
}
}
if (child.getId() == R.id.background) {
int offset = Math.round(-verticalOffset * .5F);
offsetHelper.setTopAndBottomOffset(offset); // parallax
}
if (child.getId() == R.id.avatar) {
float scaleFactor = 1F - offsetFactor * .5F ;
child.setScaleX(scaleFactor);
child.setScaleY(scaleFactor);
int topOffset = (int) ((mImageTopCollapsed - mImageTopExpanded) * offsetFactor) - verticalOffset;
int leftOffset = (int) ((mImageLeftCollapsed - mImageLeftExpanded) * offsetFactor);
child.setPivotX(0);
child.setPivotY(0);
offsetHelper.setTopAndBottomOffset(topOffset);
offsetHelper.setLeftAndRightOffset(leftOffset);
}
if (child.getId() == R.id.title) {
int topOffset = (int) ((mTitleTopCollapsed - mTitleTopExpanded) * offsetFactor) - verticalOffset;
int leftOffset = (int) ((mTitleLeftCollapsed - mTitleLeftExpanded) * offsetFactor);
offsetHelper.setTopAndBottomOffset(topOffset);
offsetHelper.setLeftAndRightOffset(leftOffset);
Log.d(TAG, "onOffsetChanged(), offsetting title top = " + topOffset + ", left = " + leftOffset);
Log.d(TAG, "onOffsetChanged(), offsetting title mTitleLeftCollapsed = " + mTitleLeftCollapsed + ", mTitleLeftExpanded = " + mTitleLeftExpanded);
}
if (child.getId() == R.id.subtitle) {
int topOffset = (int) ((mSubtitleTopCollapsed - mSubtitleTopExpanded) * offsetFactor) - verticalOffset;
int leftOffset = (int) ((mSubtitleLeftCollapsed - mSubtitleLeftExpanded) * offsetFactor);
offsetHelper.setTopAndBottomOffset(topOffset);
offsetHelper.setLeftAndRightOffset(leftOffset);
}
}
}
}
The lines child.setScaleX() and child.setScaleY() are the code that actually changes the size of the image.
Demo app is on GitHub at https://github.com/klarson2/Collapsing-Image. Enjoy.
EDIT: After adding a TabLayout I realized one mistake I made in my layout, which was to make the AppBarLayout a fixed height, then make the custom collapsing component height be match_parent. This makes it so you can't see the TabLayout that is added to the app bar. I changed the layout so that AppBarLayout height was wrap_content and the custom collapsing component had the fixed height. This makes it possible to add additional components like a TabLayout to the AppBarLayout. This has been corrected in the latest revision on GitHub.
With the following code I resize the image according to the scrolling. So that you can see it collapsed in the AppBar.
Play with the values of the duration of the animation and the value of the scaling when the AppBar is collapsed.
In my case I have the Toolbar as transparent and I manage the colors of the AppBar elements at run times.
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
/**
* Collapsed
*/
if (Math.abs(verticalOffset) == appBarLayout.getTotalScrollRange()) {
myImage.animate().scaleX((float)0.4).setDuration(3000);
myImage.animate().scaleY((float)0.4).setDuration(3000);
myImage.animate().alpha(1).setDuration(0);
/**
* Expanded
*/
} else if (verticalOffset == 0) {
myImage.animate().scaleX((float)1).setDuration(100);
myImage.animate().scaleY((float)1).setDuration(100);
myImage.animate().alpha(1).setDuration(0);
/**
* Somewhere in between
*/
} else {
final int scrollRange = appBarLayout.getTotalScrollRange();
float offsetFactor = (float) (-verticalOffset) / (float) scrollRange;
float scaleFactor = 1F - offsetFactor * .5F;
myImage.animate().scaleX(scaleFactor);
myImage.animate().scaleY(scaleFactor);
}
}
PD: This works regardless of whether the image exceeds the limits of the AppBar, as if the image were a floating button.
GL
Sources
Listener
Conditionals
Some methods
I'm trying to implement the CollapsingToolbarLayout with a custom view, but I'm unable to do it :
What I want to do (sorry I can't post images so it's on imgur) :
Expanded, the header is a profile screen with image and title
Not expanded (on scroll), the image and title will be on the toolbar
But everything I saw wasn't working as I expected
I'm new to this and lollipop animations so if someone could help me I'll be very grateful !
(I don't post sample code because I don't have something relevant to post)
My Solution
I had the same scenario to implement so I started with the dog example and made some changes for it to work exactly like you describe. My code can be found as a fork on that project, see https://github.com/hanscappelle/CoordinatorBehaviorExample
Most important changes are in the layout:
<android.support.design.widget.CoordinatorLayout
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.support.design.widget.AppBarLayout
android:id="#+id/main.appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
>
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/main.collapsing"
android:layout_width="match_parent"
android:layout_height="#dimen/expanded_toolbar_height"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
>
<FrameLayout
android:id="#+id/main.framelayout.title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
>
<LinearLayout
android:id="#+id/main.linearlayout.title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:orientation="vertical"
android:paddingBottom="#dimen/spacing_small"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="bottom|center_horizontal"
android:text="#string/tequila_name"
android:textColor="#android:color/white"
android:textSize="#dimen/textsize_xlarge"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="#dimen/spacing_xxsmall"
android:text="#string/tequila_tagline"
android:textColor="#android:color/white"
/>
</LinearLayout>
</FrameLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingExtra="#dimen/spacing_xsmall"
android:padding="#dimen/spacing_normal"
android:text="#string/lorem"
android:textSize="#dimen/textsize_medium"
/>
</android.support.v4.widget.NestedScrollView>
<android.support.v7.widget.Toolbar
android:id="#+id/main.toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/primary"
app:layout_anchor="#id/main.collapsing"
app:theme="#style/ThemeOverlay.AppCompat.Dark"
app:title=""
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<Space
android:layout_width="#dimen/image_final_width"
android:layout_height="#dimen/image_final_width"
/>
<TextView
android:id="#+id/main.textview.title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="8dp"
android:gravity="center_vertical"
android:text="#string/tequila_title"
android:textColor="#android:color/white"
android:textSize="#dimen/textsize_large"
/>
</LinearLayout>
</android.support.v7.widget.Toolbar>
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="#dimen/image_width"
android:layout_height="#dimen/image_width"
android:layout_gravity="top|center_horizontal"
android:layout_marginTop="#dimen/spacing_normal"
android:src="#drawable/ninja"
app:border_color="#android:color/white"
app:border_width="#dimen/border_width"
app:finalHeight="#dimen/image_final_width"
app:finalXPosition="#dimen/spacing_small"
app:finalYPosition="#dimen/spacing_small"
app:finalToolbarHeight="?attr/actionBarSize"
app:layout_behavior="saulmm.myapplication.AvatarImageBehavior"
/>
</android.support.design.widget.CoordinatorLayout>
And in the AvatarImageBehaviour class that I optimised for only moving the avatar from the original position to the position configured in the attributes. So if you want the image to move from another location just move it within the layout. When you do so make sure the AppBarLayout is still a sibling of it or it won't be found in code.
package saulmm.myapplication;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.View;
import de.hdodenhof.circleimageview.CircleImageView;
public class AvatarImageBehavior extends CoordinatorLayout.Behavior<CircleImageView> {
// calculated from given layout
private int startXPositionImage;
private int startYPositionImage;
private int startHeight;
private int startToolbarHeight;
private boolean initialised = false;
private float amountOfToolbarToMove;
private float amountOfImageToReduce;
private float amountToMoveXPosition;
private float amountToMoveYPosition;
// user configured params
private float finalToolbarHeight, finalXPosition, finalYPosition, finalHeight;
public AvatarImageBehavior(
final Context context,
final AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AvatarImageBehavior);
finalXPosition = a.getDimension(R.styleable.AvatarImageBehavior_finalXPosition, 0);
finalYPosition = a.getDimension(R.styleable.AvatarImageBehavior_finalYPosition, 0);
finalHeight = a.getDimension(R.styleable.AvatarImageBehavior_finalHeight, 0);
finalToolbarHeight = a.getDimension(R.styleable.AvatarImageBehavior_finalToolbarHeight, 0);
a.recycle();
}
}
#Override
public boolean layoutDependsOn(
final CoordinatorLayout parent,
final CircleImageView child,
final View dependency) {
return dependency instanceof AppBarLayout; // change if you want another sibling to depend on
}
#Override
public boolean onDependentViewChanged(
final CoordinatorLayout parent,
final CircleImageView child,
final View dependency) {
// make child (avatar) change in relation to dependency (toolbar) in both size and position, init with properties from layout
initProperties(child, dependency);
// calculate progress of movement of dependency
float currentToolbarHeight = startToolbarHeight + dependency.getY(); // current expanded height of toolbar
// don't go below configured min height for calculations (it does go passed the toolbar)
currentToolbarHeight = currentToolbarHeight < finalToolbarHeight ? finalToolbarHeight : currentToolbarHeight;
final float amountAlreadyMoved = startToolbarHeight - currentToolbarHeight;
final float progress = 100 * amountAlreadyMoved / amountOfToolbarToMove; // how much % of expand we reached
// update image size
final float heightToSubtract = progress * amountOfImageToReduce / 100;
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
lp.width = (int) (startHeight - heightToSubtract);
lp.height = (int) (startHeight - heightToSubtract);
child.setLayoutParams(lp);
// update image position
final float distanceXToSubtract = progress * amountToMoveXPosition / 100;
final float distanceYToSubtract = progress * amountToMoveYPosition / 100;
float newXPosition = startXPositionImage - distanceXToSubtract;
//newXPosition = newXPosition < endXPosition ? endXPosition : newXPosition; // don't go passed end position
child.setX(newXPosition);
child.setY(startYPositionImage - distanceYToSubtract);
return true;
}
private void initProperties(
final CircleImageView child,
final View dependency) {
if (!initialised) {
// form initial layout
startHeight = child.getHeight();
startXPositionImage = (int) child.getX();
startYPositionImage = (int) child.getY();
startToolbarHeight = dependency.getHeight();
// some calculated fields
amountOfToolbarToMove = startToolbarHeight - finalToolbarHeight;
amountOfImageToReduce = startHeight - finalHeight;
amountToMoveXPosition = startXPositionImage - finalXPosition;
amountToMoveYPosition = startYPositionImage - finalYPosition;
initialised = true;
}
}
}
Sources
Most common example is the one with the dog listed at https://github.com/saulmm/CoordinatorBehaviorExample . It's a good example but indeed has the toolbar in the middle of the expanded view with a backdrop image that also moves. All that was removed in my example.
Another explanation is found at http://www.devexchanges.info/2016/03/android-tip-custom-coordinatorlayout.html but since that cloud/sea backdrop image referenced there is also found in the dog example one is clearly build on top of the other.
I also found this SO question with a bounty awarded but couldn't really find out what the final solution was Add icon with title in CollapsingToolbarLayout
And finally this should be a working library that does the work. I've checked it out but the initial image wasn't centered and I rather worked on the dog example that I had looked at before. See https://github.com/datalink747/CollapsingAvatarToolbar
More to read
http://saulmm.github.io/mastering-coordinator
http://www.androidauthority.com/using-coordinatorlayout-android-apps-703720/
https://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.html
https://guides.codepath.com/android/handling-scrolls-with-coordinatorlayout
I'd like to create a full width navigation drawer. Setting layout_width to match_parent on #+id/left_drawer yields in width of about 80% of screen space. This seems to be the standard behavior. Do I have to override onMeasure() of DrawerLayout?
My current code:
<?xml version="1.0" encoding="utf-8"?>
<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">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black"
android:id="#+id/mainFragmentContainer">
</FrameLayout>
<include
android:id="#+id/left_drawer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
layout="#layout/drawer"/>
</android.support.v4.widget.DrawerLayout>
Thanks.
If you want simpler solution you can just set negative margin
android:layout_marginLeft="-64dp"
for your left_drawer:
<include
android:id="#+id/left_drawer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
layout="#layout/drawer"
android:layout_marginLeft="-64dp"/>
Because all these answers did not work on OS 6.0.1, I'll post here the solution that worked for me in combination with DrawerLayout + NavigationView.
So all what I do is change the width of the NavigationView programatically:
mNavigationView = (NavigationView) findViewById(R.id.nv_navigation);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) mNavigationView.getLayoutParams();
params.width = metrics.widthPixels;
mNavigationView.setLayoutParams(params);
This works for all screen sizes.
Yes, you have to extend DrawerLayout and override some methods because MIN_DRAWER_MARGIN is private
Here is a possible solution:
public class FullDrawerLayout extends DrawerLayout {
private static final int MIN_DRAWER_MARGIN = 0; // dp
public FullDrawerLayout(Context context) {
super(context);
}
public FullDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FullDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
throw new IllegalArgumentException(
"DrawerLayout must be measured with MeasureSpec.EXACTLY.");
}
setMeasuredDimension(widthSize, heightSize);
// Gravity value for each drawer we've seen. Only one of each permitted.
int foundDrawers = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isContentView(child)) {
// Content views get measured at exactly the layout's size.
final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
child.measure(contentWidthSpec, contentHeightSpec);
} else if (isDrawerView(child)) {
final int childGravity =
getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
if ((foundDrawers & childGravity) != 0) {
throw new IllegalStateException("Child drawer has absolute gravity " +
gravityToString(childGravity) + " but this already has a " +
"drawer view along that edge");
}
final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
MIN_DRAWER_MARGIN + lp.leftMargin + lp.rightMargin,
lp.width);
final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
lp.topMargin + lp.bottomMargin,
lp.height);
child.measure(drawerWidthSpec, drawerHeightSpec);
} else {
throw new IllegalStateException("Child " + child + " at index " + i +
" does not have a valid layout_gravity - must be Gravity.LEFT, " +
"Gravity.RIGHT or Gravity.NO_GRAVITY");
}
}
}
boolean isContentView(View child) {
return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
}
boolean isDrawerView(View child) {
final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
final int absGravity = Gravity.getAbsoluteGravity(gravity,
child.getLayoutDirection());
return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
}
int getDrawerViewGravity(View drawerView) {
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return Gravity.getAbsoluteGravity(gravity, drawerView.getLayoutDirection());
}
static String gravityToString(int gravity) {
if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
return "LEFT";
}
if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
return "RIGHT";
}
return Integer.toHexString(gravity);
}
}
Based on the Robert's Answer, you can use the layout_marginLeft=-64dp to solve this problem easily.
However it doesn't seems to work anymore on Android 5.0 and above. So here's my solution that worked for me.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
android:id="#+id/drawer_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="-64dp"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="#layout/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="64dp"/>
<include
android:id="#+id/left_drawer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
layout="#layout/drawer"/>
</android.support.v4.widget.DrawerLayout>
Basically, Add android:layout_marginRight="-64dp" to the root DrawerLayout so all the layout will go to the right for 64dp.
Then I add the layout_marginRight=64dp to the content so it goes back to the original position. Then you can have a full drawer there.
A variant on Grogory's solution:
Instead of subclassing I call the following utility method right after I grab a reference to the drawer layout:
/**
* The specs tell that
* <ol>
* <li>Navigation Drawer should be at most 5*56dp wide on phones and 5*64dp wide on tablets.</li>
* <li>Navigation Drawer should have right margin of 56dp on phones and 64dp on tablets.</li>
* </ol>
* yet the minimum margin is hardcoded to be 64dp instead of 56dp. This fixes it.
*/
public static void fixMinDrawerMargin(DrawerLayout drawerLayout) {
try {
Field f = DrawerLayout.class.getDeclaredField("mMinDrawerMargin");
f.setAccessible(true);
f.set(drawerLayout, 0);
drawerLayout.requestLayout();
} catch (Exception e) {
e.printStackTrace();
}
}
Nipper's FullDrawerLayout Class is just simply awesome.. it's performance is also faster than the default drawer how ever you can;t use it on devices with api that don't have view.getLayoutDirection();
(i'e : Class doesn;t work on all gingerbread devices )
so what i did was
replaced all
view.getLayoutDirection();
with the below code
GravityCompat.getAbsoluteGravity(gravity,ViewCompat.getLayoutDirection(this));
I have my support library updated to the latest also have extended the fullDrawerlayout to the support navigational drawer. Now it works fine Gingerbread devices as well
Another possible way to solve the issue without overriding too much:
public class FullScreenDrawerLayout extends DrawerLayout {
... //List of constructors calling
... //super(...);
... //init();
/** Make DrawerLayout to take the whole screen. */
protected void init() {
try {
Field field = getClass().getSuperclass().getDeclaredField("mMinDrawerMargin");
field.setAccessible(true);
field.set(this, Integer.valueOf(0));
} catch (Exception e) {
throw new IllegalStateException("android.support.v4.widget.DrawerLayout has changed and you have to fix this class.", e);
}
}
}
If, at some point, support library is updated and mMinDrawerMargin is not there anymore you will get exception and fix problem before you publish your next update.
I didn't make measurements, but suppose there is not so many reflection to affect performance. Furthermore, it executes only per view creation.
PS
it's strange why DrawerLayout is made so inflexible (I'm about private min margin) at this point...
Try out this worked for me :
<include
android:id="#+id/left_drawer"
android:orientation="vertical"
android:layout_width="320dp"
android:layout_height="match_parent"
android:layout_gravity="start"
layout="#layout/drawer"/>
Set width of included layout android:layout_width="320dp". For devices with different screen size you can dynamically set the width of this included layout.
You can use this. Inspired by this post, I've upgraded for the 5th edition. Because it was having problems with StatusBar in versions 5 and later.
you have to extend DrawerLayout and override some methods because MIN_DRAWER_MARGIN is private
public class FullDrawerLayout extends DrawerLayout {
private static final int MIN_DRAWER_MARGIN = 0; // dp
public FullDrawerLayout(Context context) {
super(context);
}
public FullDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FullDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
throw new IllegalArgumentException(
"DrawerLayout must be measured with MeasureSpec.EXACTLY.");
}
setMeasuredDimension(widthSize, heightSize);
//for support Android 5+
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
params.topMargin = getStatusBarHeight();
setLayoutParams(params);
}
// Gravity value for each drawer we've seen. Only one of each permitted.
int foundDrawers = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isContentView(child)) {
// Content views get measured at exactly the layout's size.
final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
child.measure(contentWidthSpec, contentHeightSpec);
} else if (isDrawerView(child)) {
final int childGravity =
getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
if ((foundDrawers & childGravity) != 0) {
throw new IllegalStateException("Child drawer has absolute gravity " +
gravityToString(childGravity) + " but this already has a " +
"drawer view along that edge");
}
final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
MIN_DRAWER_MARGIN + lp.leftMargin + lp.rightMargin,
lp.width);
final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
lp.topMargin + lp.bottomMargin,
lp.height);
child.measure(drawerWidthSpec, drawerHeightSpec);
} else {
throw new IllegalStateException("Child " + child + " at index " + i +
" does not have a valid layout_gravity - must be Gravity.LEFT, " +
"Gravity.RIGHT or Gravity.NO_GRAVITY");
}
}
}
boolean isContentView(View child) {
return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
}
boolean isDrawerView(View child) {
final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
final int absGravity = Gravity.getAbsoluteGravity(gravity,
child.getLayoutDirection());
return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
}
int getDrawerViewGravity(View drawerView) {
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return Gravity.getAbsoluteGravity(gravity, drawerView.getLayoutDirection());
}
static String gravityToString(int gravity) {
if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
return "LEFT";
}
if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
return "RIGHT";
}
return Integer.toHexString(gravity);
}
public int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}
you can by below code
int width = getResources().getDisplayMetrics().widthPixels/2;
DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) drawer_Linear_layout.getLayoutParams();
params.width = width;
drawer_Linear_layout.setLayoutParams(params);
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/app_bar_dashboard"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="match_parent"
android:layout_marginRight="32dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true">
<include layout="#layout/view_navigation_menu" />
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>
That's works perfectly for me. Hope help others.
Google recommends having a maxim width of 320 dip as per the UI guidelines here.
Moreover, the width can be set by specified the layout_width of the left_drawer ListView.
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".UserListActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:background="#drawable/common_gradient"
android:layoutDirection="rtl"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.2">
<TextView
android:id="#+id/userType_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="نوع المستخدم"
android:textColor="#000000"
android:textSize="20sp"
tools:text="نوع المستخدم" />
<TextView
android:id="#+id/className_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/userType_textView"
android:layout_centerHorizontal="true"
android:text="إسم القسم"
android:textColor="#000000"
android:textSize="16sp"
tools:text="إسم القسم" />
<ImageButton
android:layout_width="30dp"
android:layout_height="20dp"
android:layout_alignBottom="#+id/userType_textView"
android:layout_marginLeft="15dp"
android:layout_marginStart="15dp"
android:background="#android:color/transparent"
android:contentDescription="#string/desc"
android:onClick="showMenuAction"
android:scaleType="fitCenter"
android:src="#drawable/menu" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.8"
android:background="#FAFAFA">
<SearchView
android:id="#+id/user_searchView"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#9CC3D7" />
<ListView
android:id="#+id/users_listView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:layout_below="#+id/user_searchView"
android:layout_centerHorizontal="true"
android:divider="#DFDEE1"
android:dividerHeight="1dp" />
</RelativeLayout>
</LinearLayout>
<android.support.v4.widget.DrawerLayout
android:id="#+id/navigationDrawerUser"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutDirection="rtl">
<ExpandableListView
android:id="#+id/menu_listView_user"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#195269"
android:choiceMode="singleChoice"
android:divider="#2C637D"
android:dividerHeight="1dp"
android:groupIndicator="#null">
</ExpandableListView>
</android.support.v4.widget.DrawerLayout>
Everyone thinks that full-width Sidebar Drawer layout creation is very complicated, but it's very simple if you are following this layout pattern, you don't need to set any minus value.
This is my MainActivity.xml:
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawerLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#color/white"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Main Activity -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/toolbarMain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="#layout/layout_profile_toolbar"/>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/app_navigation" />
</LinearLayout>
<!-- Main Activity End -->
<!-- Custom Navigation Drawer Start -->
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true">
<include
android:id="#+id/custom_nav"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="#layout/fragment_profile"/>
</com.google.android.material.navigation.NavigationView>
<!-- Custom Navigation Drawer End -->
</androidx.drawerlayout.widget.DrawerLayout>
You can set width programmatically.
Give screen full width to navigation view's width.
NavigationView navigationView = findViewById(R.id.nav_view);
DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) navigationView.getLayoutParams();
params.width = Utils.screenWidth(this);
You can also take a look at SlidingDrawer class. It's a deprecated class, but as the documentation says you can write your own implementation based on its source code.