CollapsingToolbarLayout không nhận dạng cuộn cuộn


93

Tôi đã tạo một CollapsingToolbarLayout đơn giản và nó hoạt động như một sự quyến rũ. Vấn đề của tôi là nếu tôi cố gắng sử dụng cuộn cuộn trên màn hình lồng nhau , nó chỉ dừng lại khi tôi thả ngón tay ra. Thao tác cuộn bình thường hoạt động như bình thường.

Mã hoạt động của tôi không thay đổi => hoạt động trống được tạo tự động. (Tôi vừa nhấp vào tạo hoạt động trống mới trong studio android và chỉnh sửa XML).

Tôi đọc ở đây, rằng các cử chỉ cuộn trên chế độ xem hình ảnh có lỗi, nhưng không phải, bản thân thao tác cuộn có lỗi: xem tại đây .

Tôi đã thử kích hoạt "cuộn mượt" thông qua mã java. Có vẻ như nếu tôi cuộn đủ xa để chế độ xem hình ảnh không hiển thị nữa, thì các cử chỉ lướt sẽ được nhận dạng.

TLDR: Tại sao cử chỉ hất tung không hoạt động khi chế độ xem hình ảnh hiển thị? Mã XML của tôi trông như thế này:

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

Thật thú vị, tôi đã ghi nhật ký các sự kiện chạm vào chế độ xem cuộn lồng nhau trong quá trình di chuyển bị ảnh hưởng. Nó được ACTION_DOWN y=98 -> ACTION_MOVE y=-40 -> ACTION_MOVE y=-33 -> ACTION_UP y=97. Có vẻ như sự kiện lần chạm cuối cùng đang tự báo cáo sai là bên cạnh sự kiện đầu tiên.
Xiao

Bạn đang sử dụng phiên bản nào của thư viện hỗ trợ thiết kế?
Radu Topor

bạn có đang ghi đè bất kỳ sự kiện chạm nào không? cố gắng thiết lập nestedScrollView.getParent().requestDisallowInterceptTouchEvent(true);để xem di chuyển lồng nhau của bạn
Bhargav

Câu trả lời:


20

Tôi gặp chính xác vấn đề tương tự với CollapsingToolbarLayout với ImageView bên trong và NestedScrollView . Thao tác cuộn sẽ dừng khi thả ngón tay.

Tuy nhiên, tôi đã nhận thấy một điều kỳ lạ. Nếu bạn bắt đầu cuộn bằng ngón tay của mình từ một chế độ xem có OnClickListener (ví dụ: Nút), thao tác cuộn di chuyển sẽ hoạt động hoàn hảo.

Vì vậy, tôi đã sửa nó bằng một giải pháp kỳ lạ. Đặt OnClickListener (không làm gì cả) trên phần tử con trực tiếp của NestedScrollView . Sau đó, nó hoạt động hoàn hảo!

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

Cung cấp cho con trực tiếp (LinearLayout) một id và đặt OnClickListener trong Activity

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

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

Ghi chú:

Đã thử nghiệm bằng Thư viện thiết kế hỗ trợ 25.0.1

CollapsingToolbarLayout with scrollFlags = "scroll | enterAlwaysCollapsed"


AOSP nên được vá bằng giải pháp tuyệt vời này: D
evilant

Nên nhận được nhiều phiếu bầu hơn lol. Btw điều này làm cho CollapsingToolbarLayout rất nhạy cảm với cuộn, nhưng tốt hơn hành vi bị hỏng hiện tại.
Ahmad Fadli

1
này là quá tốt đến mức khó tin vì vậy tôi đã thử nó và nó không làm việc cho tôi
Gilbert Mendoza

đây là giải pháp điên rồ nhất mà tôi đã thử cho đến nay trong SO.
Techfist

: D quan sát tuyệt vời @jinang !!
Srichakradhar

10

Tôi biết câu hỏi này đã được hỏi hơn một năm trước nhưng vấn đề này dường như vẫn chưa được giải quyết trong thư viện Hỗ trợ / Thiết kế. Bạn có thể gắn dấu sao vấn đề này để nó tiến xa hơn trong hàng đợi ưu tiên.

Điều đó nói rằng, tôi đã thử hầu hết các giải pháp đã đăng cho việc này, bao gồm cả giải pháp của Patrick-iv nhưng không thành công. Cách duy nhất tôi có thể làm việc là bắt chước fling và gọi nó theo chương trình nếu một tập hợp điều kiện nhất định được phát hiện onPreNestedScroll(). Trong vài giờ gỡ lỗi của tôi, tôi nhận thấy rằng nó onNestedFling()không bao giờ được gọi khi di chuyển lên (cuộn xuống) và dường như bị tiêu thụ sớm. Tôi không thể nói chắc chắn 100% rằng điều này sẽ hoạt động cho 100% việc triển khai nhưng nó hoạt động đủ tốt cho các mục đích sử dụng của tôi vì vậy tôi đã kết thúc việc này, mặc dù nó khá hack và chắc chắn không phải những gì tôi muốn làm.

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

Và áp dụng nó vào AppBar

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

Bản demo CheeseSquare: Before After


Nó thực sự tốt hơn không có gì, nhưng không hoàn toàn như những gì một người dùng Android có kinh nghiệm mong đợi. Cảm ơn bạn đã liên kết vấn đề, tôi đã gắn dấu sao cho nó.
Raphael Royer-Rivard,

Phải xóa enterAlwayslayout_ScrollFlag để nó hoạt động, nhưng hiện đang hoạt động tốt
Alexandre G

3

Tôi đã thử giải pháp của Floofer nhưng nó vẫn không đủ tốt cho tôi. Vì vậy, tôi đã nghĩ ra một phiên bản tốt hơn về Hành vi của anh ấy. AppBarLayout giờ đây mở rộng và thu gọn một cách trơn tru khi di chuyển.

Lưu ý: Tôi đã sử dụng phản chiếu để xâm nhập vào điều này, vì vậy nó có thể không hoạt động hoàn hảo với phiên bản thư viện Thiết kế Android khác với 25.0.0.

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

Để sử dụng nó, hãy đặt Hành vi mới cho AppBarLayout của bạn.

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

Lớp học của bạn đòi hỏi một int trong constructor của nó, tuy nhiên trong các mã bạn gửi không có gì để các nhà xây dựng
bluesummers

Xấu của tôi, tôi đã thêm nó.
Raphael Royer-Rivard

Điều này có vẻ tốt, nó làm cho việc cuộn trơn tru, nhưng tôi có một câu hỏi, liệu có thể để NestedScrollView cuộn vào AppBarLayout khi AppBarLayout đạt đến đầu hay không, và khi tôi cuộn xuống, AppBarLayout xuất hiện cuối cùng, khi NestedScrollView hoàn toàn được cuộn ra, sau đó AppBarLayout bắt đầu mở rộng.
Zijian Wang

@ZijianWang Vui lòng giải thích cho tôi ý của bạn khi "cuộn vào AppBarLayout" và tôi cũng không hiểu câu hỏi thứ hai của bạn, bạn có thể diễn đạt lại được không?
Raphael Royer-Rivard

0

Câu trả lời này đã giải quyết vấn đề này cho tôi. Tạo một tùy chỉnh AppBarLayout.Behaviornhư sau:

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

và thêm nó vào AppBarLayoutnhư sau:

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

1
Không hoạt động vì vấn đề trong câu hỏi khác là RecyclerView không được sử dụng ở đây.
Felix Edelmann

0

Tôi chỉ đăng điều này ở đây để những người khác không bỏ lỡ nó trong Bình luận. Câu trả lời của Jinang rất hay, nhưng AntPachon rất thích vì đã chỉ ra một phương pháp đơn giản hơn nhiều cho cùng một phương pháp. Thay vì triển khai một OnClickphương pháp theo Child of the NestedScrollViewchương trình, một cách tốt hơn là đặtclickable=true trong xml cho đứa trẻ.

(Sử dụng ví dụ tương tự như của 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

Trong mã :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;

Khi tôi sử dụng cuộn cuộn trên NestedScrollView đôi khi "mIsBeingDragged = false", vì vậy NestedScrollView không gửi sự kiện di chuyển.

Khi tôi xóa if (mIsBeingDragged)câu lệnh.

 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;

sẽ không có vấn đề gì. Nhưng tôi không biết những vấn đề nghiêm trọng khác sẽ gây ra


Thêm chi tiết hơn để làm cho câu trả lời dễ hiểu, Bạn đã viết Khi tôi loại bỏ if ... từ nơi bạn đang nói để loại bỏ nếu ?
Devendra Singh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.