Full width Navigation Drawer - android

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.

Related

Scroll behavior coordinatorLayout custom view

I need this type of behavior to implement.Image should be scroll and set into center with text like wtsapp. but in wtsapp it set into left alignment, i need to set into center. how can i achieve this?
after scrolled image will show like that with text in toolbar.(mentioned)
1. Behavior for CoordinatorLayout and AppBarLayout
public class AvatarImageBehavior extends CoordinatorLayout.Behavior<ImageView> {
// 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;
private boolean onlyVerticalMove;
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);
onlyVerticalMove = a.getBoolean(R.styleable.AvatarImageBehavior_onlyVerticalMove, false);
a.recycle();
}
}
#Override
public boolean layoutDependsOn(#NotNull final CoordinatorLayout parent, #NotNull final ImageView child, #NotNull final View dependency) {
return dependency instanceof AppBarLayout; // change if you want another sibling to depend on
}
#Override
public boolean onDependentViewChanged(#NotNull final CoordinatorLayout parent, #NotNull final ImageView child, #NotNull 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 = Math.max(currentToolbarHeight, finalToolbarHeight);
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
if (!onlyVerticalMove) {
child.setX(newXPosition);
}
child.setY(startYPositionImage - distanceYToSubtract);
return true;
}
private void initProperties(
final ImageView 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;
}
}
}
```java
public class AppBarScrollWatcher implements AppBarLayout.OnOffsetChangedListener {
private int scrollRange = -1;
private OffsetListener listener;
public AppBarScrollWatcher(OffsetListener listener) {
this.listener = listener;
}
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (scrollRange == -1) {
scrollRange = appBarLayout.getTotalScrollRange();
}
int appbarHeight = scrollRange + verticalOffset;
float alpha = (float) appbarHeight / scrollRange;
if (alpha < 0) {
alpha = 0;
}
float alphaZeroOnCollapsed = shrinkAlpha(alpha);
float alphaZeroOnExpanded = Math.abs(alphaZeroOnCollapsed - 1);
int argbZeroOnExpanded = (int) Math.abs((alphaZeroOnCollapsed * 255) - 255);
int argbZeroOnCollapsed = (int) Math.abs(alphaZeroOnCollapsed * 255);
listener.onAppBarExpanding(alphaZeroOnExpanded <= 0, alphaZeroOnCollapsed <= 0, argbZeroOnExpanded, argbZeroOnCollapsed, alphaZeroOnCollapsed, alphaZeroOnExpanded);
}
private float shrinkAlpha(float alpha) {
NumberFormat formatter = NumberFormat.getInstance(Locale.getDefault());
formatter.setMaximumFractionDigits(2);
formatter.setMinimumFractionDigits(2);
formatter.setRoundingMode(RoundingMode.HALF_DOWN);
return Float.parseFloat(formatter.format(alpha));
}
public interface OffsetListener {
void onAppBarExpanding(boolean expanded, boolean collapsed, int argbZeroOnExpanded, int argbZeroOnCollapsed, float alphaZeroOnCollapsed, float alphaZeroOnExpanded);
}
}
res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AvatarImageBehavior">
<attr name="finalXPosition" format="dimension" />
<attr name="finalYPosition" format="dimension" />
<attr name="finalHeight" format="dimension" />
<attr name="finalToolbarHeight" format="dimension" />
<attr name="onlyVerticalMove" format="boolean" />
</declare-styleable>
</resources>
2. Implementation in Activity/Fragment
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentScrim="#color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:titleEnabled="false">
<LinearLayout
android:id="#+id/header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/holo_orange_light"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingStart="24dp"
android:paddingTop="160dp"
android:paddingEnd="24dp"
android:paddingBottom="56dp">
</LinearLayout>
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:layout_gravity="bottom"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:titleMarginStart="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<ImageView
android:id="#+id/still_photo"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:contentDescription="#string/app_name"
android:scaleType="fitCenter"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_ph_person_male_80dp" />
<ImageView
android:id="#+id/ic_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.7"
android:clickable="true"
android:contentDescription="#string/app_name"
android:focusable="true"
android:padding="8dp"
android:tint="#android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_more_vert_black_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="#+id/v_sections"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:behavior_overlapTop="24dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<View
android:layout_width="match_parent"
android:layout_height="1000dp" />
</androidx.cardview.widget.CardView>
</androidx.core.widget.NestedScrollView>
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/moving_photo"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="top|center_horizontal"
android:layout_marginTop="64dp"
android:contentDescription="#string/app_name"
android:scaleType="fitCenter"
app:finalHeight="48dp"
app:finalToolbarHeight="?android:attr/actionBarSize"
app:finalYPosition="4dp"
app:layout_behavior=".custom.AvatarImageBehavior"
app:onlyVerticalMove="true"
app:srcCompat="#drawable/ic_ph_person_male_80dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
private lateinit var appBarScrollListener: AppBarScrollWatcher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_launcher)
setupAppBar()
}
private fun setupAppBar() {
appBarScrollListener =
AppBarScrollWatcher(AppBarScrollWatcher.OffsetListener { _, collapsed, _, _, _, _ ->
still_photo.visibility = if (collapsed) View.VISIBLE else View.INVISIBLE
})
app_bar.addOnOffsetChangedListener(appBarScrollListener)
}
override fun onDestroy() {
app_bar.removeOnOffsetChangedListener(appBarScrollListener)
super.onDestroy()
}
Note that you should put two ImageView in the layout.
AppCompatImageView directly inside the CoordinatorLayout so that we can
use CoordinatorLayout.Behavior on it, it would be the moving photo.
The important prop here is app:onlyVerticalMove="true", that make
your moving photo scrolled vertically. I made the default value to
false, it will move the photo to the start point of CoordinatorLayout
(top left).
Put another ImageView inside the Appbar layout as the final photo displayed
in the Appbar. Init this with invisible state, then use AppBarLayout behavior to show the photo when the collapsing toolbar is being collapsed.
If you want to exclude Toolbar from moving elements, just remove android:layout_gravity="bottom"

Collapsing/resizing image on scroll like Whatsapp profile

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

Issues recreating a material design lists with leave behind in Android

I'd like to recreate the Material Design list: controls in Android inside a sliding panel.
I'm making use of:
com.android.support:appcompat-v7
com.android.support:support-v4
com.android.support:recyclerview-v7
com.android.support:design
https://github.com/umano/AndroidSlidingUpPanel
https://github.com/serso/android-linear-layout-manager
https://github.com/daimajia/AndroidSwipeLayout
https://github.com/tmiyamon/gradle-mdicons
I ended up using portions of the support libraries, but this specific app is 5.0+ only so there may be some Lollipop-only stuff in my code.
Here is the layout for a list item in my RecyclerView:
<com.daimajia.swipe.SwipeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="right">
<RelativeLayout
android:layout_width="42dp"
android:layout_height="match_parent"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:src="#drawable/ic_delete_black_24dp"/>
</RelativeLayout>
<RelativeLayout
android:id="#+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/ripple_floating"
android:clickable="true"
android:focusable="true"
android:minHeight="48dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:elevation="2dp">
<TextView
android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:ellipsize="end"
android:singleLine="true"
android:text="..."/>
</RelativeLayout>
</com.daimajia.swipe.SwipeLayout>
And this is the current result.
The remaining problems to solve are elevation shadows and dividers.
As you can see in the image there are somewhat reasonable shadows on the sides of the list items. However there are no elevation shadows on the bottom of the items, so when an item is revealed no shadow shows above the revealed area.
The second issue is dividers. I have a single-item list with no icons/images so the proper design is to use dividers for the items.
However I can't use the DividerItemDecoration from serso/android-linear-layout-manager because it is not integrated into the slider and this happens when 2 adjacent items are slid.
Does anyone know of any drawable, attribute, or library I should be using to style these list items as material sheets with elevation shadows and borders?
Shadows/Elevation
For the shadows/elevation to look like that you can use card view with the common trick to make them slightly wider than the screen width ("full width cards").
For example:
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="72dp"
android:layout_marginLeft="#dimen/card_margin_horizontal"
android:layout_marginRight="#dimen/card_margin_horizontal"
app:cardCornerRadius="0dp"
app:cardElevation="4dp">
In values/dimens.xml:
<dimen name="card_margin_horizontal">-3dp</dimen>
In values-v21/dimens.xml
<dimen name="card_margin_horizontal">0dp</dimen>
Divider
And with that you might not need to change the divider, it might look ok.
Otherwise try adding the divider to the view itself (top view or control it's visibility yourself). It can be just a View with height 1dp and width match_parent and backgroundColor set to some dark grey (or the system divider drawable (R.attr.listDivider).
For the divider part of your question I would recommend looking into ItemDecorators. You can add an ItemDecorator to your LayoutManager and get dividers. An example of one is here (and there are several out there if you google for it)
Create a class named DividerItemDecoration.java and paste the below code
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
and use addItemDecoration().
you can find full tutorial on this page :
http://www.androidhive.info/2016/01/android-working-with-recycler-view/
Add android:clipChildren = "false" to SwipeLayout and RecyclerView.
Here is the layout for a list item in my RecyclerView:
<com.daimajia.swipe.SwipeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginBottom="1px"
android:clipChildren="false"
app:show_mode="lay_down">
<ImageView
android:layout_width="60dp"
android:layout_height="match_parent"
android:scaleType="center"
android:src="#drawable/ic_settings_black_24dp"/>
<FrameLayout
android:id="#+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
android:elevation="2dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="#string/app_name"/>
</FrameLayout>
</com.daimajia.swipe.SwipeLayout>
Here is the layout my RecyclerView:
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorBackground"
android:clipChildren="false"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"/>
And this is the current result.

Custom view messing up layout

I have been facing a problem with a custom view I coded. Basically, I have a custom view which handles different part of a seat, with different states. I managed to code it, and it was working perfectly.
Since the app was getting a bit slow, I decided to crop the images: instead of taking the whole screen with transparency, it only takes a part of the screen (I didn’t do the graphics).
But since I have cropped the images, they are messing up my layout. And I have no idea why, because when I replace my custom view with an ImageView, it’s fitting perfectly!
It looks like my view is ignoring “wrap_content”, and takes more than what it needs. Setting the value to “match_parent” didn’t work either.
I tried cropping my images again, I thought it was because of the size, but it is not. I tried overriding the onMeasure method, no change. And I don’t want to hardcode the size, because I feel like it’s a bad thing to do.
Why is this happening, and how do I fix it?
Here are some of my code snippets.
fragment_lumbar.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- Main screen -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/background_logo">
<!-- Left menu with control buttons -->
<RelativeLayout
android:id="#+id/lumbar_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="61dp">
<ImageButton
android:id="#+id/button_arrowup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:background="#drawable/arrowup_button"
android:contentDescription="#string/moveup_lumbar" />
<!-- Plus/minus buttons -->
<ImageButton
android:id="#+id/button_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/button_arrowup"
android:layout_centerVertical="true"
android:background="#drawable/plus_button"
android:contentDescription="#string/inflate_lumbar" />
<ImageButton
android:id="#+id/button_minus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/button_arrowup"
android:layout_toRightOf="#id/button_plus"
android:layout_marginLeft="90dp"
android:background="#drawable/minus_button"
android:contentDescription="#string/deflate_back" />
<!-- Arrown down button -->
<ImageButton
android:id="#+id/button_arrowdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/button_plus"
android:layout_centerHorizontal="true"
android:background="#drawable/arrowdown_button"
android:contentDescription="#string/movedown_lumbar" />
<!-- Home button -->
</RelativeLayout>
<!-- Right panel -->
<!-- Screen -->
<FrameLayout
android:id="#+id/center_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="7dp"
android:layout_toRightOf="#id/lumbar_control"
android:background="#drawable/screen" >
<TextView
android:id="#+id/text_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_margin="10dp"
android:textSize="32sp"
android:gravity="center"
android:textColor="#color/white"
android:text="hello"/>
<!-- Seat screen -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/seat"
android:src="#drawable/lumbar_control_seat"/>
<com.SeatStateView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/lumbar_seat_state"
android:id="#+id/lumbar_seat_state" />
</FrameLayout>
<!-- Home button -->
<Button
android:id="#+id/button_home"
android:layout_width="332dp"
android:layout_height="44dp"
android:layout_centerHorizontal="true"
android:background="#android:color/transparent"
android:layout_alignParentBottom="true"
android:contentDescription="#string/home_button"/>
</RelativeLayout>
lumbar_seat_state:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="#drawable/lumbar_seat_state_1" />
<item android:drawable="#drawable/lumbar_seat_state_2" />
<item android:drawable="#drawable/lumbar_seat_state_3" />
</layer-list>
lumbar_seat_state_1:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com">
<item android:drawable="#drawable/lumbar_inflate_1"
app:state_inflating="true" />
<item android:drawable="#drawable/lumbar_deflate_1"
app:state_deflating="true" />
<item android:drawable="#drawable/lumbar_idle_1" />
</selector>
SeatStateView.java:
public class SeatStateView extends View {
/* Custom states */
private static final int[] INFLATING_STATE_SET = {
R.attr.state_inflating
};
private static final int[] DEFLATING_STATE_SET = {
R.attr.state_deflating
};
private static final int[] HEATING_STATE_SET = {
R.attr.state_heating
};
private static final int[] COOLING_STATE_SET = {
R.attr.state_cooling
};
/* Inflate/Deflate state */
private boolean isInflating = false;
private boolean isDeflating = false;
/* Heating state */
private boolean isHeating = false;
private boolean isCooling = false;
public SeatStateView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Overrides the onCreateDrawable to add our custom states
#Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
// Checking the states
if(isInflating) {
mergeDrawableStates(drawableState, INFLATING_STATE_SET);
}
if(isDeflating) {
mergeDrawableStates(drawableState, DEFLATING_STATE_SET);
}
if(isHeating) {
mergeDrawableStates(drawableState, HEATING_STATE_SET);
}
if(isCooling) {
mergeDrawableStates(drawableState, COOLING_STATE_SET);
}
return drawableState;
}
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth, parentHeight);
}
/**
* Clears all the states and set to idle.
*/
public void setIdle() {
isInflating = isDeflating = false;
// Refresh view
refreshDrawableState();
}
public void setInflating(boolean state) {
if(isInflating != state) {
// Update all the states
isInflating = state;
if(isInflating) {
isDeflating = false;
}
// Refresh view
refreshDrawableState();
}
}
public void setDeflating(boolean state) {
if(isDeflating != state) {
// Update all the states
isDeflating = state;
if(isDeflating) {
isInflating = false;
}
// Refresh view
refreshDrawableState();
}
}
public void setHeating(boolean state) {
if(isHeating != state) {
isHeating = state;
if(state) {
isCooling = false;
}
// Refresh view
refreshDrawableState();
}
}
public void setCooling(boolean state) {
if(isCooling != state) {
isCooling = state;
if(state) {
isHeating = false;
}
// Refresh view
refreshDrawableState();
}
}
}
Well, just searched more. Found out that I didn’t override my onMeasure method correctly. Here is my new implementation if someone stumbles upon this question:
SeatStateView.java:
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
// Background image’s size
int desiredWidth = getBackground().getIntrinsicWidth();
int desiredHeight = getBackground().getIntrinsicHeight();
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Final size
int width, height;
// Set width depending on mode
if(widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
}
else if(widthMode == MeasureSpec.AT_MOST) {
width = Math.min(desiredWidth, widthSize);
}
else {
width = desiredWidth;
}
// Set height depending on mode
if(heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
}
else if(widthMode == MeasureSpec.AT_MOST) {
height = Math.min(desiredHeight, heightSize);
}
else {
height = desiredHeight;
}
// Finally, set dimension
setMeasuredDimension(width, height);
}
For details, check this page: onMeasure custom view explanation.
Sorry for bothering you.

Calculate view measure before display it

I am trying to add views to LinearLayout dynamically as much as it possible (depending on screen width).
I do this before the LinearLayout displays on screen.
My LinearLayout:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:background="#666"/>
My view to display in LinearLayout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:background="#999">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="#drawable/no_photo"/>
</FrameLayout>
I add views to layout:
int allItems = 50;
int currentItem = 0;
while(currentItem < allItems)
{
FrameLayout view = (FrameLayout) inflater.inflate(R.layout.fl, null);
linearLayout.addView(view);
if (linearLayout.getMeasuredWidth() >= this.getWidth())
{
linearLayout.removeView(view);
break;
}
}
but linearLayout.getMeasuredWidth() and this.getWidth() is 0;
I know, that i must use View.measure method to calculate view size before it became visible, but i don't know where and how it use.
Edit your code as below :
Display display = getWindowManager().getDefaultDisplay();
int maxWidth = display.getWidth();
int widthSoFar=0;
int allItems = 50;
int currentItem = 0;
while(currentItem < allItems) {
FrameLayout view = (FrameLayout) inflater.inflate(R.layout.fl, null);
linearLayout.addView(view);
view .measure(0, 0);
widthSoFar = widthSoFar + view.getMeasuredWidth();
if (widthSoFar >= maxWidth) {
linearLayout.removeView(view);
break;
}
}
Hope this helps you

Categories

Resources