CollapsingToolbarLayout erkennt keinen Bildlauf


93

Ich habe ein einfaches CollapsingToolbarLayout erstellt und es funktioniert wie ein Zauber. Mein Problem ist, dass wenn ich versuche, eine Schleuderrolle in der verschachtelten Bildlaufansicht zu verwenden , diese einfach stoppt, wenn ich meinen Finger loslasse. Normales Scrollen funktioniert wie es sollte.

Mein Aktivitätscode ist unverändert => automatisch generierte leere Aktivität. (Ich habe gerade auf Neue leere Aktivität in Android Studio klicken geklickt und das XML noch bearbeitet).

Ich habe hier gelesen, dass Scroll-Gesten in der Bildansicht selbst fehlerhaft sind, aber nicht, dass das Scrollen selbst fehlerhaft ist: siehe hier .

Ich habe versucht, "Smooth Scrolling" durch Java-Code zu aktivieren . Wenn ich so weit scrolle, dass die Bildansicht nicht mehr sichtbar ist, werden Schleudergesten erkannt.

TLDR: Warum funktioniert die Fling-Geste nicht, solange die Bildansicht sichtbar ist? Mein XML-Code sieht folgendermaßen aus:

    <android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/profile_app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/profile_collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginStart="48dp"
            app:expandedTitleMarginEnd="64dp"
            android:fitsSystemWindows="true">

            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:layout_height="420dp"
                android:scaleType="centerCrop"
                android:fitsSystemWindows="true"
                android:src="@drawable/headerbg"
                android:maxHeight="192dp"

                app:layout_collapseMode="parallax"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:layout_collapseMode="pin" />

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        app:layout_anchor="@id/profile_app_bar_layout"
        app:layout_anchorGravity="bottom|right|end"
        android:layout_height="@dimen/fab_size_normal"
        android:layout_width="@dimen/fab_size_normal"
        app:elevation="2dp"
        app:pressedTranslationZ="12dp"
        android:layout_marginRight="8dp"
        android:layout_marginEnd="8dp"/>

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/profile_content_scroll"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_gravity="fill_vertical"
        android:minHeight="192dp"
        android:overScrollMode="ifContentScrolls"
        >

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/LoremIpsum"/>

        </RelativeLayout>

    </android.support.v4.widget.NestedScrollView>

</android.support.design.widget.CoordinatorLayout>

Interessanterweise habe ich die Berührungsereignisse in der verschachtelten Bildlaufansicht während eines betroffenen Flings protokolliert. Es wird ACTION_DOWN y=98 -> ACTION_MOVE y=-40 -> ACTION_MOVE y=-33 -> ACTION_UP y=97. Es sieht so aus, als würde sich das letzte Berührungsereignis fälschlicherweise als neben dem ersten melden.
Xiao

Welche Version der Design-Support-Bibliothek verwenden Sie?
Radu Topor

Überschreiben Sie Berührungsereignisse? Versuchen Sie, nestedScrollView.getParent().requestDisallowInterceptTouchEvent(true);auf Ihre verschachtelte
Bildlaufansicht

Antworten:


20

Ich hatte genau das gleiche Problem mit CollapsingToolbarLayout mit ImageView im Inneren und NestedScrollView . Die Schleuderrolle stoppt, wenn der Finger losgelassen wird.

Ich habe jedoch etwas Seltsames bemerkt. Wenn Sie mit dem Finger aus einer Ansicht mit OnClickListener (z. B. Button) mit dem Finger scrollen, funktioniert das Fling-Scrollen perfekt.

Also habe ich es mit einer seltsamen Lösung behoben. Setzen Sie OnClickListener (das tut nichts) für das direkte untergeordnete Element von NestedScrollView . Dann funktioniert es perfekt!

<android.support.v4.widget.NestedScrollView 
   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"
   app:layout_behavior="@string/appbar_scrolling_view_behavior">

  <LinearLayout
      android:id="@+id/content_container"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical">

    <!-- Page Content -->

  </LinearLayout>

</android.support.v4.widget.NestedScrollView>

Geben Sie dem direkten Kind (LinearLayout) eine ID und setzen Sie OnClickListener in Activity

ViewGroup mContentContainer = (ViewGroup) findViewById(R.id.content_container);    
mContentContainer.setOnClickListener(this);

@Override
public void onClick(View view) {
    int viewId = view.getId();
}

Anmerkungen:

Getestet mit der Support Design Library 25.0.1

CollapsingToolbarLayout mit scrollFlags = "scroll | enterAlwaysCollapsed"


AOSP sollte mit dieser großartigen Lösung gepatcht werden: D
abweichend

Sollte mehr Stimmen bekommen lol. Übrigens machte das CollapsingToolbarLayout sehr empfindlich für das Scrollen, aber besser als das aktuelle fehlerhafte Verhalten.
Ahmad Fadli

1
Das war zu schön um wahr zu sein, also habe ich es versucht und es funktioniert nicht für mich
Gilbert Mendoza

Dies ist die verrückteste Lösung, die ich bisher in SO versucht habe.
Techfist

: D tolle Beobachtung @jinang !!
Srichakradhar

10

Ich weiß, dass diese Frage vor über einem Jahr gestellt wurde, aber dieses Problem scheint in den Support- / Design-Bibliotheken immer noch nicht gelöst zu sein. Sie können dieses Problem markieren, damit es in der Prioritätswarteschlange weiter nach oben verschoben wird.

Trotzdem habe ich die meisten der veröffentlichten Lösungen dafür ausprobiert, einschließlich der von Patrick-IV, ohne Erfolg. Die einzige Möglichkeit, zur Arbeit zu kommen, bestand darin, den Fling nachzuahmen und ihn programmgesteuert aufzurufen, wenn bestimmte Bedingungen in erkannt wurden onPreNestedScroll(). In den wenigen Stunden meines Debuggens bemerkte ich, dass der onNestedFling()nie nach oben (nach unten scrollen) aufgerufen wurde und vorzeitig verbraucht zu sein schien. Ich kann nicht mit 100% iger Sicherheit sagen, dass dies für 100% der Implementierungen funktioniert, aber es funktioniert gut genug für meine Zwecke, so dass ich mich damit zufrieden gegeben habe, obwohl es ziemlich hackig ist und definitiv nicht das, was ich tun wollte.

public class NestedScrollViewBehavior extends AppBarLayout.Behavior {

    // Lower value means fling action is more easily triggered
    static final int MIN_DY_DELTA = 4;
    // Lower values mean less velocity, higher means higher velocity
    static final int FLING_FACTOR = 20;

    int mTotalDy;
    int mPreviousDy;
    WeakReference<AppBarLayout> mPreScrollChildRef;

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                                  View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        // Reset the total fling delta distance if the user starts scrolling back up
        if(dy < 0) {
            mTotalDy = 0;
        }
        // Only track move distance if the movement is positive (since the bug is only present
        // in upward flings), equal to the consumed value and the move distance is greater
        // than the minimum difference value
        if(dy > 0 && consumed[1] == dy && MIN_DY_DELTA < Math.abs(mPreviousDy - dy)) {
            mPreScrollChildRef = new WeakReference<>(child);
            mTotalDy += dy * FLING_FACTOR;
        }
        mPreviousDy = dy;
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
                                       View directTargetChild, View target, int nestedScrollAxes) {
        // Stop any previous fling animations that may be running
        onNestedFling(parent, child, target, 0, 0, false);
        return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout parent, AppBarLayout abl, View target) {
        if(mTotalDy > 0 && mPreScrollChildRef != null && mPreScrollChildRef.get() != null) {
            // Programmatically trigger fling if all conditions are met
            onNestedFling(parent, mPreScrollChildRef.get(), target, 0, mTotalDy, false);
            mTotalDy = 0;
            mPreviousDy = 0;
            mPreScrollChildRef = null;
        }
        super.onStopNestedScroll(parent, abl, target);
    }
}

Und wenden Sie es auf die AppBar an

AppBarLayout scrollView = (AppBarLayout)findViewById(R.id.appbar);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams)scrollView.getLayoutParams();
params.setBehavior(new NestedScrollViewBehavior());

CheeseSquare Demo: Vor Nach


Es ist in der Tat besser als nichts, aber nicht ganz das, was ein erfahrener Android-Benutzer erwarten würde. Vielen Dank, dass Sie das Problem verlinkt haben. Ich habe es in der Hauptrolle gespielt.
Raphael Royer-Rivard

Musste das enterAlwayslayout_ScrollFlag entfernen, damit es funktioniert, aber jetzt gut funktioniert
Alexandre G

3

Ich habe Floofers Lösung ausprobiert, aber sie war mir immer noch nicht gut genug. Also habe ich mir eine bessere Version seines Verhaltens ausgedacht. Das AppBarLayout wird jetzt erweitert und beim Schleudern reibungslos reduziert.

Hinweis: Ich habe Reflection verwendet, um mich hineinzuhacken, sodass es mit einer anderen Version der Android Design-Bibliothek als 25.0.0 möglicherweise nicht perfekt funktioniert.

public class SmoothScrollBehavior extends AppBarLayout.Behavior {
    private static final String TAG = "SmoothScrollBehavior";
    //The higher this value is, the faster the user must scroll for the AppBarLayout to collapse by itself
    private static final int SCROLL_SENSIBILITY = 5;
    //The real fling velocity calculation seems complex, in this case it is simplified with a multiplier
    private static final int FLING_VELOCITY_MULTIPLIER = 60;

    private boolean alreadyFlung = false;
    private boolean request = false;
    private boolean expand = false;
    private int velocity = 0;
    private int nestedScrollViewId;

    public SmoothScrollBehavior(int nestedScrollViewId) {
        this.nestedScrollViewId = nestedScrollViewId;
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                                  View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        if(Math.abs(dy) >= SCROLL_SENSIBILITY) {
            request = true;
            expand = dy < 0;
            velocity = dy * FLING_VELOCITY_MULTIPLIER;
        } else {
            request = false;
        }
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
                                       View directTargetChild, View target, int nestedScrollAxes) {
        request = false;
        return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout, View target) {
        if(request) {
            NestedScrollView nestedScrollView = (NestedScrollView) coordinatorLayout.findViewById(nestedScrollViewId);
            if (expand) {
                //No need to force expand if it is already ready expanding
                if (nestedScrollView.getScrollY() > 0) {
                    int finalY = getPredictedScrollY(nestedScrollView);
                    if (finalY <= 0) {
                        //since onNestedFling does not work to expand the AppBarLayout, we need to manually expand it
                        expandAppBarLayoutWithVelocity(coordinatorLayout, appBarLayout, velocity);
                    }
                }
            } else {
                //onNestedFling will collapse the AppBarLayout with an animation time relative to the velocity
                onNestedFling(coordinatorLayout, appBarLayout, target, 0, velocity, true);

                if(!alreadyFlung) {
                    //TODO wait for AppBarLayout to be collapsed before scrolling for even smoother visual
                    nestedScrollView.fling(velocity);
                }
            }
        }
        alreadyFlung = false;
        super.onStopNestedScroll(coordinatorLayout, appBarLayout, target);
    }

    private int getPredictedScrollY(NestedScrollView nestedScrollView) {
        int finalY = 0;
        try {
            //With reflection, we can get the ScrollerCompat from the NestedScrollView to predict where the scroll will end
            Field scrollerField = nestedScrollView.getClass().getDeclaredField("mScroller");
            scrollerField.setAccessible(true);
            Object object = scrollerField.get(nestedScrollView);
            ScrollerCompat scrollerCompat = (ScrollerCompat) object;
            finalY = scrollerCompat.getFinalY();
        } catch (Exception e ) {
            e.printStackTrace();
            //If the reflection fails, it will return 0, which means the scroll has reached top
            Log.e(TAG, "Failed to get mScroller field from NestedScrollView through reflection. Will assume that the scroll reached the top.");
        }
        return finalY;
    }

    private void expandAppBarLayoutWithVelocity(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout, float velocity) {
        try {
            //With reflection, we can call the private method of Behavior that expands the AppBarLayout with specified velocity
            Method animateOffsetTo = getClass().getSuperclass().getDeclaredMethod("animateOffsetTo", CoordinatorLayout.class, AppBarLayout.class, int.class, float.class);
            animateOffsetTo.setAccessible(true);
            animateOffsetTo.invoke(this, coordinatorLayout, appBarLayout, 0, velocity);
        } catch (Exception e) {
            e.printStackTrace();
            //If the reflection fails, we fall back to the public method setExpanded that expands the AppBarLayout with a fixed velocity
            Log.e(TAG, "Failed to get animateOffsetTo method from AppBarLayout.Behavior through reflection. Falling back to setExpanded.");
            appBarLayout.setExpanded(true, true);
        }
    }

    @Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {
        alreadyFlung = true;
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
    }
}

Um es zu verwenden, legen Sie ein neues Verhalten für Ihr AppBarLayout fest.

AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.app_bar);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
params.setBehavior(new SmoothScrollBehavior(R.id.nested_scroll_view));

Ihre Klasse verlangt ein int in ihrem 'Konstruktor, aber im Code senden Sie nichts an den Konstruktor
bluesummers

Mein schlechtes, ich habe es hinzugefügt.
Raphael Royer-Rivard

Das sieht gut aus, es macht das Scrollen reibungslos, aber ich habe eine Frage: Ist es möglich, das NestedScrollView in AppBarLayout scrollen zu lassen, sobald das AppBarLayout oben angekommen ist, und wenn ich nach unten scrolle, erscheint AppBarLayout endlich, wenn das NestedScrollView vollständig ausgeblättert, dann beginnt AppBarLayout zu erweitern.
Zijian Wang

@ZijianWang Bitte erklären Sie mir, was Sie unter "Scrollen in AppBarLayout" verstehen, und ich verstehe auch Ihre zweite Frage nicht. Können Sie sie umformulieren?
Raphael Royer-Rivard

0

Diese Antwort hat dieses Problem für mich gelöst. Erstellen Sie eine benutzerdefinierte AppBarLayout.Behaviorwie folgt:

public final class FlingBehavior extends AppBarLayout.Behavior {
    private static final int TOP_CHILD_FLING_THRESHOLD = 3;
    private boolean isPositive;

    public FlingBehavior() {
    }

    public FlingBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
            velocityY = velocityY * -1;
        }
        if (target instanceof RecyclerView && velocityY < 0) {
            final RecyclerView recyclerView = (RecyclerView) target;
            final View firstChild = recyclerView.getChildAt(0);
            final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);
            consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        isPositive = dy > 0;
    }
}

und füge es dem folgenden hinzu AppBarLayout:

<android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        ...
        app:layout_behavior="com.example.test.FlingBehavior">

1
Hat nicht funktioniert, da das Problem in der anderen Frage die RecyclerView war, die hier nicht verwendet wird.
Felix Edelmann

0

Ich poste dies nur hier, damit andere es nicht in den Kommentaren verpassen. Die Antwort von Jinang funktioniert wunderbar, aber ein großes Lob an AntPachon für den Hinweis auf eine weitaus einfachere Methode. Anstatt eine OnClickMethode Child of the NestedScrollViewprogrammgesteuert zu implementieren , ist es besser, clickable=truedie XML-Datei für das untergeordnete Element festzulegen.

(Am selben Beispiel wie bei Jinang )

<android.support.v4.widget.NestedScrollView 
   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"
   app:layout_behavior="@string/appbar_scrolling_view_behavior">

  <LinearLayout
      android:id="@+id/content_container"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical"
      android:clickable="true" >                  <!-- new -->

    <!-- Page Content -->

  </LinearLayout>

</android.support.v4.widget.NestedScrollView>

-1

Im Code: https://android.googlesource.com/platform/frameworks/support/+/master/core-ui/java/android/support/v4/widget/NestedScrollView.java#834

       case MotionEvent.ACTION_UP:
            if (mIsBeingDragged) {
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker,
                        mActivePointerId);
                if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                    flingWithNestedDispatch(-initialVelocity);
                } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                        getScrollRange())) {
                    ViewCompat.postInvalidateOnAnimation(this);
                }
            }
            mActivePointerId = INVALID_POINTER;
            endDrag();
            break;

Wenn ich einen Fling-Scroll in der NestedScrollView verwende, manchmal "mIsBeingDragged = false", löst NestedScrollView kein Fling-Ereignis aus.

Wenn ich die if (mIsBeingDragged)Anweisung lösche .

 case MotionEvent.ACTION_UP:
        //if (mIsBeingDragged) {
            final VelocityTracker velocityTracker = mVelocityTracker;
            velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker,
                    mActivePointerId);
            if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                flingWithNestedDispatch(-initialVelocity);
            } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                    getScrollRange())) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        //}
        mActivePointerId = INVALID_POINTER;
        endDrag();
        break;

Es wird kein Problem geben. Aber ich weiß nicht, welche anderen ernsthaften Probleme verursacht werden


Fügen Sie weitere Details hinzu, um die Antwort verständlich zu machen. Sie haben geschrieben: Wenn ich das if entferne ... von wo Sie sprechen, um if zu entfernen ?
Devendra Singh
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.