Погортання з RecyclerView + AppBarLayout


171

Я використовую новий CoordinatorLayout з AppBarLayout та CollapsingToolbarLayout. Внизу AppBarLayout у мене є RecyclerView зі списком вмісту.

Я переконався, що прокрутка перекидання працює на RecyclerView, коли я прокручую список вгору та вниз. Однак я також хотів би, щоб AppBarLayout плавно прокручував під час розширення.

Прокручуючи вгору, щоб розгорнути розкладку CollaspingToolbarLayout, прокрутка одразу припиняється, коли ви піднімаєте палець з екрана. Якщо ви швидко прокручуєте вгору рух, іноді CollapsingToolbarLayout також повторно руйнується. Така поведінка з RecyclerView, здається, функціонує набагато інакше, ніж при використанні NestedScrollView.

Я намагався встановити різні властивості прокрутки на перегляді рециркулятора, але мені не вдалося це зрозуміти.

Ось відео, яке показує деякі проблеми прокрутки. https://youtu.be/xMLKoJOsTAM

Ось приклад, що показує проблему з RecyclerView (CheeseDetailActivity). https://github.com/tylerjroach/cheesesquare

Ось оригінальний приклад, який використовує NestedScrollView від Chris Banes. https://github.com/chrisbanes/cheesesquare


У мене виникає така сама точна проблема (я використовую програму RecyclerView). Якщо ви подивитеся на список в магазині Google Play Play для будь-якого додатка, то, здається, він поводиться правильно, тож, безумовно, там є рішення ...
Aneem,

Ей, Анеем, я знаю, що це не найкраще рішення, але я почав експериментувати з цією бібліотекою: github.com/ksoichiro/Android-ObservableScrollView . Особливо в цій діяльності для досягнення потрібних мені результатів: FlexibleSpaceWithImageRecyclerViewActivity.java. Вибачте за неправильне написання свого імені перед редагуванням.
Автокоригування

2
Це ж питання тут, я закінчила уникати AppBarLayout.
Рено Серрато

Так. У кінцевому підсумку я отримав саме те, що мені потрібно з бібліотеки OvservableScrollView. Я впевнений, що це буде виправлено у майбутніх версіях.
tylerjroach

8
Флінг баггі, питання порушено (і прийнято).
Рено Серрато

Відповіді:


114

Відповідь Кирила Бояршинова була майже правильною.

Основна проблема полягає в тому, що RecyclerView іноді дає неправильний напрямок накиду, тому якщо ви додасте наступний код до його відповіді, він працює правильно:

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

Я сподіваюся, що це допомагає.


Ти врятував мій день! Здається, працює абсолютно чудово! Чому ваша відповідь не прийнята?
Zordid

9
якщо ви використовуєте SwipeRefreshLayout в якості батьків свого рециркулятора, просто додайте цей код: if (target instanceof SwipeRefreshLayout && velocityY < 0) { target = ((SwipeRefreshLayout) target).getChildAt(0); }раніше if (target instanceof RecyclerView && velocityY < 0) {
LucasFM

1
+ 1 Аналізуючи це виправлення, я не розумію, чому Google ще не виправив це. Код здається досить простим.
Гастон Флорес

3
Привіт, як досягти того ж за допомогою appbarlayout та Nestedscrollview ... Дякуємо заздалегідь ..
Гаррі Шарма

1
Для мене це не працювало = / До речі, вам не потрібно переміщувати клас у пакет підтримки, щоб досягти цього, ви можете зареєструвати DragCallback в конструкторі.
Augusto Carmo

69

Здається, це v23оновлення ще не виправлено.

Я знайшов свого роду хак, щоб виправити це, кидаючи вниз. Трюк полягає в тому, щоб відновити події fling, якщо головна дитина ScrollingView знаходиться близько до початку даних в Adapter.

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

Використовуйте його у своєму макеті так:

 <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: Відновлення подій Fling тепер базується на verticalScrollOffsetкількості предметів, розміщених зверху RecyclerView.

EDIT2: Перевірте ціль як ScrollingViewекземпляр інтерфейсу замість RecyclerView. І те RecyclerViewй NestedScrollingViewінше.


Отримання типів рядків заборонено для помилки
layout_behavior

Я перевірив це і працює краще людини! але яка мета TOP_CHILD_FLING_THRESHOLD? і чому це 3?
Julio_oa

@Julio_oa TOP_CHILD_FLING_THRESHOLD означає, що подія Fling буде відновлено, якщо подання рециркулятора прокручується до елемента, позиція якого знаходиться нижче цього порогового значення. До речі, я оновив відповідь на використання, verticalScrollOffsetяка є більш загальною. Тепер подія Fling буде відновлено, коли recyclerViewпрокручується доверху.
Кирило Бояршинов

Привіт, як досягти того ж за допомогою appbarlayout та Nestedscrollview ... Дякуємо заздалегідь ..
Гаррі Шарма

2
@Змінна зміна target instanceof RecyclerViewна target instanceof NestedScrollViewабо більше для загального випадку target instanceof ScrollingView. Я оновив відповідь.
Кирило Бояршинов

15

Я знайшов виправлення, застосувавши OnScrollingListener до recilerView. зараз це працює дуже добре. Проблема полягає в тому, що рециркулятор за умови неправильного споживаного значення, а поведінка не знає, коли рециркулятор прокручується до верху.

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

Дякуємо за ваш пост Я спробував усі відповіді на цій сторінці, і на мій досвід це найефективніша відповідь. Але RecylerView в моєму макеті прокручується внутрішньо, перш ніж AppBarLayout не прокрутить екран, якщо я не прокручую RecyclerView з достатньою силою. Іншими словами, коли я прокручую RecyclerView з достатньою силою, AppBar прокручується з екрана без прокрутки RecyclerView внутрішньо, але коли я не прокручую RecyclerView з достатньою силою, RecyclerView прокручується внутрішньо, перш ніж AppbarLayout не прокрутить екран. Чи знаєте ви, що це викликає?
Міка Сіммонс

Рециркулятор все ще отримує сенсорні події, тому він все ще прокручується, поведінка наNestedFling буде оживляти одночасно прокручування панелі додатків. Можливо, ви можете спробувати змінити функцію onInterceptTouch, щоб змінити це. Для мене поточна поведінка прийнятна з того, що я бачу. (не впевнений, чи бачимо ми те саме)
Мак Сінг,

@MakSing це дуже корисно з налаштуванням CoordinatorLayoutі ViewPagerдуже дякую за це найочікуваніше рішення. Будь ласка, напишіть GIST для того ж, щоб інші дияволи могли також отримати від нього користь. Я також ділюсь цим рішенням. Знову дякую.
Нітін Місра

1
@MakSing Від усіх рішень, це найкраще працює для мене. Я скоригував швидкість, передану onNestedFling, трохи швидкістю * 0.6f ..., здається, дає приємніший потік до неї.
saberrider

Працює для мене. @MakSing Чи в методі onScroll потрібно зателефонувати наNestedFling AppBarLayout.Behavior, а не RecyclerViewAppBarBehavior? Здається мені трохи дивно.
Антон Малмигін

13

Це було виправлено з моменту підтримки 26.0.0.

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

2
Це потрібно рухати вгору. Це описано тут, якщо когось цікавлять деталі.
Кріс Дінон

1
Тепер, здається, виникла проблема з панеллю стану, коли при прокручуванні вниз рядок стану трохи знижується з прокруткою ... її супер дратує!
коробка

2
@Xiaozou Я використовую 26.1.0 і все ще маю проблеми з флінг. Швидкий кидок іноді призводить до протилежного руху (Швидкість руху протилежна / неправильна, як це можна побачити в методі onNestedFling). Відтворено його у Xiaomi Redmi Note 3 та Galaxy S3
dor506

@ Dor506 stackoverflow.com/a/47298312/782870 Я не впевнений , що якщо у нас є один і той же питання , коли ви говорите , протилежний результат руху. Але я опублікував відповідь тут. Сподіваюся, це допомагає :)
vida



2

Це моя макет і прокрутка. Вона працює як слід.

<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

Моє рішення поки що засноване на відповідях Мак Сінга та Маноло Гарсія .

Це не зовсім ідеально. На даний момент я не знаю, як перерахувати валідну швидкість, щоб уникнути дивного ефекту: панель додатків може розширюватися швидше, ніж швидкість прокрутки. Але стан із розширеною панеллю додатків та прокрученим видом рециркулятора неможливо досягти.

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

    }

}

Ви можете отримати поточну швидкість утилізатора перегляду (станом на 25.1.0), використовуючи відображення: 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());
Микола

2

У моєму випадку я отримував питання про те, коли перекидання RecyclerViewне буде прокручувати його гладко, змушуючи застрягти.

Це сталося через те, що з якоїсь - то причини, я зовсім забув , що я кладу RecyclerViewвNestedScrollView .

Це дурна помилка, але мені знадобилося певний час, щоб зрозуміти це ...


1

Я додаю вигляд висоти 1dp всередині AppBarLayout, тоді він працює набагато краще. Це моя верстка.

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


Він працює лише в тому випадку, якщо ви прокрутите вгору. Не коли ви прокручуєте вниз
Артур

Для мене добре працює в обох напрямках. Ви додали подання 1dp всередині панелі програм ?. Я протестував його лише на льодяниках і андроїдах.
Jachumbelechao Unto Mantekilla

Ну, я також використовую CollapsingToolbarLayout, який обгортає панель інструментів. Я помістив 1dp подання всередині цього. Це якось, як це AppBarLayout -> CollapsingToolbarLayout -> Панель інструментів + ​​1dp перегляд
Артур

Я не знаю, чи добре працює це з програмою CollapsingToolbarLayout. Я протестував лише з цим кодом. Чи намагалися ви поставити 1dp подання за межами CollapsingToolbarLayout?
Jachumbelechao Unto Mantekilla

Так. Прокрутка вгору працює, прокручування вниз не розширює панель інструментів.
Артур

1

Тут вже є досить популярні рішення, але, граючи з ними, я придумав досить просте рішення, яке добре працювало для мене. Моє рішення також гарантує, що AppBarLayoutрозширюється лише тоді, коли вміст, що прокручується, сягає вершини, перевага перед іншими рішеннями тут.

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
ARR.s

1

Прийнята відповідь не спрацювала для мене, тому що я мав RecyclerViewвсередині а SwipeRefreshLayoutта а ViewPager. Це вдосконалена версія, яка шукає RecyclerViewв ієрархії і повинна працювати для будь-якої верстки:

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

Відповідь: Фіксовано в бібліотеці підтримки v26

але v26 має певну проблему у флінгу. Іноді AppBar відскакує знову, навіть якщо літати не надто важко.

Як видалити ефект підстрибування на панелі додатків?

Якщо під час оновлення до підтримки v26 ви стикаєтесь з тією ж проблемою, ось короткий опис цієї відповіді .

Рішення : Розширіть поведінку за замовчуванням AppBar і заблокуйте виклик для AppBar.Behavior onNestedPreScroll () та onNestedScroll (), коли доторкнуться до AppBar, поки NestedScroll ще не зупинився.


0

Джуліан Ос має рацію.

Відповідь Маноло Гарсія не працює, якщо перегляд рециркулятора знаходиться нижче порогу і прокручується. Ви повинні порівняти перегляд offsetрециркулятора та положення velocity to the distance, а не позицію товару.

Я створив версію java, посилаючись на код Котліна Джуліана і віднімаючи відбиття.

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

}

не можу продовжувати роботуBaseApplication
ARR.s

@ ARR., вибачте, ви просто заміните його своїм контекстом, як показано нижче.
정성민

YOUR_CONTEXT.getResources (). GetDisplayMetrics (). Щільність * 160.0f;
정성민


0

З посиланням на трекер випуску Google , він був виправлений з Android 26.0.0-beta2 версія бібліотеки підтримки

Оновіть версію бібліотеки підтримки Android версії 26.0.0-beta2

Якщо будь-яка проблема не зникає, повідомте про це у трекері випуску Google, який вони знову відкриють для розгляду.


0

Додавши сюди ще одну відповідь, оскільки вищезазначені або не повністю задовольнили мої потреби, або не спрацювали дуже добре. Частково це засноване на поширених тут ідеях.

Отже, що це робить?

Сценарій вниз перекидається: Якщо AppBarLayout згортається, він дозволяє RecyclerView самостійно перекидатися, не роблячи нічого. В іншому випадку він згортає AppBarLayout і перешкоджає RecyclerView робити його розгортання. Як тільки він згортається (аж до того, що вимагає задана швидкість), і якщо залишилася швидкість, RecyclerView отримує посилання з початковою швидкістю мінус ті, що AppBarLayout щойно витрачається в руйнуванні.

Сценарій вгору перекидається: Якщо зсув прокрутки RecyclerView не дорівнює нулю, він запускається з початковою швидкістю. Як тільки це закінчено, і якщо залишилася швидкість (тобто RecyclerView прокручується до позиції 0), AppBarLayout розширюється до того, що початкова швидкість мінус щойно витрачені вимоги. В іншому випадку AppBarLayout розширюється до того, що вимагає початкова швидкість.

AFAIK, це поведінка, яка не відповідає.

Тут задіяно багато роздумів, і це досить звичай. Однак проблем поки не знайдено. Це також написано в Котліні, але розуміння цього не повинно бути проблемою. Ви можете використовувати плагін IntelliJ Kotlin, щоб компілювати його в байт-код -> і декомпілювати його назад на Java. Щоб використовувати його, помістіть його в пакет android.support.v7.widget та встановіть його як поведінку в коді Координатора AppBarLayout.LayoutParams (або додайте конструктор xml, що застосовується, або щось таке)

/*
 * 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
    }

}

Як його встановити?
ARR.s

0

це моє рішення в моєму проекті.
просто зупиніть mScroller, коли отримаєте 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

для androidx,

Якщо ваш файл маніфесту має андроїд: hardwareAccelerated = "помилковий" рядок, видаліть його.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.