Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement NestedScrolling on Android?

With support-v4 library 22.1.0 android supports nested scrolling (pre android 5.0). Unfortunately, this feature is not really documented. There are two interfaces (NestedScrollingParent and NestedScrollingChild) as well as two helper delegate classes (NestedScrollingChildHelper and NestedScrollingParentHelper).

Has anyone worked with NestedScrolling on Android?

I tried to setup a little example, where I use NestedScrollView which implements both NestedScrollingParent and NestedScrollingChild.

My layout looks like this:

<android.support.v4.widget.NestedScrollView     xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     android:id="@+id/parent"     android:layout_width="match_parent"     android:layout_height="match_parent"     tools:context=".MainActivity">    <LinearLayout       android:layout_width="match_parent"       android:layout_height="wrap_content"       android:orientation="vertical">      <View         android:id="@+id/header"         android:layout_width="match_parent" android:layout_height="100dp"         android:background="#AF1233"/>      <android.support.v4.widget.NestedScrollView         android:id="@+id/child"         android:layout_width="match_parent"         android:layout_height="wrap_content"         >        <FrameLayout           android:layout_width="match_parent"           android:layout_height="wrap_content"           android:orientation="vertical">          <TextView             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:background="#12AF33"             android:text="@string/long_text"/>        </FrameLayout>     </android.support.v4.widget.NestedScrollView>    </LinearLayout>  </android.support.v4.widget.NestedScrollView> 

I want to display a header view and another NestedScrollView (id = child) in a NestedScrollView (id = parent).

The idea was, to adjust the height of the child scroll view at runtime by using a OnPredrawListener:

public class MainActivity extends Activity {    @Override protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.activity_main);      final NestedScrollView parentScroll = (NestedScrollView) findViewById(R.id.parent);     final NestedScrollView nestedScroll = (NestedScrollView) findViewById(R.id.child);     parentScroll.setNestedScrollingEnabled(false);     final View header = findViewById(R.id.header);      parentScroll.getViewTreeObserver()         .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {           @Override public boolean onPreDraw() {             if (parentScroll.getHeight() > 0) {               parentScroll.getViewTreeObserver().removeOnPreDrawListener(this);               nestedScroll.getLayoutParams().height = parentScroll.getHeight() - 40;               nestedScroll.setLayoutParams(nestedScroll.getLayoutParams());               nestedScroll.invalidate();               return false;             }             return true;           }         });    } } 

So the header view will be scrolled away partially, 40 pixels will remain visible since I set the height of the nested child scroll view to parentScroll.getHeight() - 40. Alright, setting the height at runtime and scrolling the parent scroll view works like expected (header scrolls out, 40 pixels remain visible and then the child scrollview fills the rest of the screen below the header).

I would expect that "NestedScrolling" means that I can make a scroll gesture anywhere on the screen (touch event caught by parent scroll view) and if the parent scroll view has reached the end the nested child scroll view beginns to scroll. However that seems not to be the case (neither for simple scroll gestures nor for fling gestures).

The touch event is always handled by nested child scrollview if the touch event begins in its boundaries, otherwise by the parent scrollview.

Is that the expected behaviour of "nested scrolling" or is there an option to change that behaviour?

I also tried to replace the nested child scroll view with a NestedRecyclerView. I subclassed RecyclerView and implemented NestedScrollingChild where I delegate all methods to NestedScrollingChildHelper:

public class NestedRecyclerView extends RecyclerView implements NestedScrollingChild {    private final NestedScrollingChildHelper scrollingChildHelper =       new NestedScrollingChildHelper(this);     public void setNestedScrollingEnabled(boolean enabled) {     scrollingChildHelper.setNestedScrollingEnabled(enabled);   }    public boolean isNestedScrollingEnabled() {     return scrollingChildHelper.isNestedScrollingEnabled();   }    public boolean startNestedScroll(int axes) {     return scrollingChildHelper.startNestedScroll(axes);   }    public void stopNestedScroll() {     scrollingChildHelper.stopNestedScroll();   }    public boolean hasNestedScrollingParent() {     return scrollingChildHelper.hasNestedScrollingParent();   }    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,       int dyUnconsumed, int[] offsetInWindow) {      return scrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed,         dyUnconsumed, offsetInWindow);   }    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {     return scrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);   }    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {     return scrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);   }    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {     return scrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);   } } 

but the NestedRecyclerView doesn't scroll at all. All touch events are caught by the parent scroll view.

like image 849
sockeqwe Avatar asked Apr 26 '15 23:04

sockeqwe


People also ask

What is nested scrolling in Android?

NestedScrollView is just like ScrollView, but it supports acting as both a nested scrolling parent and child on both new and old versions of Android. It is enabled by default. NestedScrollView is used when there is a need for a scrolling view inside another scrolling view.

How do I scroll NestedScrollView to bottom programmatically?

Try this: scrollView. post(new Runnable() { @Override public void run() { scrollView. fullScroll(ScrollView.

What is nestedScrollingEnabled?

android:nestedScrollingEnabled="true" in the nested (child) scrollable view, assuming you have one somewhere inside another. This causes the child view to scroll to completion, and then allow its parent to consume the rest of the scroll.


1 Answers

I spent quite a bit of time on this just going through android code trying to figure out what's going on in NestedScrollView. The following should work.

public abstract class ParentOfNestedScrollView extends NestedScrollView{      public ParentOfNestedScrollView(Context context, AttributeSet attrs) {         super(context, attrs);     }      /*      Have this return the range you want to scroll to until the      footer starts scrolling I have it as headerCard.getHeight()      on most implementations     */     protected abstract int getScrollRange();      /*     This has the parent do all the scrolling that happens until      you are ready for the child to scroll.     */     @Override     public void onNestedPreScroll(View target, int dx, int dy, int[] consumed){         if (dy > 0 && getScrollY() < getScrollRange()) {             int oldScrollY = getScrollY();             scrollBy(0, dy);             consumed[1] = getScrollY() - oldScrollY;         }     }      /*     Sometimes the parent scroll intercepts the event when you don't     want it to.  This prevents this from ever happening.     */     @Override     public boolean onInterceptTouchEvent(MotionEvent ev) {         return false;     } } 

Some of my code was borrowed from this question. From this I just extend this class as needed. My xml has the child as a NestedScrollView as a child and the parent as this. This doesn't handle flings as well as I would like, that's a work in progress.

like image 97
Ryan C Avatar answered Sep 18 '22 11:09

Ryan C