I checked this stackoverflow question cause it's very similar, but the Google's bug have been fixed in current versions, but I still having the problem.
I have an RecyclerView inside a NestedScrollView, after NestedScrollView scrolled if I click on item inside RecyclerView, onClick method does not work propertly.
Can anyone help me? Thanks
Okey, I found the solution here, we need:
public class FixAppBarLayoutBehavior extends AppBarLayout.Behavior {
public FixAppBarLayoutBehavior() {
super();
}
public FixAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, type);
stopNestedScrollIfNeeded(dyUnconsumed, child, target, type);
}
#Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View target, int dx, int dy, int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
stopNestedScrollIfNeeded(dy, child, target, type);
}
private void stopNestedScrollIfNeeded(int dy, AppBarLayout child, View target, int type) {
if (type == ViewCompat.TYPE_NON_TOUCH) {
final int currOffset = getTopAndBottomOffset();
if ((dy < 0 && currOffset == 0)
|| (dy > 0 && currOffset == -child.getTotalScrollRange())) {
ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH);
}
}
}
}
and, in our AppBarLayout:
<android.support.design.widget.AppBarLayout>
...
app:layout_behavior="your.package.FixAppBarLayoutBehavior"
...
</android.support.design.widget.AppBarLayout>
Your RecyclerView shouldn't permit nested scrolling, so it must have nestedScrollingEnabled="false"
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"/>
Related
I'm new to android development. currently I'm creating a widget so I need to create an FrameLayout with scroll effect. When look at the android docs come across the abstract/interface class NestedScrollParent3. Is this the correct way to do this?
package com.oasis.motion;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.view.NestedScrollingParent3;
public class CustomDrawerView extends ConstraintLayout implements NestedScrollingParent3 {
private NestedScrollingParent3 motionLayout;
public CustomDrawerView(Context context) {
super(context);
motionLayout = (NestedScrollingParent3) getParent();
}
public CustomDrawerView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
motionLayout = (NestedScrollingParent3) getParent();
}
public CustomDrawerView(Context context, AttributeSet attributeSet, int defStyleAttr) {
super(context, attributeSet, defStyleAttr);
motionLayout = (NestedScrollingParent3) getParent();
}
#Override
public boolean onStartNestedScroll(View child, View target, int axes, int type) {
return motionLayout.onStartNestedScroll(child, target, axes, type);
}
#Override
public void onNestedScrollAccepted(View child, View target, int axes, int type) {
motionLayout.onNestedScrollAccepted(child, target, axes, type);
}
#Override
public void onStopNestedScroll(View target, int type) {
motionLayout.onStopNestedScroll(target, type);
}
#Override
public void onNestedScroll(View target, int dx, int dy, int consumed, int unconsumed, int type) {
motionLayout.onNestedScroll(target, dx, dy, consumed, unconsumed, type);
}
#Override
public void onNestedPreScroll(View target, int dx, int dy, int[] windowInset, int type) {
motionLayout.onNestedPreScroll(target, dx, dy, windowInset, type);
}
#Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
int type, int[] windowInset) {
motionLayout.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, windowInset);
}
}
Is this the correct way to make a FrameLayout scrollable? Especially horizontal scrollable?
I'm trying to make scrolling Bottom navigation bar view, which will scroll down when scrolling down, scroll up, when scrolling up, and scroll up with last item of recycler view when scrolling to the end. But I don't know how to implement the last thing. This is my BottomNavigationBehavior class:
public class NavigationBehavior extends CoordinatorLayout.Behavior {
private float height = 0;
public final void BottomNavigationBehavior() {
}
public NavigationBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onLayoutChild(#NonNull #NotNull CoordinatorLayout parent, #NonNull #NotNull View child, int layoutDirection) {
ViewGroup.MarginLayoutParams paramsCompat =
(ViewGroup.MarginLayoutParams) child.getLayoutParams();
height = child.getMeasuredHeight() + paramsCompat.bottomMargin;
return super.onLayoutChild(parent, child, layoutDirection);
}
#Override
public boolean onStartNestedScroll(#NonNull #NotNull CoordinatorLayout coordinatorLayout, #NonNull #NotNull View child, #NonNull #NotNull View directTargetChild, #NonNull #NotNull View target, int axes, int type) {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedPreScroll(#NonNull #NotNull CoordinatorLayout coordinatorLayout, #NonNull #NotNull View child, #NonNull #NotNull View target, int dx, int dy, #NonNull #NotNull int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
child.setTranslationY(Float.max(0.0F, Float.min(height, child.getTranslationY() + (float)dy)));
}
public void setEndOfList(boolean endOfList) {
this.endOfList = endOfList;
}
}
Can someone help me ?
My MainActivity contains my main content as a ViewPager2 as well as my BottomNavigationView, which has the hide_bottom_view_on_scroll_behavior. Each ViewPager2 child uses the appbar_scrolling_view_behavior and in most cases, this causes the bottom nav to hide when a child fragment is scrolled, which is the desired behavior.
However, on my child fragment that contains a CollapsingToolbarLayout with app:layout_scrollFlags="scroll", this behavior doesn't work. Removing the scrollFlags=scroll causes the bottom nav to act how it should. This leads me to believe that for some reason, the CollapsingToolbarLayout is intercepting the scroll behavior and it isn't propagating up to the bottom nav.
Any thoughts?
activity_main.xml
<androidx.coordinatorlayout.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:layout_height="match_parent"
android:layout_width="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/view_pager"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:nestedScrollingEnabled="true"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/main_bottom_navigation_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp"
app:itemTextColor="?android:textColorPrimary"
android:layout_marginBottom="36dp"
app:itemIconTint="#color/nav_icon_tint"
android:background="#drawable/rounded_background"
app:backgroundTint="?android:colorPrimaryDark"
app:labelVisibilityMode="unlabeled"
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"
app:menu="#menu/activity_main_navigation"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
fragment_devotional.xml (child of ViewPager2)
<androidx.coordinatorlayout.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:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/background"
android:theme="#style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="260dp"
android:fitsSystemWindows="true"
app:contentScrim="#color/background"
app:expandedTitleGravity="bottom"
app:expandedTitleTextAppearance="#style/Toolbar.ExpandedText"
app:layout_scrollFlags="scroll"
app:title="#{devotional.title}"
app:titleTextAppearance="#style/Toolbar.TitleText"
app:titleTextColor="?android:textColorPrimary"
app:toolbarId="#+id/toolbar">
<ImageView
android:id="#+id/header_logo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:contentDescription="#{devotional.time}"
android:scaleType="center"
app:layout_collapseMode="parallax" />
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:textAlignment="textStart"
app:contentScrim="#color/background"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll"
app:popupTheme="#style/AppTheme.PopupOverlay"
app:title="#{devotional.title}" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="#+id/scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/background"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
... content ....
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
The problem is that you have a Coordinator Layout in another Coordinator Layout
the first CoordinatorLayout with the bottom Appbar doesnt get the scrolldata because the first one already consumes it.
what you need to do is to create a new type of Coordinator Layout that "communicates with the outer Coordinatorlayout (ie a nested Coordinator Layout).
public class NestedCoordinatorLayout extends CoordinatorLayout implements NestedScrollingChild {
private NestedScrollingChildHelper mChildHelper;
public NestedCoordinatorLayout(Context context) {
super(context);
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
public NestedCoordinatorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
public NestedCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
#Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) {
int[][] tConsumed = new int[2][2];
super.onNestedPreScroll(target, dx, dy, consumed, type);
dispatchNestedPreScroll(dx, dy, tConsumed[1], null);
consumed[0] = tConsumed[0][0] + tConsumed[1][0];
consumed[1] = tConsumed[0][1] + tConsumed[1][1];
}
#Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null);
}
#Override
public void onStopNestedScroll(View target, int type) {
/* Disable the scrolling behavior of our own children */
super.onStopNestedScroll(target, type);
/* Disable the scrolling behavior of the parent's other children */
stopNestedScroll();
}
#Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes, int type) {
/* Enable the scrolling behavior of our own children */
boolean tHandled = super.onStartNestedScroll(child, target, nestedScrollAxes, type);
/* Enable the scrolling behavior of the parent's other children */
return startNestedScroll(nestedScrollAxes) || tHandled;
}
#Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
/* Enable the scrolling behavior of our own children */
boolean tHandled = super.onStartNestedScroll(child, target, nestedScrollAxes);
/* Enable the scrolling behavior of the parent's other children */
return startNestedScroll(nestedScrollAxes) || tHandled;
}
#Override
public void onStopNestedScroll(View target) {
/* Disable the scrolling behavior of our own children */
super.onStopNestedScroll(target);
/* Disable the scrolling behavior of the parent's other children */
stopNestedScroll();
}
#Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
int[][] tConsumed = new int[2][2];
super.onNestedPreScroll(target, dx, dy, tConsumed[0]);
dispatchNestedPreScroll(dx, dy, tConsumed[1], null);
consumed[0] = tConsumed[0][0] + tConsumed[1][0];
consumed[1] = tConsumed[0][1] + tConsumed[1][1];
}
#Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null);
}
#Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
boolean tHandled = super.onNestedPreFling(target, velocityX, velocityY);
return dispatchNestedPreFling(velocityX, velocityY) || tHandled;
}
#Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
boolean tHandled = super.onNestedFling(target, velocityX, velocityY, consumed);
return dispatchNestedFling(velocityX, velocityY, consumed) || tHandled;
}
#Override
public boolean isNestedScrollingEnabled() {
return mChildHelper.isNestedScrollingEnabled();
}
#Override
public void setNestedScrollingEnabled(boolean enabled) {
mChildHelper.setNestedScrollingEnabled(enabled);
}
#Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
#Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
#Override
public boolean hasNestedScrollingParent() {
return mChildHelper.hasNestedScrollingParent();
}
#Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed, offsetInWindow);
}
#Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
#Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
#Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}}
use this for the inner coordinator layout and everything should work hopefully :)
I have a class
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
super();
}
public ScrollAwareFABBehavior() {
super();
}
#Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,final View directTargetChild, final View target, final int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,final View target, final int dxConsumed, final int dyConsumed,final int dxUnconsumed, final int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide();
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
}
the problem is that onNestedScroll is called only once when I scroll up a recyclerview, so the fab is hiding and never shows again. Here is layout I am using
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/refresh_layout"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
app:layoutManager="#string/vertical_linear_layout_manager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
<android.support.design.widget.FloatingActionButton
android:id="#+id/add"
android:layout_width="wrap_content"
app:layout_behavior="mypackagename.util.ScrollAwareFABBehavior"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
app:useCompatPadding="true"
android:layout_margin="16sp"
android:src="#drawable/ic_add"/>
</android.support.design.widget.CoordinatorLayout>
My support libraries version is 25.1.0
I solved the problem changing the visibility from GONE to INVISIBLE with the following code.
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child,
View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
#Override
public void onShown(FloatingActionButton fab) {
super.onShown(fab);
}
#Override
public void onHidden(FloatingActionButton fab) {
super.onHidden(fab);
fab.setVisibility(View.INVISIBLE);
}
});
} else if (dyConsumed <= 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
I have just answered
to absolutely the same problem in another post, check it.
Speaking shortly, use the following:
compile 'com.android.support:design:25.0.1'
#Override
public void onNestedScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull FloatingActionButton child, #NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
#Override
public void onHidden(FloatingActionButton fab) {
super.onHidden(fab);
fab.setVisibility(View.INVISIBLE);
}
});
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
Do not set the visibility of FAB to GONE in child.hide() - use INVISIBLE instead. From 25.1.0, the scroll events are not passed on to views whose visibility is GONE
For me i replace child.hide() with ((View)child).setVisibility(View.INVISIBLE).
I have the FAB working when recyclerview has enough items to scroll, but i need to handle the case when recyclerview does not scroll (the total of items do not cover the screen).
At the moment this is how I handle the scroll:
public class FABBehavior extends FloatingActionButton.Behavior {
public FABBehavior() {
super();
}
public FABBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, final View target, final int dxConsumed, final int dyConsumed, final int dxUnconsumed, final int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0) {
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
int fab_bottomMargin = layoutParams.bottomMargin;
child.animate().translationY(child.getHeight() + fab_bottomMargin).setInterpolator(new LinearInterpolator()).start();
} else if (dyConsumed < 0) {
child.animate().translationY(0).setInterpolator(new LinearInterpolator()).start();
}
}
#Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, final View directTargetChild, final View target, final int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
}
How to handle when recyclerview has few items?
You have to handle another case independently from CoordinatorLayout.
override a function layoutDependsOn:
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return super.layoutDependsOn(parent, child, dependency) || dependency instanceof RecyclerView;
}
onNestedScroll should also handle another case:
if (target instanceof RecyclerView) {
handleRecyclerViewScrolling(target, child);
return;
}
handleRecyclerViewScrolling should look like:
private void handleRecyclerViewScrolling(View target, FloatingActionButton child) {
if (scrollListener != null) {
return;
}
RecyclerView recyclerView = (RecyclerView) target;
scrollListener = new RecyclerViewScrollListener(child);
recyclerView.addOnScrollListener(scrollListener);
}
scrollListener should be a field in your FABBehavior class. Also declare inner class inside FABBehavior:
private class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
FloatingActionButton mChild;
public RecyclerViewScrollListener(FloatingActionButton child) {
this.mChild = child;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
mChild.show();
} else {
mChild.hide();
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(Integer.MAX_VALUE)) {
mChild.show();
}
}
}
RecyclerViewScrollListener hides FAB, when it is scrolling and shows it when it is in idle state.