Tung ra với RecyclerView + AppBarLayout


171

Tôi đang sử dụng Điều phối viên mới với AppBarLayout và CollapsingToolbarLayout. Bên dưới AppBarLayout, tôi có RecyclerView với danh sách nội dung.

Tôi đã xác minh rằng cuộn cuộn hoạt động trên RecyclerView khi tôi cuộn lên và xuống danh sách. Tuy nhiên, tôi cũng muốn AppBarLayout cuộn trơn tru trong quá trình mở rộng.

Khi cuộn lên để mở rộng CollaspingToolbarLayout, cuộn ngay lập tức dừng lại sau khi nhấc ngón tay khỏi màn hình. Nếu bạn cuộn lên trong một chuyển động nhanh, đôi khi CollapsingToolbarLayout cũng sụp đổ. Hành vi này với RecyclerView dường như hoạt động khác nhiều so với khi sử dụng NestedScrollView.

Tôi đã cố gắng thiết lập các thuộc tính cuộn khác nhau trên recyclerview nhưng tôi không thể tìm ra điều này.

Dưới đây là một video cho thấy một số vấn đề cuộn. https://youtu.be/xMLKoJOsTAM

Dưới đây là một ví dụ cho thấy vấn đề với RecyclerView (CheeseDetailActivity). https://github.com/tylerjroach/cheesesapes

Dưới đây là ví dụ ban đầu sử dụng NestedScrollView từ Chris Banes. https://github.com/chrisbanes/cheesesapes


Tôi đang gặp vấn đề chính xác tương tự (Tôi đang sử dụng với RecyclerView). Nếu bạn xem danh sách cửa hàng google play cho bất kỳ ứng dụng nào, nó dường như hoạt động chính xác, vì vậy chắc chắn có một giải pháp ngoài kia ...
Aneem

Xin chào Aneem, tôi biết đây không phải là giải pháp tốt nhất nhưng tôi đã bắt đầu thử nghiệm với thư viện này: github.com/ksoichiro/Android-ObservableScrollView . Đặc biệt là tại hoạt động này để đạt được kết quả tôi cần: FlexSpaceWithImageRecyclerViewActivity.java. Xin lỗi về việc viết sai tên của bạn trước khi chỉnh sửa. Tự động sửa lỗi ..
tylerjroach

2
Vấn đề tương tự ở đây, tôi đã kết thúc việc tránh AppBarLayout.
Renaud Cerrato

Vâng. Cuối cùng tôi đã nhận được chính xác những gì tôi cần từ thư viện OvservableScrollView. Tôi chắc chắn rằng nó sẽ được sửa trong các phiên bản trong tương lai.
tylerjroach

8
Các fling là lỗi, một vấn đề đã được nêu ra (và được chấp nhận).
Renaud Cerrato

Câu trả lời:


114

Câu trả lời của Kirill Boyarshinov gần như đúng.

Vấn đề chính là đôi khi RecyclerView đưa ra hướng ném không chính xác, vì vậy nếu bạn thêm đoạn mã sau vào câu trả lời của anh ta thì nó hoạt động chính xác:

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

Tôi hy vọng rằng điều này sẽ giúp.


Bạn đã cứu ngày của tôi! Có vẻ như đang làm việc hoàn toàn tốt! Tại sao câu trả lời của bạn không được chấp nhận?
Zordid

9
nếu bạn đang sử dụng SwipeRefreshLayout với tư cách là cha mẹ của recyclerview của bạn, chỉ cần thêm mã này: if (target instanceof SwipeRefreshLayout && velocityY < 0) { target = ((SwipeRefreshLayout) target).getChildAt(0); }trước if (target instanceof RecyclerView && velocityY < 0) {
LucasFM

1
+ 1 Phân tích sửa lỗi này, tôi không hiểu tại sao Google chưa sửa lỗi này. Mã dường như khá đơn giản.
Gaston Flores

3
Xin chào làm thế nào để đạt được điều tương tự với appbarlayout và Nestedscrollview ... Cảm ơn trước ..
Harry Sharma

1
Nó không hoạt động với tôi = / Nhân tiện, bạn không cần chuyển lớp vào gói hỗ trợ để đạt được nó, bạn có thể đăng ký DragCallback trong hàm tạo.
Augusto Carmo

69

Có vẻ như v23bản cập nhật chưa khắc phục được.

Tôi đã tìm thấy một loại hack để sửa nó với sự cố. Mẹo nhỏ là xem xét lại sự kiện fling nếu con đầu của ScrollingView gần với phần đầu của dữ liệu trong Adaptor.

public final class FlingBehavior extends AppBarLayout.Behavior {

    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 (target instanceof ScrollingView) {
            final ScrollingView scrollingView = (ScrollingView) target;
            consumed = velocityY > 0 || scrollingView.computeVerticalScrollOffset() > 0;
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }
}

Sử dụng nó trong bố trí của bạn như thế:

 <android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="your.package.FlingBehavior">
    <!--your views here-->
 </android.support.design.widget.AppBarLayout>

EDIT: Việc xem xét lại sự kiện Fling hiện được dựa trên verticalScrollOffsetthay vì số lượng vật phẩm từ đầu RecyclerView.

EDIT2: Kiểm tra mục tiêu dưới dạng ScrollingViewgiao diện thay vì RecyclerView. Cả hai RecyclerViewNestedScrollingViewthực hiện nó.


Bắt các loại chuỗi không được phép cho lỗi
layout_behavior

Tôi đã thử nó và làm việc tốt hơn người đàn ông! nhưng mục đích của TOP_CHILD_FLING_THRESHOLD là gì? và tại sao nó là 3?
Julio_oa

@Julio_oa TOP_CHILD_FLING_THRESHOLD có nghĩa là sự kiện fling sẽ được xem xét lại nếu chế độ xem của trình tái chế được cuộn đến phần tử có vị trí nằm dưới giá trị ngưỡng này. Btw Tôi đã cập nhật câu trả lời để sử dụng verticalScrollOffsetchung hơn. Bây giờ sự kiện fling sẽ được xem xét lại khi recyclerViewđược cuộn lên trên cùng.
Kirill Boyarshinov

Xin chào làm thế nào để đạt được điều tương tự với appbarlayout và Nestedscrollview ... Cảm ơn trước ..
Harry Sharma

2
@Hardeep thay đổi target instanceof RecyclerViewthành target instanceof NestedScrollView, hoặc nhiều hơn cho trường hợp chung thành target instanceof ScrollingView. Tôi cập nhật câu trả lời.
Kirill Boyarshinov

15

Tôi đã tìm thấy bản sửa lỗi bằng cách áp dụng OnScrollingListener cho recyclerView. bây giờ nó hoạt động rất tốt Vấn đề là recyclerview cung cấp giá trị tiêu thụ sai và hành vi không biết khi nào recyclerview được cuộn lên trên cùng.

package com.singmak.uitechniques.util.coordinatorlayout;

import android.content.Context;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by maksing on 26/3/2016.
 */
public final class RecyclerViewAppBarBehavior extends AppBarLayout.Behavior {

    private Map<RecyclerView, RecyclerViewScrollListener> scrollListenerMap = new HashMap<>(); //keep scroll listener map, the custom scroll listener also keep the current scroll Y position.

    public RecyclerViewAppBarBehavior() {
    }

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

    /**
     *
     * @param coordinatorLayout
     * @param child The child that attached the behavior (AppBarLayout)
     * @param target The scrolling target e.g. a recyclerView or NestedScrollView
     * @param velocityX
     * @param velocityY
     * @param consumed The fling should be consumed by the scrolling target or not
     * @return
     */
    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        if (target instanceof RecyclerView) {
            final RecyclerView recyclerView = (RecyclerView) target;
            if (scrollListenerMap.get(recyclerView) == null) {
                RecyclerViewScrollListener recyclerViewScrollListener = new RecyclerViewScrollListener(coordinatorLayout, child, this);
                scrollListenerMap.put(recyclerView, recyclerViewScrollListener);
                recyclerView.addOnScrollListener(recyclerViewScrollListener);
            }
            scrollListenerMap.get(recyclerView).setVelocity(velocityY);
            consumed = scrollListenerMap.get(recyclerView).getScrolledY() > 0; //recyclerView only consume the fling when it's not scrolled to the top
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    private static class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
        private int scrolledY;
        private boolean dragging;
        private float velocity;
        private WeakReference<CoordinatorLayout> coordinatorLayoutRef;
        private WeakReference<AppBarLayout> childRef;
        private WeakReference<RecyclerViewAppBarBehavior> behaviorWeakReference;

        public RecyclerViewScrollListener(CoordinatorLayout coordinatorLayout, AppBarLayout child, RecyclerViewAppBarBehavior barBehavior) {
            coordinatorLayoutRef = new WeakReference<CoordinatorLayout>(coordinatorLayout);
            childRef = new WeakReference<AppBarLayout>(child);
            behaviorWeakReference = new WeakReference<RecyclerViewAppBarBehavior>(barBehavior);
        }

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            dragging = newState == RecyclerView.SCROLL_STATE_DRAGGING;
        }

        public void setVelocity(float velocity) {
            this.velocity = velocity;
        }

        public int getScrolledY() {
            return scrolledY;
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            scrolledY += dy;

            if (scrolledY <= 0 && !dragging && childRef.get() != null && coordinatorLayoutRef.get() != null && behaviorWeakReference.get() != null) {
                //manually trigger the fling when it's scrolled at the top
                behaviorWeakReference.get().onNestedFling(coordinatorLayoutRef.get(), childRef.get(), recyclerView, 0, velocity, false);
            }
        }
    }
}

Cảm ơn bài viết của bạn. Tôi đã thử tất cả các câu trả lời trên trang này và theo kinh nghiệm của tôi, đây là câu trả lời hiệu quả nhất. Nhưng, RecylerView trong bố cục của tôi cuộn bên trong trước khi AppBarLayout cuộn khỏi màn hình nếu tôi không cuộn RecyclerView với đủ lực. Nói cách khác, khi tôi cuộn RecyclerView với đủ lực, AppBar sẽ cuộn ra khỏi màn hình mà không cuộn RecyclerView bên trong, nhưng khi tôi không cuộn RecyclerView với đủ lực thì RecyclerView sẽ cuộn bên trong trước khi AppbarLayout cuộn ra khỏi màn hình. Bạn có biết những gì gây ra điều đó?
Micah Simmons

Recyclerview vẫn nhận được các sự kiện chạm đó là lý do tại sao nó vẫn cuộn, hành vi trênNestedFling sẽ tạo hiệu ứng để cuộn appbarLayout cùng một lúc. Có lẽ bạn có thể thử ghi đè onInterceptTouch trong hành vi để thay đổi điều này. Đối với tôi hành vi hiện tại là chấp nhận được từ những gì tôi thấy. (không chắc chúng ta có thấy điều tương tự không)
Mak Sing

@MakSing nó thực sự hữu ích với CoordinatorLayoutViewPagerthiết lập cảm ơn rất nhiều cho giải pháp được chờ đợi nhất này. Vui lòng viết GIST cho cùng để các nhà phát triển khác cũng có thể hưởng lợi từ nó. Tôi cũng đang chia sẻ giải pháp này. Cảm ơn một lần nữa.
Nitin Misra

1
@MakSing Tắt tất cả các giải pháp, điều này hoạt động tốt nhất cho tôi. Tôi đã điều chỉnh vận tốc được truyền cho onNestedFling một chút vận tốc * 0,6f ... dường như mang lại một luồng tốt hơn cho nó.
kẻ phá hoại

Làm việc cho tôi. @MakSing Có phải trong phương thức onScrolled, bạn phải gọi onNestedFling của AppBarLayout.Behavior chứ không phải của RecyclerViewAppBarBehavior? Có vẻ hơi lạ đối với tôi.
Anton Malmygin

13

Nó đã được sửa chữa kể từ khi hỗ trợ thiết kế 26.0.0.

compile 'com.android.support:design:26.0.0'

2
Điều này cần phải di chuyển lên. Điều này được mô tả ở đây trong trường hợp bất cứ ai quan tâm đến các chi tiết.
Chris Dinon

1
Bây giờ dường như có một vấn đề với thanh trạng thái, khi bạn cuộn thanh trạng thái xuống một chút với cuộn ... siêu khó chịu!
hộp

2
@Xiaozou Tôi đang sử dụng 26.1.0 và vẫn gặp sự cố với flelling. Ném nhanh đôi khi dẫn đến chuyển động ngược lại (Vận tốc của chuyển động ngược / sai như có thể thấy trong phương pháp onNestedFling). Đã sao chép nó trong Xiaomi Redmi Note 3 và Galaxy S3
dor506

@ dor506 stackoverflow.com/a/47298312/782870 Tôi không chắc liệu chúng tôi có gặp vấn đề tương tự khi bạn nói kết quả chuyển động ngược lại hay không. Nhưng tôi đã đăng một câu trả lời ở đây. Hy vọng nó sẽ giúp :)
vida



2

Đây là Bố cục của tôi và cuộn Nó hoạt động như bình thường.

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

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbarLayout"
    android:layout_height="192dp"
    android:layout_width="match_parent">

    <android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/ctlLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_scrollFlags="scroll|exitUntilCollapsed"
        app:contentScrim="?attr/colorPrimary"
        app:layout_collapseMode="parallax">

        <android.support.v7.widget.Toolbar
            android:id="@+id/appbar"
            android:layout_height="?attr/actionBarSize"
            android:layout_width="match_parent"
            app:layout_scrollFlags="scroll|enterAlways"
            app:layout_collapseMode="pin"/>

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

<android.support.v7.widget.RecyclerView
    android:id="@+id/catalogueRV"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

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

2

Giải pháp của tôi cho đến nay, dựa trên câu trả lời của Mak SingManolo Garcia .

Nó không hoàn toàn hoàn hảo. Hiện tại tôi không biết cách tính lại vận tốc valide để tránh hiệu ứng lạ: thanh ứng dụng có thể mở rộng nhanh hơn tốc độ cuộn. Nhưng trạng thái với một thanh ứng dụng mở rộng và chế độ xem tái chế cuộn có thể đạt được.

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;

import java.lang.ref.WeakReference;

public class FlingAppBarLayoutBehavior
        extends AppBarLayout.Behavior {

    // The minimum I have seen for a dy, after the recycler view stopped.
    private static final int MINIMUM_DELTA_Y = -4;

    @Nullable
    RecyclerViewScrollListener mScrollListener;

    private boolean isPositive;

    public FlingAppBarLayoutBehavior() {
    }

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

    public boolean callSuperOnNestedFling(
            CoordinatorLayout coordinatorLayout,
            AppBarLayout child,
            View target,
            float velocityX,
            float velocityY,
            boolean consumed) {
        return super.onNestedFling(
                coordinatorLayout,
                child,
                target,
                velocityX,
                velocityY,
                consumed
        );
    }

    @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) {
            RecyclerView recyclerView = (RecyclerView) target;

            if (mScrollListener == null) {
                mScrollListener = new RecyclerViewScrollListener(
                        coordinatorLayout,
                        child,
                        this
                );
                recyclerView.addOnScrollListener(mScrollListener);
            }

            mScrollListener.setVelocity(velocityY);
        }

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

    private static class RecyclerViewScrollListener
            extends RecyclerView.OnScrollListener {

        @NonNull
        private final WeakReference<AppBarLayout> mAppBarLayoutWeakReference;

        @NonNull
        private final WeakReference<FlingAppBarLayoutBehavior> mBehaviorWeakReference;

        @NonNull
        private final WeakReference<CoordinatorLayout> mCoordinatorLayoutWeakReference;

        private int mDy;

        private float mVelocity;

        public RecyclerViewScrollListener(
                @NonNull CoordinatorLayout coordinatorLayout,
                @NonNull AppBarLayout child,
                @NonNull FlingAppBarLayoutBehavior barBehavior) {
            mCoordinatorLayoutWeakReference = new WeakReference<>(coordinatorLayout);
            mAppBarLayoutWeakReference = new WeakReference<>(child);
            mBehaviorWeakReference = new WeakReference<>(barBehavior);
        }

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                if (mDy < MINIMUM_DELTA_Y
                        && mAppBarLayoutWeakReference.get() != null
                        && mCoordinatorLayoutWeakReference.get() != null
                        && mBehaviorWeakReference.get() != null) {

                    // manually trigger the fling when it's scrolled at the top
                    mBehaviorWeakReference.get()
                            .callSuperOnNestedFling(
                                    mCoordinatorLayoutWeakReference.get(),
                                    mAppBarLayoutWeakReference.get(),
                                    recyclerView,
                                    0,
                                    mVelocity, // TODO find a way to recalculate a correct velocity.
                                    false
                            );

                }
            }
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            mDy = dy;
        }

        public void setVelocity(float velocity) {
            mVelocity = velocity;
        }

    }

}

Bạn có thể có được vận tốc hiện tại của một recyclerView (kể từ ngày 25.1.0) bằng phản xạ: Field viewFlingerField = recyclerView.getClass().getDeclaredField("mViewFlinger"); viewFlingerField.setAccessible(true); Object flinger = viewFlingerField.get(recyclerView); Field scrollerField = flinger.getClass().getDeclaredField("mScroller"); scrollerField.setAccessible(true); ScrollerCompat scroller = (ScrollerCompat) scrollerField.get(flinger); velocity = Math.signum(mVelocity) * Math.abs(scroller.getCurrVelocity());
Nicholas

2

Trong trường hợp của tôi, tôi đã gặp phải vấn đề trong đó việc vẩy RecyclerViewsẽ không di chuyển trơn tru, khiến nó bị kẹt.

Đây là bởi vì, đối với một số lý do, tôi đã quên rằng tôi đã đưa tôi RecyclerViewtrong mộtNestedScrollView .

Đó là một sai lầm ngớ ngẩn, nhưng tôi phải mất một thời gian để tìm ra ...


1

Tôi thêm chế độ xem chiều cao 1dp bên trong AppBarLayout và sau đó nó hoạt động tốt hơn nhiều. Đây là cách bố trí của tôi.

  <android.support.design.widget.CoordinatorLayout 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"
android:background="@android:color/white"
tools:context="com.spof.spof.app.UserBeachesActivity">

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.v7.widget.Toolbar
        android:id="@+id/user_beaches_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_alignParentTop="true"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/WhiteTextToolBar"
        app:layout_scrollFlags="scroll|enterAlways" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp" />
</android.support.design.widget.AppBarLayout>


<android.support.v7.widget.RecyclerView
    android:id="@+id/user_beaches_rv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />


Nó chỉ hoạt động nếu bạn cuộn lên. Không phải khi bạn cuộn xuống mặc dù
Arthur

Đối với tôi hoạt động tốt ở cả hai hướng. Bạn đã thêm chế độ xem 1dp bên trong appbarlayout chưa?. Tôi chỉ thử nghiệm nó trong android kẹo mút và kitkat.
Jachumbelechao Unto Mantekilla

Chà, tôi cũng đang sử dụng CollapsingToolbarLayout, nó bao bọc thanh công cụ. Tôi đặt chế độ xem 1dp bên trong đó. Nó giống như AppBarLayout này -> CollapsingToolbarLayout -> Thanh công cụ + Chế độ xem 1dp
Arthur

Tôi không biết nếu nó hoạt động tốt với CollapsingToolbarLayout. Tôi chỉ thử nghiệm với mã này. Bạn đã thử đặt chế độ xem 1dp bên ngoài CollapsingToolbarLayout chưa?
Jachumbelechao Unto Mantekilla

Đúng. Cuộn lên hoạt động, cuộn xuống không mở rộng thanh công cụ.
Arthur

1

Đã có một số giải pháp khá phổ biến ở đây nhưng sau khi chơi với chúng, tôi đã nghĩ ra một giải pháp khá đơn giản, phù hợp với tôi. Giải pháp của tôi cũng đảm bảo rằng AppBarLayoutchỉ được mở rộng khi nội dung có thể cuộn đạt đến đỉnh, một lợi thế so với các giải pháp khác ở đây.

private int mScrolled;
private int mPreviousDy;
private AppBarLayout mAppBar;

myRecyclerView.addOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            mScrolled += dy;
            // scrolled to the top with a little more velocity than a slow scroll e.g. flick/fling.
            // Adjust 10 (vertical change of event) as you feel fit for you requirement
            if(mScrolled == 0 && dy < -10 && mPrevDy < 0) {
                mAppBar.setExpanded(true, true);
            }
            mPreviousDy = dy;
    });

mPrevDy là gì
ARR.s

1

Câu trả lời được chấp nhận không có tác dụng với tôi vì tôi đã RecyclerViewở bên trong a SwipeRefreshLayoutvà a ViewPager. Đây là phiên bản cải tiến tìm kiếm RecyclerViewtrong cấu trúc phân cấp và sẽ hoạt động cho mọi bố cục:

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) {
            RecyclerView recycler = findRecycler((ViewGroup) target);
            if (recycler != null){
                target = recycler;
            }
        }
        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;
    }

    @Nullable
    private RecyclerView findRecycler(ViewGroup container){
        for (int i = 0; i < container.getChildCount(); i++) {
            View childAt = container.getChildAt(i);
            if (childAt instanceof RecyclerView){
                return (RecyclerView) childAt;
            }
            if (childAt instanceof ViewGroup){
                return findRecycler((ViewGroup) childAt);
            }
        }
        return null;
    }
}

1

Trả lời: Nó đã được sửa trong thư viện hỗ trợ v26

nhưng v26 có một số vấn đề trong flelling. Đôi khi, AppBar bị trả lại một lần nữa ngay cả khi fling không quá khó.

Làm cách nào để xóa hiệu ứng nảy trên appbar?

Nếu bạn gặp phải vấn đề tương tự khi cập nhật lên hỗ trợ v26, đây là tóm tắt câu trả lời này .

Giải pháp : Mở rộng Hành vi mặc định của AppBar và chặn cuộc gọi cho AppBar.Behavior's onNestedPreScroll () và onNestedScroll () khi AppBar bị chạm trong khi NestedScroll chưa dừng.


0

Julian Os đã đúng.

Câu trả lời của Manolo Garcia không hoạt động nếu recyclerview nằm dưới ngưỡng và cuộn. Bạn phải so sánh các offsetrecyclerview và velocity to the distance, không phải vị trí mục.

Tôi đã tạo phiên bản java bằng cách tham khảo mã kotlin của julian và trừ đi sự phản chiếu.

public final class FlingBehavior extends AppBarLayout.Behavior {

    private boolean isPositive;

    private float mFlingFriction = ViewConfiguration.getScrollFriction();

    private float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
    private final float INFLEXION = 0.35f;
    private float mPhysicalCoeff;

    public FlingBehavior(){
        init();
    }

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

    private void init(){
        final float ppi = BaseApplication.getInstance().getResources().getDisplayMetrics().density * 160.0f;
        mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * ppi
                * 0.84f; // look and feel tuning
    }

    @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) {
            RecyclerView recyclerView = (RecyclerView) target;

            double distance = getFlingDistance((int) velocityY);
            if (distance < recyclerView.computeVerticalScrollOffset()) {
                consumed = true;
            } else {
                consumed = false;
            }
        }
        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;
    }

    public double getFlingDistance(int velocity){
        final double l = getSplineDeceleration(velocity);
        final double decelMinusOne = DECELERATION_RATE - 1.0;
        return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
    }

    private double getSplineDeceleration(int velocity) {
        return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
    }

}

không thể resloveBaseApplication
ARR.s

@ ARR.s xin lỗi, bạn chỉ cần thay thế nó bối cảnh của bạn như dưới đây.
정성민

Your_CONTEXT.getResource (). GetDisplayMetrics (). Mật độ * 160.0f;
정성민


0

Với tham chiếu đến trình theo dõi sự cố của Google , nó đã được sửa với phiên bản thư viện hỗ trợ Android 26.0.0-beta2

Vui lòng cập nhật thư viện hỗ trợ Android phiên bản 26.0.0-beta2.

Nếu bất kỳ vấn đề nào vẫn còn, vui lòng báo cáo tại Google theo dõi vấn đề họ sẽ mở lại để kiểm tra.


0

Thêm một câu trả lời khác ở đây vì những câu hỏi trên không đáp ứng hoàn toàn nhu cầu của tôi hoặc không hoạt động tốt. Điều này một phần dựa trên ý tưởng lan truyền ở đây.

Vậy cái này làm gì?

Kịch bản đi xuống: Nếu AppBarLayout bị sập, nó cho phép RecyclerView tự bay mà không làm gì cả. Mặt khác, nó thu gọn AppBarLayout và ngăn RecyclerView thực hiện thao tác. Ngay khi nó bị sập (đến mức mà nhu cầu vận tốc đã cho) và nếu có vận tốc còn lại, RecyclerView sẽ bị ném với vận tốc ban đầu trừ đi những gì AppBarLayout vừa tiêu thụ bị sụp đổ.

Kịch bản hướng lên trên: Nếu độ lệch cuộn của RecyclerView không bằng 0, nó sẽ bị rung với vận tốc ban đầu. Ngay khi kết thúc và nếu vẫn còn vận tốc (tức là RecyclerView được cuộn đến vị trí 0), AppBarLayout được mở rộng đến điểm mà vận tốc ban đầu trừ đi nhu cầu vừa tiêu thụ. Mặt khác, AppBarLayout được mở rộng đến điểm mà vận tốc ban đầu yêu cầu.

AFAIK, đây là hành vi dự định.

Có rất nhiều phản ánh liên quan, và nó khá tùy chỉnh. Không có vấn đề được tìm thấy mặc dù. Nó cũng được viết bằng Kotlin, nhưng hiểu nó sẽ không có vấn đề gì. Bạn có thể sử dụng plugin IntelliJ Kotlin để biên dịch nó thành mã byte -> và dịch ngược nó về Java. Để sử dụng nó, hãy đặt nó trong gói android.support.v7.widget và đặt nó làm hành vi của Trình điều phối của AppBarLayout.LayoutParams (hoặc thêm hàm tạo của xml hoặc một cái gì đó)

/*
 * Copyright 2017 Julian Ostarek
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.support.v7.widget

import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.support.v4.widget.ScrollerCompat
import android.view.View
import android.widget.OverScroller

class SmoothScrollBehavior(recyclerView: RecyclerView) : AppBarLayout.Behavior() {
    // We're using this SplineOverScroller from deep inside the RecyclerView to calculate the fling distances
    private val splineOverScroller: Any
    private var isPositive = false

    init {
        val scrollerCompat = RecyclerView.ViewFlinger::class.java.getDeclaredField("mScroller").apply {
            isAccessible = true
        }.get(recyclerView.mViewFlinger)
        val overScroller = ScrollerCompat::class.java.getDeclaredField("mScroller").apply {
            isAccessible = true
        }.get(scrollerCompat)
        splineOverScroller = OverScroller::class.java.getDeclaredField("mScrollerY").apply {
            isAccessible = true
        }.get(overScroller)
    }

    override fun onNestedFling(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout, target: View?, velocityX: Float, givenVelocity: Float, consumed: Boolean): Boolean {
        // Making sure the velocity has the correct sign (seems to be an issue)
        var velocityY: Float
        if (isPositive != givenVelocity > 0) {
            velocityY = givenVelocity * - 1
        } else velocityY = givenVelocity

        if (velocityY < 0) {
            // Decrement the velocity to the maximum velocity if necessary (in a negative sense)
            velocityY = Math.max(velocityY, - (target as RecyclerView).maxFlingVelocity.toFloat())

            val currentOffset = (target as RecyclerView).computeVerticalScrollOffset()
            if (currentOffset == 0) {
                super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, false)
                return true
            } else {
                val distance = getFlingDistance(velocityY.toInt()).toFloat()
                val remainingVelocity = - (distance - currentOffset) * (- velocityY / distance)
                if (remainingVelocity < 0) {
                    (target as RecyclerView).addOnScrollListener(object : RecyclerView.OnScrollListener() {
                        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                                recyclerView.post { recyclerView.removeOnScrollListener(this) }
                                if (recyclerView.computeVerticalScrollOffset() == 0) {
                                    super@SmoothScrollBehavior.onNestedFling(coordinatorLayout, child, target, velocityX, remainingVelocity, false)
                                }
                            }
                        }
                    })
                }
                return false
            }
        }
        // We're not getting here anyway, flings with positive velocity are handled in onNestedPreFling
        return false
    }

    override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout, target: View?, velocityX: Float, givenVelocity: Float): Boolean {
        // Making sure the velocity has the correct sign (seems to be an issue)
        var velocityY: Float
        if (isPositive != givenVelocity > 0) {
            velocityY = givenVelocity * - 1
        } else velocityY = givenVelocity

        if (velocityY > 0) {
            // Decrement to the maximum velocity if necessary
            velocityY = Math.min(velocityY, (target as RecyclerView).maxFlingVelocity.toFloat())

            val topBottomOffsetForScrollingSibling = AppBarLayout.Behavior::class.java.getDeclaredMethod("getTopBottomOffsetForScrollingSibling").apply {
                isAccessible = true
            }.invoke(this) as Int
            val isCollapsed = topBottomOffsetForScrollingSibling == - child.totalScrollRange

            // The AppBarlayout is collapsed, we'll let the RecyclerView handle the fling on its own
            if (isCollapsed)
                return false

            // The AppbarLayout is not collapsed, we'll calculate the remaining velocity, trigger the appbar to collapse and fling the RecyclerView manually (if necessary) as soon as that is done
            val distance = getFlingDistance(velocityY.toInt())
            val remainingVelocity = (distance - (child.totalScrollRange + topBottomOffsetForScrollingSibling)) * (velocityY / distance)

            if (remainingVelocity > 0) {
                (child as AppBarLayout).addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
                    override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
                        // The AppBarLayout is now collapsed
                        if (verticalOffset == - appBarLayout.totalScrollRange) {
                            (target as RecyclerView).mViewFlinger.fling(velocityX.toInt(), remainingVelocity.toInt())
                            appBarLayout.post { appBarLayout.removeOnOffsetChangedListener(this) }
                        }
                    }
                })
            }

            // Trigger the expansion of the AppBarLayout
            super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, false)
            // We don't let the RecyclerView fling already
            return true
        } else return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
    }

    override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout?, child: AppBarLayout?, target: View?, dx: Int, dy: Int, consumed: IntArray?) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed)
        isPositive = dy > 0
    }

    private fun getFlingDistance(velocity: Int): Double {
        return splineOverScroller::class.java.getDeclaredMethod("getSplineFlingDistance", Int::class.javaPrimitiveType).apply {
            isAccessible = true
        }.invoke(splineOverScroller, velocity) as Double
    }

}

Làm thế nào để thiết lập nó?
ARR.s

0

đây là giải pháp của tôi trong dự án của tôi
chỉ dừng mScroller khi nhận Action_Down

xml:

    <android.support.design.widget.AppBarLayout
        android:id="@+id/smooth_app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        app:elevation="0dp"
        app:layout_behavior="com.sogou.groupwenwen.view.topic.FixAppBarLayoutBehavior">

FixAppBarLayoutBehavior.java:

    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
        if (ev.getAction() == ACTION_DOWN) {
            Object scroller = getSuperSuperField(this, "mScroller");
            if (scroller != null && scroller instanceof OverScroller) {
                OverScroller overScroller = (OverScroller) scroller;
                overScroller.abortAnimation();
            }
        }

        return super.onInterceptTouchEvent(parent, child, ev);
    }

    private Object getSuperSuperField(Object paramClass, String paramString) {
        Field field = null;
        Object object = null;
        try {
            field = paramClass.getClass().getSuperclass().getSuperclass().getDeclaredField(paramString);
            field.setAccessible(true);
            object = field.get(paramClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }

//or check the raw file:
//https://github.com/shaopx/CoordinatorLayoutExample/blob/master/app/src/main/java/com/spx/coordinatorlayoutexample/util/FixAppBarLayoutBehavior.java

0

cho androidx

Nếu tệp kê khai của bạn có dòng android: phần cứngAccelerated = "false", hãy xóa nó.

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.