webentwicklung-frage-antwort-db.com.de

Die Symbolleiste in AppBarLayout ist scrollbar, obwohl RecyclerView nicht genug Inhalt zum Scrollen aufweist

Ist wirklich beabsichtigt, dass die Toolbar in einem AppBarLayout scrollbar ist, obwohl der Hauptcontainer mit "appbar_scrolling_view_behavior" nicht genügend Inhalt hat, um wirklich scrollen zu können?

Was ich bisher getestet habe:
Wenn ich eine NestedScrollView (mit dem Attribut "wrap_content") als Hauptcontainer und eine TextView als untergeordnetes Element verwende, funktioniert AppBarLayout ordnungsgemäß und scrollt nicht.

Wenn ich jedoch eine RecyclerView mit nur wenigen Einträgen und dem Attribut "wrap_content" verwende (damit kein Bildlauf erforderlich ist), ist die Toolbar im AppBarLayout scrollbar, obwohl das RecyclerView niemals ein Bildlaufereignis empfängt (getestet mit einem OnScrollChangeListener) ).

Hier ist mein Layoutcode:

<Android.support.design.widget.CoordinatorLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:id="@+id/coordinatorLayout"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/appBarLayout"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            Android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:theme="@style/ToolbarStyle" />
    </Android.support.design.widget.AppBarLayout>

    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/recycler"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</Android.support.design.widget.CoordinatorLayout>

Mit dem folgenden Effekt kann die Symbolleiste gescrollt werden, obwohl dies nicht erforderlich ist:

 

Ich habe auch einen Weg gefunden, um damit umzugehen, indem ich prüfe, ob alle RecyclerView-Elemente sichtbar sind und die setNestedScrollingEnabled () -Methode der RecyclerView verwenden.
Trotzdem scheint es mir eher ein Fehler zu sein. Irgendwelche Meinungen? : D

EDIT # 1:

Für Leute, die an meiner aktuellen Lösung interessiert sein könnten, musste ich die setNestedScrollingEnabled () - Logik in die postDelayed () - Methode eines Handlers mit einer Verzögerung von 5 ms einfügen, da der LayoutManager beim Aufruf der Methoden immer -1 ausgegeben hat ob das erste und das letzte Element sichtbar ist.
Ich benutze diesen Code in der onStart () -Methode (nachdem mein RecyclerView initialisiert wurde) und jedes Mal, wenn eine Inhaltsänderung des 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 Unterstützungsbibliothek Version 23.3.0 (oder sogar früher) behoben wurde. Somit sind keine Workarounds mehr erforderlich!

57
eickeee

Edit 2:

Die einzige Möglichkeit, sicherzustellen, dass die Symbolleiste nicht scrollbar ist, wenn RecyclerView nicht scrollbar ist, besteht darin, setScrollFlags programmgesteuert festzulegen, 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);
    }
}

activity_main.xml:

<Android.support.v4.widget.DrawerLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:id="@+id/drawer_layout"
    Android:layout_height="match_parent"
    Android:layout_width="match_parent"
    Android:fitsSystemWindows="true">

    <Android.support.design.widget.CoordinatorLayout
        xmlns:Android="http://schemas.Android.com/apk/res/Android"
        xmlns:app="http://schemas.Android.com/apk/res-auto"
        Android:id="@+id/main_content"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent">

        <Android.support.design.widget.AppBarLayout
            Android:id="@+id/appbar"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

            <Android.support.design.widget.CollapsingToolbarLayout
                Android:id="@+id/collapsing_toolbar"
                Android:layout_width="match_parent"
                Android:layout_height="match_parent"
                Android:fitsSystemWindows="true"
                app:contentScrim="?attr/colorPrimary">

                <Android.support.v7.widget.Toolbar
                    Android:id="@+id/toolbar"
                    Android:layout_width="match_parent"
                    Android:layout_height="?attr/actionBarSize"
                    Android:background="?attr/colorPrimary"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

            </Android.support.design.widget.CollapsingToolbarLayout>

        </Android.support.design.widget.AppBarLayout>

        <FrameLayout
            Android:id="@+id/root_content"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:layout_gravity="fill_vertical"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

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

</Android.support.v4.widget.DrawerLayout>

Testfragment:

public class CheeseListFragment extends Fragment {

    private static final int DOWN = 1;
    private static final int UP = 0;

    private LayoutController controller;
    private RecyclerView rv;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        try {
            controller = (MainActivity) getActivity();
        } catch (ClassCastException e) {
            throw new RuntimeException(getActivity().getLocalClassName()
                    + "must implement controller.", e);
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rv = (RecyclerView) inflater.inflate(
                R.layout.fragment_cheese_list, container, false);
        setupRecyclerView(rv);

        // Find out if RecyclerView are scrollable, delay required
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
                    controller.enableScroll();
                } else {
                    controller.disableScroll();
                }
            }
        }, 100);

        return rv;
    }

    private void setupRecyclerView(RecyclerView recyclerView) {
        final LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext());

        recyclerView.setLayoutManager(layoutManager);

        final SimpleStringRecyclerViewAdapter adapter =
                new SimpleStringRecyclerViewAdapter(
                        getActivity(),
                        // Test ToolBar scroll
                        getRandomList(/* with enough items to scroll */)
                        // Test ToolBar pin
                        getRandomList(/* with only 3 items*/)
                );

        recyclerView.setAdapter(adapter);
    }
}

Quellen:

Bearbeiten:

Sie sollten CollapsingToolbarLayout verwenden, um das Verhalten zu steuern.

Durch das direkte Hinzufügen einer Symbolleiste zu einem AppBarLayout haben Sie Zugriff auf die Scroll-Flags enterAlwaysCollapsed und exitUntilCollapsed, nicht jedoch auf die detaillierte Steuerung, wie verschiedene Elemente auf das Zusammenfallen von Elementen reagieren . [...] Setup verwendet die App von collapsingToolbarLayout: layout_collapseMode = "pin", um sicherzustellen, dass die Toolbar selbst am oberen Bildschirmrand fixiert bleibt, während die Ansicht. http://Android-developers.blogspot.com.tr /2015/05/Android-design-support-library.html

<Android.support.design.widget.CollapsingToolbarLayout
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">

    <Android.support.v7.widget.Toolbar
        Android:id="@+id/drawer_toolbar"
        Android:layout_width="match_parent"
        Android:layout_height="?attr/actionBarSize"
        app:layout_collapseMode="pin"/>

</Android.support.design.widget.CollapsingToolbarLayout>

Hinzufügen

app:layout_collapseMode="pin"

in der Toolbar in xml.

    <Android.support.v7.widget.Toolbar
        Android:id="@+id/toolbar"
        Android:layout_width="match_parent"
        Android:layout_height="?attr/actionBarSize"
        Android:background="?attr/colorPrimary"
        app:layout_scrollFlags="scroll|enterAlways"
        app:layout_collapseMode="pin"
        app:theme="@style/ToolbarStyle" />
6
user3623735

Also, richtig gut, diese Antwort hat es fast für mich gelöst https://stackoverflow.com/a/32923226/5050087 . Da die Symbolleiste jedoch nicht angezeigt wurde, als Sie tatsächlich über eine scrollbare Recyclingansicht verfügten und deren letztes Element sichtbar war (die Symbolleiste wurde nicht beim ersten Bildlauf nach oben angezeigt), entschied ich mich, sie zu modifizieren und für eine einfachere Implementierung und für Dynamik anzupassen Adapter.

Zunächst müssen Sie ein benutzerdefiniertes Layoutverhalten für Ihre Appbar erstellen:

public class ToolbarBehavior extends AppBarLayout.Behavior{

private boolean scrollableRecyclerView = false;
private int count;

public ToolbarBehavior() {
}

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

@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
    return scrollableRecyclerView && super.onInterceptTouchEvent(parent, child, ev);
}

@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
    updatedScrollable(directTargetChild);
    return scrollableRecyclerView && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}

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

private void updatedScrollable(View directTargetChild) {
    if (directTargetChild instanceof RecyclerView) {
        RecyclerView recyclerView = (RecyclerView) directTargetChild;
        RecyclerView.Adapter adapter = recyclerView.getAdapter();
        if (adapter != null) {
            if (adapter.getItemCount()!= count) {
                scrollableRecyclerView = false;
                count = adapter.getItemCount();
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                if (layoutManager != null) {
                    int lastVisibleItem = 0;
                    if (layoutManager instanceof LinearLayoutManager) {
                        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
                        lastVisibleItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition());
                    } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                        StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
                        int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]);
                        lastVisibleItem = Math.abs(lastItems[lastItems.length - 1]);
                    }
                    scrollableRecyclerView = lastVisibleItem < count - 1;
                }
            }
        }
    } else scrollableRecyclerView = true;
  }
}

Dann müssen Sie dieses Verhalten nur für Ihre Appbar in Ihrer Layoutdatei definieren:

<Android.support.design.widget.AppBarLayout
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:fitsSystemWindows="true"
    app:layout_behavior="com.yourappname.whateverdir.ToolbarBehavior"
    >

Ich habe es noch nicht auf Bildschirmdrehung getestet, also lassen Sie es mich wissen, wenn es so funktioniert. Ich denke, es sollte funktionieren, da ich glaube, dass die Zählvariable nicht gespeichert wird, wenn die Rotation stattfindet. Lassen Sie mich wissen, ob dies nicht der Fall ist. 

Dies war die einfachste und sauberste Implementierung für mich. Genieße es.

5
emirua

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:

final View appBarLayout = ((DrawerActivity) getActivity()).getAppBarLayoutView();
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(recyclerView, getResources().getDimensionPixelSize(R.dimen.control_bar_height)));
1

Es ist kein Fehler, alle Ereignisse in einer viewGroup werden auf diese Weise behandelt. Da Ihr Recyclerview ein untergeordnetes Element von "CoordinatorLayout" ist, wird das Ereignis bei der Generierung des Ereignisses zuerst auf das übergeordnete Element überprüft. Wenn das übergeordnete Element nicht nur an diesem interessiert ist, wird es an das untergeordnete Element weitergegeben. __ Siehe google documentation

1

So etwas in einer LayoutManager-Unterklasse scheint das gewünschte Verhalten zu bewirken:

@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 zu 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 den Wortlaut von "kann den aktuellen Inhalt vertikal scrollen", was meiner Meinung nach impliziert, dass der aktuelle Zustand durch den Rückgabewert reflektiert werden sollte.

Dies wird jedoch nicht von einer der LayoutManager-Unterklassen durchgeführt, die über die v7-Recyclerview-Bibliothek (23.1.1) zur Verfügung gestellt werden. Dies kann in anderen Situationen als der in dieser Frage behandelten Situation unerwünschte Auswirkungen haben.

1
joelpet

Ich schlug vor, dass Sie this sample für die Unterstützung von Designbibliothekelementen ausprobieren.

dies ist ein Layout wie Ihr Layout im Beispiel.

<Android.support.design.widget.CoordinatorLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:id="@+id/main_content"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/appbar"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            Android:background="?attr/colorPrimary"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:layout_scrollFlags="scroll|enterAlways" />

        <Android.support.design.widget.TabLayout
            Android:id="@+id/tabs"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content" />

    </Android.support.design.widget.AppBarLayout>

    <Android.support.v4.view.ViewPager
        Android:id="@+id/viewpager"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

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

Vielen Dank, ich habe eine benutzerdefinierte Klasse von RecyclerView erstellt, der Schlüssel verwendet jedoch weiterhin setNestedScrollingEnabled(). Es hat auf meiner Seite gut funktioniert.

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

Ich möchte der Antwort von user3623735 etwas hinzufügen. Der folgende Code ist absolut falsch.

// Find out if RecyclerView are scrollable, delay required
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
                controller.enableScroll();
            } else {
                controller.disableScroll();
            }
        }
    }, 100);

Und selbst wenn es funktioniert, deckt es nicht alle Fälle ab. Es gibt absolut keine Garantie dafür, dass Daten in 100 ms angezeigt werden, und die Daten können die Höhe der Ansicht während des Arbeitsprozesses nicht nur in der onCreateView-Methode ausdehnen. Deshalb sollten Sie nächsten Code verwenden und Änderungen in der Ansichtshöhe verfolgen:

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
        @Override
        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
            if(bottom != oldBottom)
            {
                mActivity.setScrollEnabled(view.canScrollVertically(0) || view.canScrollVertically(1));
            }
        }
    });

Außerdem müssen Sie nicht zwei getrennte Methoden erstellen, um den Bildlaufstatus zu steuern. Verwenden Sie eine setScrollEnabled-Methode:

public void setScrollEnabled(boolean enabled) {
    final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
            mToolbar.getLayoutParams();

    params.setScrollFlags(enabled ?
            AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS : 0);

    mToolbar.setLayoutParams(params);
}
0
Manunich