web-dev-qa-db-fra.com

la méthode onClick ne fonctionne pas correctement après le défilement de NestedScrollView

J'ai utilisé NestedScrollView avec CoordinatorLayout pour activer l'animation de défilement pour la barre d'outils (par application: layout_scrollFlags = "scroll | enterAlways").

NestedScrollView contient LinearLayout en tant qu'enfant racine. J'ai placé les 2 TextViews dans LinearLayout pour activer les animations à développer/réduire. L'un était défini sur Visible Et l'autre sur Gone. Et commutation de la visibilité par l'événement onClick de LinearLayout

Normalement, tout fonctionne comme prévu, mais lorsque j'ai fait défiler le NestedScrollView L'événement onClick ne fonctionne pas correctement. J'ai besoin de double-clic après le défilement pour obtenir une animation de développement/réduction

Est-ce que quelqu'un a le même problème avec moi? Aidez-moi, s'il vous plaît

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

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

    <LinearLayout
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:orientation="vertical"
        Android:paddingBottom="98dp"
        Android:paddingLeft="24dp"
        Android:paddingRight="24dp">

        <Android.support.v7.widget.AppCompatTextView
            Android:id="@+id/detail_expense_reason_trim"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:singleLine="false"
            Android:textColor="@color/add_new_expense_text_color" />

        <Android.support.v7.widget.AppCompatTextView
            Android:id="@+id/detail_expense_reason"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:singleLine="false"
            Android:textColor="@color/add_new_expense_text_color"
            Android:visibility="gone" />
    </LinearLayout>

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

<Android.support.design.widget.AppBarLayout
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content">

    <Android.support.v7.widget.Toolbar
        Android:id="@+id/detail_expense_toolbar"
        Android:layout_width="match_parent"
        Android:layout_height="?attr/actionBarSize"
        Android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:layout_scrollFlags="scroll|enterAlways"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</Android.support.design.widget.AppBarLayout>

 @InjectView(R.id.detail_expense_reason)
AppCompatTextView originalReason;

@InjectView(R.id.detail_expense_reason_trim)
AppCompatTextView trimReason;

@InjectView(R.id.detail_expense_container)
LinearLayout expenseContainer;

// gérer l'événement

public void onClick() {
    if (originalReason.getVisibility() == View.VISIBLE) {
        originalReason.setVisibility(View.GONE);
        trimReason.setVisibility(View.VISIBLE);
    } else {
        originalReason.setVisibility(View.VISIBLE);
        trimReason.setVisibility(View.GONE);
    }

}
33
toidv

C'est un bogue de NestedScrollView, les détails du bogue peuvent être trouvés ici: issue . Le problème est que mScroller.isFinished() dans onInterceptTouchEvent(MotionEvent ev) ne retournera pas true après une opération de lancement (même si le lancement est arrêté). Par conséquent, l'événement tactile est intercepté.

Ce bogue a été signalé pendant un certain temps, mais n'a toujours pas été corrigé. J'ai donc créé ma propre version du correctif pour ce problème. J'ai implémenté ma propre NestedScrollView, copié tout le code de NestedScrollView et comportant les modifications suivantes:

public class NestedScrollView extends FrameLayout implements NestedScrollingParent, NestedScrollingChild {
    ...
    private void initScrollView() {
        ...
        // replace this line:
        // mScroller = new ScrollerCompat(getContext(), null);
        mScroller = ScrollerCompat.create(getContext(), null);
        ...
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        ...
        switch (action & MotionEventCompat.ACTION_MASK) {
            ...
            case MotionEvent.ACTION_DOWN: {
                ...
                // replace this line:
                // mIsBeingDragged = !mScroller.isFinished();
                mIsBeingDragged = false;
                ...
            }
        }
    }   
}

Et cette NestedScrollView devrait avoir le même comportement que l'original.

17
Chris

J'ai trouvé la solution au même problème sur ce fil de discussion: L'élément dans RecyclerView ne peut pas être cliqué juste après le défilement

Vous pouvez réparer votre code en ajoutant layout_behavior à votre AppBarLayout .Vous pouvez trouver le code ici Fixed AppBarLayout.Behavior . Ajoutez simplement cette classe à votre projet et corrigez votre code:

<Android.support.design.widget.AppBarLayout Android:layout_width="match_parent" app:layout_behavior="yourPackageName.FixAppBarLayoutBehavior" Android:layout_height="wrap_content">

30
Mihuilk

J'ai ouvert un autre numéro ici: https://issuetracker.google.com/issues/68103042 car cela semble toujours être un problème à Oreo pour nous (plusieurs périphériques, y compris des émulateurs).

Mon correctif (adapté des suggestions de ta .. @ graymeter.com à https://issuetracker.google.com/issues/37051723 ) ne nécessite pas de modification du code AOSP car il utilise la réflexion:

public class MyNestedScrollView extends NestedScrollView {

    private static final Logger sLogger = LogFactory.getLogger(MyNestedScrollView.class);

    private OverScroller mScroller;
    public boolean isFling = false;

    public MyNestedScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = getOverScroller();
    }

    @Override
    public void fling(int velocityY) {
        super.fling(velocityY);

        // here we effectively extend the super class functionality for backwards compatibility and just call invalidateOnAnimation()
        if (getChildCount() > 0) {
            ViewCompat.postInvalidateOnAnimation(this);

            // Initializing isFling to true to track fling action in onScrollChanged() method
            isFling = true;
        }
    }

    @Override
    protected void onScrollChanged(int l, final int t, final int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        if (isFling) {
            if (Math.abs(t - oldt) <= 3 || t == 0 || t == (getChildAt(0).getMeasuredHeight() - getMeasuredHeight())) {
                isFling = false;

                // This forces the mFinish variable in scroller to true (as explained the
                //    mentioned link above) and does the trick
                if (mScroller != null) {
                    mScroller.abortAnimation();
                }
            }
        }
    }

    private OverScroller getOverScroller() {
        Field fs = null;
        try {
            fs = this.getClass().getSuperclass().getDeclaredField("mScroller");
            fs.setAccessible(true);
            return (OverScroller) fs.get(this);
        } catch (Throwable t) {
            return null;
        }
    }
}
6
DustinB

C'est une mention Bug à Google #issues 194398.

Juste besoin d'utiliser cette classe WorkaroundNestedScrollView.Java qui étend NestedScrollView comme,

WorkaroundNestedScrollView.Java

public class WorkaroundNestedScrollView extends NestedScrollView {

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

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // Explicitly call computeScroll() to make the Scroller compute itself
        computeScroll();
    }
    return super.onInterceptTouchEvent(ev);
}
}

Et dans yourlayout utilisez-le comme ça,

layout.xml

<com.yourpackagename.whatever.WorkaroundNestedScrollView
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">
...
...

</com.yourpackagename.whatever.WorkaroundNestedScrollView>

Vous pouvez également trouver plus détails ici .

1
Jaydipsinh Zala

J'ai rencontré ce problème aussi

    public class NestedScrollView extends FrameLayout implements NestedScrollingParent,
    NestedScrollingChild, ScrollingView {
       @Override
        public boolean onTouchEvent(MotionEvent ev) {
          switch (actionMasked) {
            case MotionEvent.ACTION_DOWN: {
                if (getChildCount() == 0) {
                    return false;
                }
                //add this line
                if (!inChild((int) ev.getX(), (int) ev.getY())) {
                    return false;
                }
                if ((mIsBeingDragged = !mScroller.isFinished())) {
                        final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }
           }
      }

et je modifie mon fichier xml, change paddingTop en margin_top, puis l'événement OnClick de ma vue flottante supérieure ne sera pas intercepté par NestedScrollView

0
vivianChen

Meilleure solution :

1) Créer cette classe:

public class FixAppBarLayoutBehavior extends AppBarLayout.Behavior {

public FixAppBarLayoutBehavior() {
    super();
}

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

@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target,
        int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
    super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
            dxUnconsumed, dyUnconsumed, type);
    stopNestedScrollIfNeeded(dyUnconsumed, child, target, type);
}

@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
        View target, int dx, int dy, int[] consumed, int type) {
    super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
    stopNestedScrollIfNeeded(dy, child, target, type);
}

private void stopNestedScrollIfNeeded(int dy, AppBarLayout child, View target, int type) {
    if (type == ViewCompat.TYPE_NON_TOUCH) {
        final int currOffset = getTopAndBottomOffset();
        if ((dy < 0 && currOffset == 0)
                || (dy > 0 && currOffset == -child.getTotalScrollRange())) {
            ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH);
        }
    }
}}

2) Et utiliser en XML:

<Android.support.design.widget.AppBarLayout
...
app:layout_behavior="yourPackageName.FixAppBarLayoutBehavior"
...>
0
Fidan Bacaj