web-dev-qa-db-fra.com

Comment éviter le blocage du défilement lors de l'utilisation de setNestedScrollingEnabled (false)?

Contexte

Nous avons une mise en page assez complexe qui contient CollapsingToolbarLayout, ainsi qu’une vue RecyclerView en bas. 

Dans certains cas, nous désactivons temporairement le développement/la réduction de CollapsingToolbarLayout en appelant setNestedScrollingEnabled (boolean) sur RecyclerView.

Le problème

Cela fonctionne généralement bien.

Cependant, dans certains cas (rares), le défilement lent sur le RecyclerView est semi-bloqué, ce qui signifie qu'il essaie de revenir en arrière lors du défilement. C'est comme si il y avait 2 défilement qui se battent (défilement vers le haut et vers le bas):

 enter image description here

Le code à déclencher est en tant que tel:

res/layout/activity_scrolling.xml

<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:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/app_bar"
        Android:layout_width="match_parent"
        Android:layout_height="@dimen/app_bar_height"
        Android:fitsSystemWindows="true"
        Android:theme="@style/AppTheme.AppBarOverlay">

        <Android.support.design.widget.CollapsingToolbarLayout
            Android:id="@+id/toolbar_layout"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

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

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

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

    <LinearLayout
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            Android:id="@+id/disableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="disable"/>

        <Button
            Android:id="@+id/enableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="enable"
            />
    </LinearLayout>

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

ScrollingActivity.Java

public class ScrollingActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final RecyclerView nestedView = (RecyclerView) findViewById(R.id.nestedView);
        findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                nestedView.setNestedScrollingEnabled(false);
            }
        });
        findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                nestedView.setNestedScrollingEnabled(true);
            }
        });
        nestedView.setLayoutManager(new LinearLayoutManager(this));
        nestedView.setAdapter(new Adapter() {
            @Override
            public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
                        Android.R.layout.simple_list_item_1,
                        parent,
                        false)) {
                };
            }

            @Override
            public void onBindViewHolder(final ViewHolder holder, final int position) {
                ((TextView) holder.itemView.findViewById(Android.R.id.text1)).setText("item " + position);
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        });
    }

}

Ce que j'ai essayé

Au début, je pensais que c'était à cause de quelque chose d'autre (j'ai pensé que c'était une étrange combinaison avec DrawerLayout), mais j'ai ensuite trouvé un échantillon minimal à montrer, et c'est exactement ce que je pensais: tout cela est dû à setNestedScrollingEnabled. 

J'ai essayé d'en informer le site Web de Google ( ici ), en espérant qu'il soit corrigé s'il s'agit d'un véritable bogue. Si vous souhaitez l'essayer ou regarder les vidéos du problème, allez-y, je ne peux pas tous les télécharger ici (trop volumineux et trop de fichiers).

J'ai aussi essayé d'utiliser des drapeaux spéciaux comme indiqué dans d'autres messages (exemples: ici , ici , ici , here et here ), mais aucun n'a aidé. En fait, chacun d’eux avait un problème, qu’il s’agisse de rester en mode développé ou de faire défiler d’une manière différente de la mienne.

Questions

  1. Est-ce un problème connu? Pourquoi ça se passe?

  2. Y a-t-il un moyen de surmonter cela?

  3. Existe-t-il une alternative à l'appel de cette fonction de setNestedScrollingEnabled? Un sans aucun problème de défilement ou de verrouillage de l’état de CollapsingToolbarLayout?

20
android developer

Il s'agit d'une approche alternative pour atteindre le même objectif que cette réponse . Bien que cette réponse utilise Reflection, cette réponse ne le fait pas, mais le raisonnement reste le même.

Pourquoi cela arrive-t-il?

Le problème est que RecyclerView utilise parfois une valeur périmée pour la variable membre mScrollOffset. mScrollOffset est défini à seulement deux endroits dans RecyclerView: dispatchNestedPreScroll et dispatchNestedScroll. Nous ne sommes concernés que par dispatchNestedPreScroll. Cette méthode est appelée par RecyclerView#onTouchEvent quand elle gère les événements MotionEvent.ACTION_MOVE

Ce qui suit est extrait de la documentation de dispatchNestedPreScroll .

dispatchNestedPreScroll

boolean dispatchNestedPreScroll (int dx, int dy, int [] consommé, int [] offsetInWindow)

Distribue une étape d'un défilement imbriqué en cours avant que cette vue n'en consomme une partie.

Les événements de pré-défilement imbriqués sont des événements de défilement imbriqués, ce que l'interception tactile doit toucher. dispatchNestedPreScroll offre la possibilité à la vue parent dans une opération de défilement imbriquée de consommer tout ou partie de l'opération de défilement avant que la vue enfant ne l'utilise.

...

offsetInWindow int: facultatif. Si non null, retourne le décalage dans les coordonnées de la vue locale de cette vue d'avant cette opération à sa fin. Les implémentations de View peuvent l’utiliser pour ajuster le suivi des coordonnées d’entrée attendues.

offsetInWindow est en fait un int[2] avec le deuxième index représentant le décalage y à appliquer à la RecyclerView en raison du défilement imbriqué.

RecyclerView#DispatchNestedPrescroll est résolu en une méthode du même nom dans NestedScrollingChildHelper .

Lorsque RecyclerView appelle dispatchNestedPreScroll, mScrollOffset est utilisé comme argument offsetInWindow. Ainsi, toute modification apportée à offsetInWindow met directement à jour mScrollOffset. dispatchNestedPreScroll mises à jour mScrollOffset tant que le défilement imbriqué est actif. Si le défilement imbriqué n’est pas activé, alors mScrollOffset n’est pas mis à jour et procède à la dernière valeur définie par dispatchNestedPreScroll. Ainsi, lorsque le défilement imbriqué est désactivé, la valeur de mScrollOffset devient immédiatement obsolète, mais RecyclerView continue de l’utiliser.

La valeur correcte de mScrollOffset[1] au retour de dispatchNestedPreScroll est le montant à ajuster pour input coordinate tracking (voir ci-dessus). Dans RecyclerView, les lignes suivantes ajustent la coordonnée y tactile:

mLastTouchY = y - mScrollOffset[1];

Si mScrollOffset[1] est, disons, -30 (car il est périmé et devrait être nul), alors mLastTouchY sera désactivé de +30 pixels (--30 = + 30). L’effet de cette erreur de calcul est qu’il apparaîtra que le toucher s’est produit plus loin en bas de l’écran. Ainsi, un défilement lent vers le bas fait défiler vers le haut et un défilement vers le haut défile plus rapidement. (Si un défilement vers le bas est assez rapide pour surmonter cette barrière 30px, le défilement vers le bas aura lieu mais plus lentement qu'il ne le devrait.) Le défilement vers le haut sera trop rapide, car l'application pense que davantage d'espace a été couvert.

mScrollOffset continuera en tant que variable périmée jusqu'à ce que le défilement imbriqué soit activé et dispatchNestedPreScroll rapporte à nouveau la valeur correcte dans mScrollOffset.

_ {Approche

Étant donné que mScrollOffset[1] a une valeur périmée dans certaines circonstances, l'objectif est de lui attribuer la valeur correcte dans ces circonstances. Cette valeur doit être égale à zéro lorsque le défilement imbriqué n’a pas lieu, c’est-à-dire lorsque la barre d’application est développée ou réduite. Malheureusement, mScrollOffset est local à RecyclerView et il n'y a pas de passeur pour cela. Pour accéder à mScrollOffset sans avoir recours à Reflection, une RecyclerView personnalisée est créée qui remplace dispatchNestedPreScroll. Le quatrième élément est offsetInWindow, qui est la variable à modifier.

Une mScrollOffset obsolète se produit chaque fois que le défilement imbriqué est désactivé pour la RecyclerView. Une condition supplémentaire que nous imposerons est que la barre d’application doit être inactive afin que nous puissions dire en toute sécurité que mScrollOffset[1] doit être égal à zéro. Ce n'est pas un problème car CollapsingToolbarLayout spécifie snap dans les drapeaux de défilement.

Dans l'exemple d'application, ScrollingActivity a été modifié pour permettre l'enregistrement du développement et de la fermeture de la barre d'applications. Un rappel a également été créé (clampPrescrollOffsetListener) qui retournera true lorsque nos deux conditions sont remplies. Notre variable dispatchNestedPreScroll invoquera ce rappel et pince mScrollOffset[1] à zéro sur une réponse true.

Le fichier source mis à jour pour ScrollingActivity est présenté ci-dessous, de même que le RecyclerView - MyRecyclerView personnalisé. Le fichier de présentation XML doit être modifié pour refléter la variable MyRecyclerView personnalisée.

ScrollingActivity

public class ScrollingActivity extends AppCompatActivity
        implements MyRecyclerView.OnClampPrescrollOffsetListener {

    private CollapsingToolbarLayout mCollapsingToolbarLayout;
    private AppBarLayout mAppBarLayout;
    private MyRecyclerView mNestedView;
    // This variable will be true when the app bar is completely open or completely collapsed.
    private boolean mAppBarIdle = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mNestedView = (MyRecyclerView) findViewById(R.id.nestedView);
        mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar);
        mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);

        // Set the listener for the patch code.
        mNestedView.setOnClampPrescrollOffsetListener(this);

        // Listener to determine when the app bar is collapsed or fully open (idle).
        mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                mAppBarIdle = verticalOffset == 0
                        || verticalOffset <= appBarLayout.getTotalScrollRange();
            }
        });
        findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // If the AppBar is fully expanded or fully collapsed (idle), then disable
                // expansion and apply the patch; otherwise, set a flag to disable the expansion
                // and apply the patch when the AppBar is idle.
                setExpandEnabled(false);

            }
        });
        findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                setExpandEnabled(true);
            }
        });
        mNestedView.setLayoutManager(new LinearLayoutManager(this));
        mNestedView.setAdapter(new Adapter() {
            @Override
            public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
                        Android.R.layout.simple_list_item_1,
                        parent,
                        false)) {
                };
            }

            @Override
            public void onBindViewHolder(final ViewHolder holder, final int position) {
                ((TextView) holder.itemView.findViewById(Android.R.id.text1)).setText("item " + position);
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        });
    }

    private void setExpandEnabled(boolean enabled) {
        mNestedView.setNestedScrollingEnabled(enabled);
    }

    // Return "true" when the app bar is idle and nested scrolling is disabled. This is a signal
    // to the custom RecyclerView to clamp the y prescroll offset to zero.
    @Override
    public boolean clampPrescrollOffsetListener() {
        return mAppBarIdle && !mNestedView.isNestedScrollingEnabled();
    }

    private static final String TAG = "ScrollingActivity";
}

MyRecyclerView

public class MyRecyclerView extends RecyclerView {
    private OnClampPrescrollOffsetListener mPatchListener;

    public MyRecyclerView(Context context) {
        super(context);
    }

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

    public MyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    // Just a call to super plus code to force offsetInWindow[1] to zero if the patchlistener
    // instructs it.
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        boolean returnValue;
        int currentOffset;
        returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
        currentOffset = offsetInWindow[1];
        Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset);
        if (mPatchListener.clampPrescrollOffsetListener() && offsetInWindow[1] != 0) {
            Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset + " -> 0");
            offsetInWindow[1] = 0;
        }
        return returnValue;
    }

    public void setOnClampPrescrollOffsetListener(OnClampPrescrollOffsetListener patchListener) {
        mPatchListener = patchListener;
    }

    public interface OnClampPrescrollOffsetListener {
        boolean clampPrescrollOffsetListener();
    }

    private static final String TAG = "MyRecyclerView";
}
2
Cheticamp

En fait, vous envisagez peut-être le problème de la mauvaise façon. 

La seule chose dont vous avez besoin est de définir les indicateurs Toolbar en conséquence. Vous ne faites pas vraiment autre chose alors je dirais que votre mise en page devrait être simplifiée comme suit: 

<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:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <Android.support.design.widget.AppBarLayout
         Android:id="@+id/app_bar"
         Android:layout_width="match_parent"
         Android:layout_height="@dimen/app_bar_height"
         Android:fitsSystemWindows="true"
         Android:theme="@style/AppTheme.AppBarOverlay">

        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:title="Title" />

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

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

    <LinearLayout
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            Android:id="@+id/disableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="disable"/>

        <Button
            Android:id="@+id/enableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="enable"
            />
    </LinearLayout>
</Android.support.design.widget.CoordinatorLayout>

Ensuite, lorsque vous souhaitez désactiver la réduction, définissez les indicateurs de votre barre d’outils:

// To disable collapsing
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
toolbar.setLayoutParams(params);

Et pour permettre

// To enable collapsing
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL|AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
toolbar.setLayoutParams(params);

Conservez une référence aux paramètres de présentation si vous modifiez au lieu de l’obtenir tout le temps.

Si vous avez besoin deCollapsingToolbarLayoutget de et définir la LayoutParams à cette View à la place, mettez à jour les drapeaux de la même manière, mais ajoutez maintenant le appBarLayout.setExpanded(true/false)

Remarque: L'utilisation de la variable setScrollFlags efface tous les indicateurs précédents. Soyez donc prudent et définissez tout requis indicateurs lorsque vous utilisez cette méthode.

4
Joaquim Ley

dans la vue du recycleur, faire défiler en douceur

Android:nestedScrollingEnabled="false" 

faire chevaucher la carte dans la barre d'outils

 app:behavior_overlapTop = "24dp" 

Essayez ce code pour CollapsingToolbar:

  <Android.support.design.widget.CoordinatorLayout
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:background="@color/background"
    Android:fitsSystemWindows="true">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/app_bar"
        Android:layout_width="match_parent"
        Android:layout_height="@dimen/app_bar_height"
        Android:fitsSystemWindows="true"
        Android:theme="@style/AppTheme.AppBarOverlay">

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

            <Android.support.v7.widget.Toolbar
                Android:id="@+id/toolbar"
                Android:layout_width="match_parent"
                Android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"
                app:title="Title" />

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


    <Android.support.v4.widget.NestedScrollView
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:layout_marginLeft="10dp"
        Android:layout_marginRight="10dp"
        Android:background="@Android:color/transparent"
        app:behavior_overlapTop="@dimen/behavior_overlap_top"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            Android:id="@+id/linearLayout"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:orientation="vertical">

            <Android.support.v7.widget.RecyclerView
                Android:id="@+id/recycler_view
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:layout_margin="@dimen/text_min_padding"
                Android:nestedScrollingEnabled="false"
                Android:scrollbarSize="2dp"
                Android:scrollbarStyle="outsideInset"
                Android:scrollbarThumbVertical="@color/colorAccent"
                Android:scrollbars="vertical" />

        </LinearLayout>

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

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

Capture d'écran

3
RamaKrishnan

Comme @Moinkhan le fait remarquer, vous pouvez essayer d’emballer RecyclerView et les éléments suivants dans un NestedScrollView comme ceci, ceci devrait résoudre votre problème de défilement avec la présentation de votre barre d’outil qui se réduit:

<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:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/app_bar"
        Android:layout_width="match_parent"
        Android:layout_height="@dimen/app_bar_height"
        Android:fitsSystemWindows="true"
        Android:theme="@style/AppTheme.AppBarOverlay">

        <Android.support.design.widget.CollapsingToolbarLayout
            Android:id="@+id/toolbar_layout"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

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

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

    <Android.support.v4.widget.NestedScrollView
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:layout_gravity="fill_vertical"
        Android:fillViewport="true"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <RelativeLayout
            Android:layout_width="match_parent"
            Android:layout_height="match_parent">

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

        </RelativeLayout>

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

    <LinearLayout
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            Android:id="@+id/disableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="disable"/>

        <Button
            Android:id="@+id/enableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="enable"
            />
    </LinearLayout>

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

Si le contenu de Recyclerview n’est pas affiché, vous pouvez suivre ce fil de discussion pour résoudre ce problème Comment utiliser RecyclerView dans NestedScrollView? .

J'espère que ça aide.

3
fmaccaroni

Je devais résoudre un problème similaire et le faire en utilisant un comportement personnalisé sur le AppBarLayout. Tout fonctionne parfaitement . En remplaçant onStartNestedScroll dans le comportement personnalisé, il est possible d'empêcher la réduction ou l'agrandissement de la présentation de la barre d'outils tout en conservant la vue de défilement (NestedScrollView) dans mon cas et fonctionnant correctement. J'ai expliqué les détails ici , espérons que cela aide.

private class AppBarLayoutBehavior : AppBarLayout.Behavior() {
    var canDrag = true
    var acceptsNestedScroll = true

    init {
        setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
            override fun canDrag(appBarLayout: AppBarLayout): Boolean {
                // Allow/Do not allow dragging down/up to expand/collapse the layout
                return canDrag
            }
        })
    }

    override fun onStartNestedScroll(parent: CoordinatorLayout,
                                     child: AppBarLayout,
                                     directTargetChild: View,
                                     target: View,
                                     nestedScrollAxes: Int,
                                     type: Int): Boolean {
        // Refuse/Accept any nested scroll event
        return acceptsNestedScroll
    }}
2
Francesco Rigoni

Utilisez le code suivant, cela fonctionne très bien pour moi:

lockAppBarClosed();
ViewCompat.setNestedScrollingEnabled(recyclerView, false);   // to lock the CollapsingToolbarLayout

et implémentez les méthodes suivantes:

private void setAppBarDragging(final boolean isEnabled) {
        CoordinatorLayout.LayoutParams params =
                (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
        AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
        behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
            @Override
            public boolean canDrag(AppBarLayout appBarLayout) {
                return isEnabled;
            }
        });
        params.setBehavior(behavior);
    }

    public void unlockAppBarOpen() {
        appBarLayout.setExpanded(true, false);
        appBarLayout.setActivated(true);
        setAppBarDragging(false);
    }

    public void lockAppBarClosed() {
        appBarLayout.setExpanded(false, false);
        appBarLayout.setActivated(false);
        setAppBarDragging(false);

    }
2
Usman Rana

Je crois que ce problème est lié à la fermeture de la barre d’outils (fermée ou ouverte) et au fait de laisser une variable de décalage vertical (mScrollOffset[1] dans RecyclerView) avec une valeur non nulle qui polarise ensuite le défilement en ralentissant ou en inversant le défilement. direction et en accélérant dans l'autre. Cette variable ne semble être définie que dans NestedScrollingChildHelper si le défilement imbriqué est activé. Ainsi, quelle que soit la valeur mScrollOffset[1] reste inchangée une fois le défilement imbriqué désactivé.

Pour reproduire ce problème de manière fiable, vous pouvez enclencher la barre d’outils puis cliquer immédiatement sur désactiver. Voir cette vidéo pour une démonstration. Je crois que l’ampleur du problème varie en fonction du nombre de "claquements".

Si je fais glisser la barre d'outils en position complètement ouverte ou fermée et que je ne la laisse pas "claquer", je n'ai pas été en mesure de reproduire ce problème et mScrollOffset[1] est défini sur zéro, ce qui, à mon avis, est la bonne valeur. J'ai également reproduit le problème en supprimant snap du layout_scrollFlags de la barre d'outils réduite dans la présentation et en plaçant la barre d'outils dans un état partiellement ouvert.

Si vous voulez jouer avec cela, vous pouvez mettre votre application de démonstration en mode débogage et observer la valeur de mScrollOffset[1] dans RecyclerView#onTouchEvent. Jetez également un coup d'œil aux méthodes NestedScrollingChildHelper's dispatchNestedScroll et dispatchNestedPreScroll pour voir comment le décalage est défini uniquement lorsque le défilement imbriqué est activé. 

Alors, comment résoudre ce problème? mScrollOffset est privé àRecyclerView et il n'est pas immédiatement évident de savoir comment sous-classer quoi que ce soit pour changer la valeur de mScrollOffset[1]. Cela laisserait la réflexion, mais cela peut ne pas être souhaitable pour vous. Peut-être un autre lecteur a-t-il une idée sur la façon de l'aborder ou connaît-il une sauce secrète? Je republierai si quelque chose m'arrive.

Edit: J'ai fourni une nouvelle classe ScrollingActivity.Java qui résout ce problème. Il utilise la réflexion et applique un correctif pour définir mScrollOffset[1] of RecyclerView à zéro lorsque le bouton de défilement désactivé a été enfoncé et que la barre d’application est inactive. J'ai fait des tests préliminaires et ça marche. Voici le Gist . (Voir Gist mis à jour ci-dessous.)

Deuxième édition: J'ai réussi à faire en sorte que la barre d'outils s'emboîte de manière amusante et reste bloquée au milieu sans le correctif. Il ne semble donc pas que le correctif soit à l'origine de ce problème particulier. Je peux faire en sorte que la barre d’outils rebondisse de complètement ouverte à réduite en faisant défiler assez rapidement vers le bas dans l’application non corrigée. 

J'ai également examiné de nouveau le fonctionnement du correctif et je pense qu'il se comportera de manière autonome: la variable est privée et n'est référencée qu'à un seul endroit après la désactivation du défilement. Lorsque le défilement est activé, la variable est toujours réinitialisée avant utilisation. La vraie réponse est pour Google de résoudre ce problème. Jusqu'à ce qu'ils le fassent, je pense que c'est peut-être le plus proche possible d'une solution de contournement acceptable avec ce modèle particulier. (J'ai posté un Gist mis à jour qui traite des problèmes potentiels avec un clic rapide en laissant les commutateurs dans un état potentiellement inapproprié.)

Quoi qu'il en soit, le problème sous-jacent a été identifié et vous disposez d'un moyen fiable pour le reproduire. Vous pouvez ainsi plus facilement vérifier les autres solutions proposées.

J'espère que ça aide.

1
Cheticamp

Je veux présenter une alternative de Nice, principalement basée sur celle ici :

AppBarLayoutEx.kt

class AppBarLayoutEx : AppBarLayout {
    private var isAppBarExpanded = true
    private val behavior = AppBarLayoutBehavior()
    private var onStateChangedListener: (Boolean) -> Unit = {}
    var enableExpandAndCollapseByDraggingToolbar: Boolean
        get() = behavior.canDrag
        set(value) {
            behavior.canDrag = value
        }

    var enableExpandAndCollapseByDraggingContent: Boolean
        get() = behavior.acceptsNestedScroll
        set(value) {
            behavior.acceptsNestedScroll = value
        }

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    init {
        addOnOffsetChangedListener(
                AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
                    isAppBarExpanded = verticalOffset == 0
                    onStateChangedListener(isAppBarExpanded)
                })
    }

    override fun setLayoutParams(params: ViewGroup.LayoutParams?) {
        super.setLayoutParams(params)
        (params as CoordinatorLayout.LayoutParams).behavior = behavior
    }

    fun toggleExpandedState() {
        setExpanded(!isAppBarExpanded, true)
    }

    fun setOnExpandAndCollapseListener(onStateChangedListener: (Boolean) -> Unit) {
        this.onStateChangedListener = onStateChangedListener
    }

    private class AppBarLayoutBehavior : AppBarLayout.Behavior() {
        var canDrag = true
        var acceptsNestedScroll = true

        init {
            setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
                override fun canDrag(appBarLayout: AppBarLayout) = canDrag
            })
        }

        override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View,
                                         target: View, nestedScrollAxes: Int, type: Int) = acceptsNestedScroll
    }
}

Utilisation: en plus de l’utiliser dans le fichier XML de présentation, vous pouvez désactiver/activer son développement en utilisant:

appBarLayout.enableExpandAndCollapseByDraggingToolbar = true/false

appBarLayout.enableExpandAndCollapseByDraggingContent = true/false
0
android developer