2015-04-26 8 views
55

Mit Unterstützung-v4-Bibliothek 22.1.0 Android unterstützt geschachteltes Scrollen (Pre-Android 5.0). Leider ist diese Funktion nicht wirklich dokumentiert. Es gibt zwei Schnittstellen (NestedScrollingParent und NestedScrollingChild) sowie zwei Helfer-Delegate-Klassen (NestedScrollingChildHelper und NestedScrollingParentHelper).Wie Implementiere NestedScrolling auf Android?

Hat jemand mit NestedScrolling auf Android gearbeitet?

Ich habe versucht, ein kleines Beispiel einzurichten, wo ich NestedScrollView verwende, die beide NestedScrollingParent und NestedScrollingChild implementiert.

Mein Layout sieht wie folgt aus:

<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> 

ich eine header view und andere NestedScrollView (id = Kind) in einer NestedScrollView (id = parent) angezeigt werden sollen.

Die Idee war, unter Verwendung einer der Höhe der Kind Scroll-Ansicht zur Laufzeit anpassen 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 wird die Kopfansicht teilweise gescrollt entfernt wird, werden 40 Pixel sichtbar bleiben, da ich die eingestellte Höhe der geschachtelten untergeordneten Ansicht von unter parentScroll.getHeight() - 40. In Ordnung, die Höhe zur Laufzeit und Scrollen der übergeordneten Bildlaufansicht funktioniert wie erwartet (Header scrollt heraus, 40 Pixel bleiben sichtbar und dann füllt der untergeordnete scrollview den Rest des Bildschirms unterhalb der Kopfzeile).

Ich würde erwarten, dass „NestedScrolling“ bedeutet, dass ich irgendwo eine Scroll-Geste auf dem Bildschirm machen kann (Touch-Ereignis gefangen durch Scroll-Ansicht Elternteil) und wenn die übergeordnete Ansicht blättern, das Ende des verschachtelte Kind Scroll-Ansicht beginn blättern erreicht hat . Dies scheint jedoch nicht der Fall zu sein (weder für einfache Scroll-Gesten noch für Fling-Gesten).

Das Berührungsereignis wird immer von verschachtelten untergeordneten Scrollviews behandelt, wenn das Berührungsereignis in seinen Grenzen beginnt, andernfalls vom übergeordneten Scrollview.

Ist das das erwartete Verhalten von "verschachteltem Scrollen" oder gibt es eine Option, dieses Verhalten zu ändern?

Ich habe auch versucht, die verschachtelte untergeordnete Ansicht mit einem NestedRecyclerView zu ersetzen. Ich subclassed RecyclerView und umgesetzt NestedScrollingChild, wo ich alle Methoden zu NestedScrollingChildHelper delegieren:

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); 
    } 
} 

aber die NestedRecyclerView gar nicht bewegen. Alle Berührungsereignisse werden von der übergeordneten Bildlaufansicht abgefangen.

+0

Ich habe das gleiche Problem und denke, dass dieses Widget verbessert werden sollte, um Eltern zu ermöglichen, unverbrauchten Scroll zu seinem Kind zu schicken. –

+0

Ich denke, Recycler-View zu einem NestedScrollingChild machen ist nicht so einfach, da es den LayoutManager zum Scrollen und Positionieren seines Elements verwendet. Daher funktioniert nur die Implementierung von NestedScrollingChild mit NestedScrollingChildHelper nicht, da diese Funktionen beim Scrollen nicht aufgerufen werden. –

+0

Ich bin mir nicht sicher. Ich nehme an, dass NestedScrollingParent/NestedScrollingChild verwendet wird, um das Touch-Ereignis weiterzuleiten. Ich hoffe, dass 'NestedScrollingChild' dies intern tut, ich kann keinen Grund sehen, warum es für' ScrollView' funktionieren sollte, aber nicht für 'RecyclerView' ...' LayoutManager' sollte kein Problem sein ... – sockeqwe

Antwort

3

Ich verbrachte ziemlich viel Zeit auf dieser gerade durch Android-Code versucht, herauszufinden, was in NestedScrollView vor sich geht. Folgendes sollte funktionieren.

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; 
    } 
} 

Einige meiner Code wurde aus diesem question entlehnt. Von daher erweitere ich diese Klasse nur nach Bedarf. Mein xml hat das Kind als NestedScrollView als Kind und das Eltern als dies. Dies funktioniert nicht so gut wie es mir gefällt, das ist ein Work in Progress.

+0

Ich verlor einen Tag an diesem Thema und Ihre Lösung hat mich gerettet, danke! –

Verwandte Themen