Die Symbolleiste in AppBarLayout ist scrollbar, obwohl RecyclerView nicht genügend Inhalt zum Scrollen hat
Lesezeit: 14 Minuten
eiki
Ist es wirklich beabsichtigt, dass die Toolbar in einem AppBarLayout scrollbar ist, obwohl der Hauptcontainer mit dem „appbar_scrolling_view_behavior“ nicht genug Inhalt hat, um wirklich scrollen zu können?
Was ich bisher getestet habe:
Wenn ich ein NestedScrollView (mit “wrap_content”-Attribut) als Hauptcontainer und ein TextView als untergeordnetes Element verwende, funktioniert das AppBarLayout ordnungsgemäß und scrollt nicht.
Wenn ich jedoch eine RecyclerView mit nur wenigen Einträgen und dem Attribut “wrap_content” verwende (damit kein Scrollen erforderlich ist), ist die Symbolleiste im AppBarLayout scrollbar, obwohl die RecyclerView nie ein Scroll-Ereignis erhält (getestet mit einem OnScrollChangeListener ).
Mit folgendem Effekt, dass die Symbolleiste scrollbar ist, obwohl es nicht notwendig ist:
Ich habe auch einen Weg gefunden, damit umzugehen, indem ich prüfe, ob alle RecyclerView-Elemente sichtbar sind, und die Methode setNestedScrollingEnabled() von RecyclerView verwende.
Trotzdem scheint es mir eher ein Bug zu sein, als beabsichtigt. Irgendwelche Meinungen? 😀
EDIT #1:
Für Leute, die an meiner aktuellen Lösung interessiert sind, musste ich die setNestedScrollingEnabled() -Logik in die postDelayed() -Methode eines Handlers mit 5 ms Verzögerung einfügen, da der LayoutManager beim Aufrufen der Methoden immer -1 zurückgab, um dies herauszufinden ob das erste und das letzte Element sichtbar sind.
Ich verwende diesen Code in der Methode onStart() (nachdem meine RecyclerView initialisiert wurde) und jedes Mal, wenn eine Inhaltsänderung der RecyclerView auftritt.
final LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//no items in the RecyclerView
if (mRecyclerView.getAdapter().getItemCount() == 0)
mRecyclerView.setNestedScrollingEnabled(false);
//if the first and the last item is visible
else if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0
&& layoutManager.findLastCompletelyVisibleItemPosition() == mRecyclerView.getAdapter().getItemCount() - 1)
mRecyclerView.setNestedScrollingEnabled(false);
else
mRecyclerView.setNestedScrollingEnabled(true);
}
}, 5);
EDIT #2:
Ich habe gerade mit einer neuen App herumgespielt und es scheint, dass dieses (unbeabsichtigte) Verhalten in der Support-Bibliotheksversion 23.3.0 (oder sogar früher) behoben wurde. Somit sind keine Workarounds mehr erforderlich!
Meine Meinung ist, dass es beabsichtigt ist. Dies wurde hier mehrfach gefragt, und wenn dies ein Fehler wäre, hätten sie ihn schon früher behoben – die Designbibliothek ist nicht mehr so jung.
– natürlich
7. September 2015 um 17:39 Uhr
Danke für die Antwort. Da ich die genannten Antworten/Diskussionen nicht selbst gefunden habe, könnten Sie bitte zumindest eine Ihrer Quellen posten.
– eiki
13. September 2015 um 11:21 Uhr
Es ist kein Fehler, alle Ereignisse in einer viewGroup werden auf diese Weise behandelt. Da Ihr recyclerview ein untergeordnetes Element von coordinatorLayout ist, wird es bei jedem Generieren des Ereignisses zuerst auf das übergeordnete Element überprüft, und wenn das übergeordnete Element nicht interessiert ist, wird es nur an das untergeordnete Element weitergegeben.
– Sulabh Deep Puri
2. November 2015 um 6:41 Uhr
Ich habe in der Material Design-Spezifikation keinen Hinweis darauf gefunden, aber basierend auf dem Wie Posteingang von Gmail und Google Play (Meine Wunschliste) funktioniert derzeit. Es scheint, dass das richtige Verhalten darin besteht, die App-Leiste nur wegzuscrollen, wenn genügend Inhalt zum Scrollen vorhanden ist.
– joelpet
1. Februar 2016 um 13:13 Uhr
Überprüfen Sie stackoverflow.com/a/61941446/5745574
– Vipul Kumar
21. Mai 2020 um 18:39 Uhr
Benutzer3623735
Bearbeiten 2:
Es stellt sich heraus, dass die einzige Möglichkeit, um sicherzustellen, dass die Symbolleiste nicht scrollbar ist, wenn RecyclerView nicht scrollbar ist, darin besteht, setScrollFlags programmgesteuert zu setzen, wodurch überprüft werden muss, ob RecyclerView scrollbar ist. Diese Überprüfung muss bei jeder Änderung des Adapters durchgeführt werden.
Schnittstelle zur Kommunikation mit der Aktivität:
public interface LayoutController {
void enableScroll();
void disableScroll();
}
Hauptaktivität:
public class MainActivity extends AppCompatActivity implements
LayoutController {
private CollapsingToolbarLayout collapsingToolbarLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
collapsingToolbarLayout =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
final FragmentManager manager = getSupportFragmentManager();
final Fragment fragment = new CheeseListFragment();
manager.beginTransaction()
.replace(R.id.root_content, fragment)
.commit();
}
@Override
public void enableScroll() {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
| AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
);
collapsingToolbarLayout.setLayoutParams(params);
}
@Override
public void disableScroll() {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
collapsingToolbarLayout.setLayoutParams(params);
}
}
Benötigen Sie einen postDelayed, um sicherzustellen, dass RecyclerView-Kinder für Berechnungen bereit sind
Bearbeiten:
Sie sollten CollapsingToolbarLayout verwenden, um das Verhalten zu steuern.
Durch das direkte Hinzufügen einer Toolbar zu einem AppBarLayout erhalten Sie Zugriff auf die Scroll-Flags enterAlwaysCollapsed und exitUntilCollapsed, aber nicht auf die detaillierte Kontrolle darüber, wie verschiedene Elemente auf das Reduzieren reagieren.
[…] setup verwendet app:layout_collapseMode=”pin” von CollapsingToolbarLayout, um sicherzustellen, dass die Symbolleiste selbst am oberen Rand des Bildschirms angeheftet bleibt, während die Ansicht minimiert wird.http://android-developers.blogspot.com.tr/2015/05/android-design-support-library.html
Es funktioniert nicht für mich. Hast du es getestet? Ich vermute nein.
– eiki
18. Februar 2016 um 13:39 Uhr
Ja, ich verwende es in meiner eigenen App. Aber ich habe gerade den Unterschied bemerkt, denke ich. Siehe meine Bearbeitung. Sie sollten die Toolbar mit CollapsingToolbarLayout umschließen und die scrollFlags für diese Ansicht festlegen und sicherstellen, dass die Toolbar auf „Pin“ festgelegt ist.
– Benutzer3623735
18. Februar 2016 um 16:16 Uhr
Hey, erstens entschuldige ich mich dafür, dass ich völlig missverstanden habe, was du zu tun versuchst, und deine Zeit verschwendet hast, indem du eine falsche Antwort gegeben hast. Zweitens, überprüfen Sie bitte mein edit2, es macht genau das, was Sie versuchen zu tun. Lassen Sie mich wissen, wenn Sie Fragen haben.
– Benutzer3623735
18. Februar 2016 um 19:43 Uhr
Die Gesamtidee macht Sinn, aber es gibt ein Problem. Wenn die Recycleransicht wann nicht scrollen würde nimmt den gesamten vertikalen Raum des CoordinatorLayout ein aber wann nimmt den gesamten Platz ein, der von AppBarLayout + CollapsingToolbarLayout übrig bleibt, dies bricht: Die RecyclerView würde innerhalb des verbleibenden Platzes gut scrollen, aber das AppBarLayout wird nicht zusammenbrechen. Um dies zu beheben, sollten wir irgendwie “würde vertikal innerhalb des vertikalen Raums scrollen, der übrig bleibt, wenn CollapsingToolbarLayout vollständig erweitert ist” aktivieren.
– Droid256
17. Oktober 2018 um 16:34 Uhr
Emirua
Also, richtige Anerkennung, diese Antwort hat es fast für mich gelöst https://stackoverflow.com/a/32923226/5050087. Aber da die Symbolleiste nicht angezeigt wurde, wenn Sie tatsächlich eine scrollbare Recycleransicht hatten und ihr letztes Element sichtbar war (es würde die Symbolleiste nicht beim ersten Scrollen nach oben zeigen), habe ich mich entschieden, sie zu ändern und anzupassen, um eine einfachere Implementierung und Dynamik zu ermöglichen Adapter.
Zuerst müssen Sie ein benutzerdefiniertes Layoutverhalten für Ihre Appbar erstellen:
Ich habe es nicht für die Bildschirmdrehung getestet, also lassen Sie mich wissen, ob es so funktioniert. Ich denke, es sollte funktionieren, da ich nicht glaube, dass die Zählvariable gespeichert wird, wenn die Rotation stattfindet, aber lassen Sie es mich wissen, wenn dies nicht der Fall ist.
Dies war die einfachste und sauberste Implementierung für mich, genieße es.
Ich denke, angesichts des Layouts in der Frage sollten Sie die Methode so nennen updatedScrollable(target) seit der RecyclerView ist kein direktes Kind der AppBarLayout.
– OneEyeQuestion
27. März 2016 um 20:56 Uhr
Es ist eigentlich ein Verweis auf das CoordinatorLayout, das das übergeordnete Element ist ;).
– Emirate
28. März 2016 um 6:38 Uhr
Es ist kein Fehler, alle Ereignisse in einer viewGroup werden auf diese Weise behandelt. Da Ihr recyclerview ein untergeordnetes Element von coordinatorLayout ist, wird es bei jedem Generieren des Ereignisses zuerst auf das übergeordnete Element überprüft, und wenn das übergeordnete Element nicht interessiert ist, wird es nur an das untergeordnete Element weitergegeben. Siehe Google Dokumentation
So etwas in einem LayoutManager Unterklasse scheint zum gewünschten Verhalten zu führen:
@Override
public boolean canScrollVertically() {
int firstCompletelyVisibleItemPosition = findFirstCompletelyVisibleItemPosition();
if (firstCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;
int lastCompletelyVisibleItemPosition = findLastCompletelyVisibleItemPosition();
if (lastCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;
if (firstCompletelyVisibleItemPosition == 0 &&
lastCompletelyVisibleItemPosition == getItemCount() - 1)
return false;
return super.canScrollVertically();
}
Die Dokumentation für canScrollVertically() sagt:
/**
* Query if vertical scrolling is currently supported. The default implementation
* returns false.
*
* @return True if this LayoutManager can scroll the current contents vertically
*/
Beachten Sie die Formulierung „can scroll the aktuelle Inhalte vertikal”, was meiner Meinung nach impliziert, dass der aktuelle Zustand durch den Rückgabewert widergespiegelt werden sollte.
Das tut aber keiner der LayoutManager Unterklassen bereitgestellt durch die v7 Recyclerview-Bibliothek (23.1.1), was mich etwas zweifeln lässt, ob es eine richtige Lösung ist; es könnte in anderen Situationen als in dieser Frage zu unerwünschten Effekten kommen.
Ich habe es mit meiner eigenen Behavior-Klasse implementiert, die möglicherweise an AppBarLayout angehängt ist:
public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {
private RecyclerView recyclerView;
private int additionalHeight;
public CustomAppBarLayoutBehavior(RecyclerView recyclerView, int additionalHeight) {
this.recyclerView = recyclerView;
this.additionalHeight = additionalHeight;
}
public boolean isRecyclerViewScrollable(RecyclerView recyclerView) {
return recyclerView.computeHorizontalScrollRange() > recyclerView.getWidth() || recyclerView.computeVerticalScrollRange() > (recyclerView.getHeight() - additionalHeight);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
if (isRecyclerViewScrollable(mRecyclerView)) {
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
return false;
}
}
Und unten ist der Code, wie man dieses Verhalten einstellt:
Danke, ich habe eine benutzerdefinierte Klasse von RecyclerView erstellt, aber der Schlüssel wird immer noch verwendet setNestedScrollingEnabled(). Auf meiner Seite hat es gut geklappt.
public class RecyclerViewCustom extends RecyclerView implements ViewTreeObserver.OnGlobalLayoutListener
{
public RecyclerViewCustom(Context context)
{
super(context);
}
public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
}
public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
/**
* This supports scrolling when using RecyclerView with AppbarLayout
* Basically RecyclerView should not be scrollable when there's no data or the last item is visible
*
* Call this method after Adapter#updateData() get called
*/
public void addOnGlobalLayoutListener()
{
this.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
public void onGlobalLayout()
{
// If the last item is visible or there's no data, the RecyclerView should not be scrollable
RecyclerView.LayoutManager layoutManager = getLayoutManager();
final RecyclerView.Adapter adapter = getAdapter();
if (adapter == null || adapter.getItemCount() <= 0 || layoutManager == null)
{
setNestedScrollingEnabled(false);
}
else
{
int lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
boolean isLastItemVisible = lastVisibleItemPosition == adapter.getItemCount() - 1;
setNestedScrollingEnabled(!isLastItemVisible);
}
unregisterGlobalLayoutListener();
}
private void unregisterGlobalLayoutListener()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
else
{
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
}
12571300cookie-checkDie Symbolleiste in AppBarLayout ist scrollbar, obwohl RecyclerView nicht genügend Inhalt zum Scrollen hatyes
Meine Meinung ist, dass es beabsichtigt ist. Dies wurde hier mehrfach gefragt, und wenn dies ein Fehler wäre, hätten sie ihn schon früher behoben – die Designbibliothek ist nicht mehr so jung.
– natürlich
7. September 2015 um 17:39 Uhr
Danke für die Antwort. Da ich die genannten Antworten/Diskussionen nicht selbst gefunden habe, könnten Sie bitte zumindest eine Ihrer Quellen posten.
– eiki
13. September 2015 um 11:21 Uhr
Es ist kein Fehler, alle Ereignisse in einer viewGroup werden auf diese Weise behandelt. Da Ihr recyclerview ein untergeordnetes Element von coordinatorLayout ist, wird es bei jedem Generieren des Ereignisses zuerst auf das übergeordnete Element überprüft, und wenn das übergeordnete Element nicht interessiert ist, wird es nur an das untergeordnete Element weitergegeben.
– Sulabh Deep Puri
2. November 2015 um 6:41 Uhr
Ich habe in der Material Design-Spezifikation keinen Hinweis darauf gefunden, aber basierend auf dem Wie Posteingang von Gmail und Google Play (Meine Wunschliste) funktioniert derzeit. Es scheint, dass das richtige Verhalten darin besteht, die App-Leiste nur wegzuscrollen, wenn genügend Inhalt zum Scrollen vorhanden ist.
– joelpet
1. Februar 2016 um 13:13 Uhr
Überprüfen Sie stackoverflow.com/a/61941446/5745574
– Vipul Kumar
21. Mai 2020 um 18:39 Uhr