Ich benutze das neue CoordinatorLayout mit AppBarLayout und CollapsingToolbarLayout. Unter AppBarLayout habe ich eine RecyclerView mit einer Liste von Inhalten.
Ich habe bestätigt, dass Flingscrolling auf RecyclerView funktioniert, wenn ich in der Liste nach oben und unten scrolle. Ich möchte jedoch auch, dass AppBarLayout während der Erweiterung reibungslos scrollt.
Wenn Sie nach oben scrollen, um das CollaspingToolbarLayout zu erweitern, stoppt das Scrollen sofort, sobald Sie den Finger vom Bildschirm nehmen. Wenn Sie in einer schnellen Bewegung nach oben scrollen, wird CollapsingToolbarLayout manchmal auch wieder reduziert. Dieses Verhalten mit der RecyclerView scheint anders zu funktionieren als bei Verwendung einer NestedScrollView.
Ich habe versucht, verschiedene Scroll-Eigenschaften im Recycling-Bereich festzulegen, konnte dies jedoch nicht herausfinden.
Hier ist ein Video, das einige der Probleme beim Scrollen zeigt . https://youtu.be/xMLKoJOsTAM
Hier ist ein Beispiel, das das Problem mit der RecyclerView (CheeseDetailActivity) zeigt https://github.com/tylerjroach/cheesesquare
Hier ist das ursprüngliche Beispiel, das eine NestedScrollView von Chris Banes verwendet https://github.com/chrisbanes/cheesesquare
Die Antwort von Kirill Boyarshinov war fast richtig.
Das Hauptproblem ist, dass die RecyclerView manchmal eine falsche Fling-Richtung gibt. Wenn Sie der Antwort den folgenden Code hinzufügen, funktioniert sie richtig:
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;
}
}
Ich hoffe, dass das hilft.
Es scheint, dass v23
update es noch nicht behoben hat.
Ich habe eine Art Hack gefunden, um es mit einem Abwurf zu beheben. Der Trick besteht darin, das Fling-Ereignis erneut zu starten, wenn sich das oberste untergeordnete Element von ScrollingView nahe am Anfang der Daten in Adapter befindet.
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);
}
}
Verwenden Sie es in Ihrem Layout so:
<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: Die Wiederaufnahme des Fling-Ereignisses basiert jetzt auf verticalScrollOffset
anstelle der Anzahl der Elemente auf RecyclerView
.
EDIT2: Ziel als ScrollingView
Schnittstelleninstanz prüfen statt RecyclerView
. Sowohl RecyclerView
als auch NestedScrollingView
implementieren es.
Ich habe das Update gefunden, indem ich OnScrollingListener auf die RecyclerView angewendet habe. jetzt funktioniert es sehr gut. Das Problem ist, dass der Recycling-Bericht den falschen Verbrauchswert angegeben hat und das Verhalten nicht weiß, wann das Recycling-Fenster nach oben gerollt wird.
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);
}
}
}
}
Es wurde seit dem Supportdesign 26.0.0 behoben.
compile 'com.Android.support:design:26.0.0'
Dies ist eine reibungslose Version von Google Support Design AppBarLayout. Wenn Sie AppBarLayout verwenden, wissen Sie, dass es ein Problem mit Fling gibt.
compile "me.henrytao:smooth-app-bar-layout:<latest-version>"
Siehe Bibliothek hier .. https://github.com/henrytao-me/smooth-app-bar-layout
Es ist ein Fehler im Recycler. Es soll in Version 23.1.0 behoben sein.
look https://code.google.com/p/Android/issues/detail?id=177729
Dies ist mein Layout und die Schriftrolle. Es funktioniert wie es soll.
<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>
In meinem Fall bekam ich das Problem, dass das Schleudern der RecyclerView
es nicht reibungslos scrollen würde, sodass es feststeckte.
Dies war, weil aus irgendeinem Grund ich hatte vergessen, dass ich meine RecyclerView
in eine NestedScrollView
gegeben hatte.
Es ist ein dummer Fehler, aber ich habe eine Weile gebraucht, um es herauszufinden ...
Meine bisherige Lösung basiert auf Mak Sing und Manolo Garcia Antworten.
Es ist nicht vollkommen perfekt. Im Moment weiß ich nicht, wie ich eine Validengeschwindigkeit neu berechnen kann, um einen seltsamen Effekt zu vermeiden: Die Appbar kann sich schneller als die Bildlaufgeschwindigkeit ausdehnen. Der Status mit einer erweiterten Appbar und einer gescrollten Recycler-Ansicht kann jedoch nicht erreicht werden.
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;
}
}
}
Answer: In Support Library V26 ist ein Problem behoben
v26 hat jedoch ein problem beim schleudern. AppBar springt manchmal wieder zurück, auch wenn das Fling nicht zu hart ist.
Wie entferne ich den Bouncing-Effekt in Appbar?
Wenn dasselbe Problem auftritt, wenn Sie ein Update auf die Unterstützung von v26 durchführen, finden Sie hier die Zusammenfassung dieser Antwort .
Solution: Erweitern Sie das Standardverhalten von AppBar und blockieren Sie den Aufruf für AppBar.Behavior's onNestedPreScroll () und onNestedScroll (), wenn AppBar wird berührt, während NestedScroll noch nicht aufgehört hat.
Die akzeptierte Antwort funktionierte nicht für mich, da ich RecyclerView
in einer SwipeRefreshLayout
und einer ViewPager
hatte. Dies ist die verbesserte Version, die eine RecyclerView
in der Hierarchie sucht und für jedes Layout funktionieren sollte:
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;
}
}
Hier sind schon einige ziemlich beliebte Lösungen, aber nachdem ich mit ihnen gespielt habe, habe ich eine etwas einfachere Lösung gefunden, die für mich gut funktionierte. Meine Lösung stellt auch sicher, dass die AppBarLayout
nur erweitert wird, wenn der scrollbare Inhalt die Spitze erreicht. Dies ist ein Vorteil gegenüber anderen Lösungen.
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;
});
Ich füge eine Ansicht von 1dp Höhe im AppBarLayout hinzu und funktioniert dann viel besser. Das ist mein Layout.
<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" />
Julian Os ist richtig.
Manolo Garcias Antwort funktioniert nicht, wenn die Recyclingübersicht unter dem Schwellenwert liegt und scrollt. Sie müssen die offset
des Recyclingblatts und den velocity to the distance
vergleichen, nicht die Artikelposition.
Ich habe Java-Version erstellt, indem ich mich auf julians Kotlin-Code bezog und die Reflexion subtrahiere.
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));
}
}
Ich habe das Update von Eniz Bilgin _ https://stackoverflow.com/a/45090239/7639018 gefunden. _
Das Problem wurde mit den Bibliotheken in diesem Repository behoben.
( https://developer.Android.com/topic/libraries/support-library/setup.html )
allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
}
Eine weitere Antwort hier hinzuzufügen, da die oben genannten meine Anforderungen nicht vollständig erfüllt haben oder nicht gut funktionierten. Dieses basiert zum Teil auf den hier verbreiteten Ideen.
Also, was macht dieser?
Abwärtsszenario des Szenarios: Wenn das AppBarLayout reduziert ist, kann RecyclerView von sich aus gestartet werden, ohne etwas zu tun. Andernfalls wird das AppBarLayout ausgeblendet und der RecyclerView wird nicht ausgeführt. Sobald es zusammengebrochen ist (bis zu dem Punkt, an dem die vorgegebene Geschwindigkeit erforderlich ist), und wenn noch Geschwindigkeit vorhanden ist, wird RecyclerView mit der ursprünglichen Geschwindigkeit abzüglich des Werts geworfen, den das AppBarLayout gerade verbraucht hat.
Szenario nach oben: Wenn der Scroll-Offset der RecyclerView nicht Null ist, wird er mit der Originalgeschwindigkeit geworfen. Sobald dies beendet ist und noch Geschwindigkeit vorhanden ist (dh die RecyclerView wurde auf Position 0 gescrollt), wird das AppBarLayout bis zu dem Punkt erweitert, an dem die ursprüngliche Geschwindigkeit abzüglich der gerade verbrauchten Anforderungen erforderlich ist. __ Ansonsten wird das AppBarLayout erweitert bis zu dem Punkt, den die ursprüngliche Geschwindigkeit erfordert.
AFAIK, das ist das eigensinnige Verhalten.
Es gibt viele Überlegungen, und es ist ziemlich üblich. Es wurden jedoch noch keine Probleme gefunden . Es ist auch in Kotlin geschrieben, aber das Verständnis sollte kein Problem sein ..__ Sie können das IntelliJ Kotlin-Plugin verwenden, um es zu Bytecode -> zu kompilieren und es zurück nach Java zu dekompilieren. Um es zu verwenden, platzieren Sie es im Paket Android.support.v7.widget und legen Sie es als CoordinatorLayout.LayoutParams-Verhalten von AppBarLayout im Code fest (oder fügen Sie den anwendbaren XML-Konstruktor oder etwas anderes hinzu)
/*
* 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) {
[email protected](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
}
}
In Bezug auf Google Problem-Tracker wurde es mit der Android 26.0.0-beta2-Version der Support-Bibliothek behoben
Bitte aktualisieren Sie Ihre Android Support Library-Version 26.0.0-beta2.
Wenn ein Problem weiterhin besteht, melden Sie sich unter Google Problem-Tracker es wird erneut geöffnet, um es zu überprüfen.
das ist meine Lösung in meinem Projekt.
Stoppen Sie einfach den mScroller, wenn Sie Action_Down erhalten
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