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?
Related
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 ?
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"/>
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.
Does anyone know how to implement the Collapsing Toolbar using A listview instead of a recycler view?
To make it woks you should to:
Implement NestedScrollingChild in your custom ListView implementation.
Add field private final NestedScrollingChildHelper mScrollingChildHelper; and init it in constructors
Delegate to it methods from NestedScrollingChild
Invoke setNestedScrollingEnabled(true); after mScrollingChildHelper initialization
Here is my list view implementation for example:
public class NestedScrollingListView extends ListView implements NestedScrollingChild {
private final NestedScrollingChildHelper mScrollingChildHelper;
public NestedScrollingListView(Context context) {
super(context);
mScrollingChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
public NestedScrollingListView(Context context, AttributeSet attrs) {
super(context, attrs);
mScrollingChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
#Override
public void setNestedScrollingEnabled(boolean enabled) {
mScrollingChildHelper.setNestedScrollingEnabled(enabled);
}
#Override
public boolean isNestedScrollingEnabled() {
return mScrollingChildHelper.isNestedScrollingEnabled();
}
#Override
public boolean startNestedScroll(int axes) {
return mScrollingChildHelper.startNestedScroll(axes);
}
#Override
public void stopNestedScroll() {
mScrollingChildHelper.stopNestedScroll();
}
#Override
public boolean hasNestedScrollingParent() {
return mScrollingChildHelper.hasNestedScrollingParent();
}
#Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
#Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
#Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
#Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
}
Only add this code to your project. It only work in Lollipop devices and later.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
listView.setNestedScrollingEnabled(true);
listView.startNestedScroll(View.OVER_SCROLL_ALWAYS);
}
If you want to make nested scrolling work on pre-Lollipop devices, which you probably do, you have to use corresponding utility classes from the Support library. First you have to replace you ScrollView with NestedScrollView. The latter implements both NestedScrollingParent and NestedScrollingChild so it can be used as a parent or a child scroll container.
But ListView doesn't support nested scrolling, therefore you need to subclass it and implement NestedScrollingChild. Fortunately, the Support library provides NestedScrollingChildHelper class, so you just have to create an instance of this class and call its methods from the corresponding methods of your view class.
I have two activities using AppBarLayout with a Toolbar and TabLayout from support library 22.
The layout of both is pretty similar: A Toolbar at the top, below it TabLayout, below it a ViewPager containing 3 Fragments.
The first activity's Fragment has a RecyclerView,
the second activity's Fragment is using a ListView instead.
The scrollable Toolbar example from https://github.com/chrisbanes/cheesesquare is working fine on the first activity using the RecyclerView, but on with the ListView.
I've tried created a custom ListViewScrollBehavior that extends AppBarLayout.ScrollingViewBehavior, but so far no luck.
The TouchEvents are passed to the custom class only for horizontal scrolling, but not when scrolling the ListView (vertically).
Any way to use a CoordinatorLayout with ListView?
The only solution to make it work now is to use this:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
listView.setNestedScrollingEnabled(true);
}
It will obviously only work on Lollipop.
Alternative solution to Nicolas POMEPUY's answer is to use ViewCompat.setNestedScrollingEnabled(View, boolean)
ViewCompat.setNestedScrollingEnabled(listView, true);
Of course nested scrolling behavior will only work from Lollipop.
I believe that the CoordinatorLayout works only with RecyclerView and NestedScrollView. Try wrapping your ListView in a NestedScrollView or convert it to a RecyclerView with a LinearLayoutManager
To a view able to react on AppBarLayout, it need to implement NestedScrollingChild. ListView is not. But it could be implement by a delegate class easily. Use it, it will do like what RecyclerView did
public class NestedScrollingListView extends ListView implements NestedScrollingChild {
private NestedScrollingChildHelper mNestedScrollingChildHelper;
public NestedScrollingListView(final Context context) {
super(context);
initHelper();
}
public NestedScrollingListView(final Context context, final AttributeSet attrs) {
super(context, attrs);
initHelper();
}
public NestedScrollingListView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHelper();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public NestedScrollingListView(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initHelper();
}
private void initHelper() {
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
#Override
public void setNestedScrollingEnabled(final boolean enabled) {
mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
}
#Override
public boolean isNestedScrollingEnabled() {
return mNestedScrollingChildHelper.isNestedScrollingEnabled();
}
#Override
public boolean startNestedScroll(final int axes) {
return mNestedScrollingChildHelper.startNestedScroll(axes);
}
#Override
public void stopNestedScroll() {
mNestedScrollingChildHelper.stopNestedScroll();
}
#Override
public boolean hasNestedScrollingParent() {
return mNestedScrollingChildHelper.hasNestedScrollingParent();
}
#Override
public boolean dispatchNestedScroll(final int dxConsumed, final int dyConsumed, final int dxUnconsumed, final int dyUnconsumed, final int[] offsetInWindow) {
return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
#Override
public boolean dispatchNestedPreScroll(final int dx, final int dy, final int[] consumed, final int[] offsetInWindow) {
return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
#Override
public boolean dispatchNestedFling(final float velocityX, final float velocityY, final boolean consumed) {
return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
#Override
public boolean dispatchNestedPreFling(final float velocityX, final float velocityY) {
return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
}
ListView ScrollingViewBehavior supports only >= 21.
Otherwise you should to write code as below way:
private int mPreviousVisibleItem;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
listView.setNestedScrollingEnabled(true);
} else {
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem > mPreviousVisibleItem) {
appBarLayout.setExpanded(false, true);
} else if (firstVisibleItem < mPreviousVisibleItem) {
appBarLayout.setExpanded(true, true);
}
mPreviousVisibleItem = firstVisibleItem;
}
});
}
You can add
android:nestedScrollingEnabled="true"
to the ListView from XML, just note that this only supports API 21+. Alternatively, you can swap out your ListView for a RecyclerView, and that should work better.