Bouton gauche en surbrillance avec touchListener et clickListener

10

Je rencontre un problème avec mon bouton dans un état mis en surbrillance, après avoir effectué les opérations suivantes:

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        v.performClick();
                        Log.d("Test", "Performing click");
                        return true;
                    }
                }
                return false;
            }
        });

    }
}

En ce qui concerne le code ci-dessus, lorsque je l'utilise, je m'attends à ce que le clic sur le bouton soit géré par le toucher, et en renvoyant "true", le traitement devrait s'arrêter au touchListener.

Mais ce n'est pas le cas. Le bouton reste dans un état en surbrillance, même si le clic est appelé.

Ce que je reçois est:

Test - calling onClick
Test - Performing click

d'autre part, si j'utilise le code suivant, le bouton est cliqué, même impressions, mais le bouton ne finit pas coincé dans un état en surbrillance:

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        // v.performClick();
                        Log.d("Test", "Performing click");
                        return false;
                    }
                }
                return false;
            }
        });

    }
}

Je suis un peu confus quant à la chaîne de réponse à l'événement tactile. Je suppose que c'est:

1) TouchListener

2) ClickListener

3) ParentViews

Quelqu'un peut-il également le confirmer?

Ours blanc
la source
Qu'est-ce que vous voulez réellement faire, est-ce que cela se fait au toucher ou changez de couleur en appuyant simplement?
Haider Saleem
Je veux exécuter une logique en contact, puis appeler performClick et pour que cela ne change pas la couleur du bouton.
Whitebear
@Whitebear Veuillez vérifier la réponse ci-dessous. Je peux peut-être ajouter plus d'informations.
GensaGames
Cela pourrait vous aider à comprendre le flux des événements tactiles. On ne sait pas exactement ce que vous voulez faire. Voulez-vous un gestionnaire de clics et effectuer le clic? Voulez-vous que le bouton passe de sa couleur initiale à l'état défini par le filtre de couleur, puis revienne à sa couleur initiale?
Cheticamp
Permettez-moi d'expliquer ce que je veux dire. J'ai un touchListener et un clickListener sur le bouton. Le toucher précède le clic par priorité et renvoie vrai s'il a géré l'événement, ce qui signifie que personne d'autre ne devrait le gérer. C'est exactement ce que je fais par le toucher, en gérant le clic et en retournant vrai, mais le bouton reste toujours en surbrillance même si son écouteur onclick est appelé et le flux est effectué correctement.
Whitebear

Réponses:

10

De telles personnalisations ne nécessitent aucune modification programmatique. Vous pouvez le faire simplement dans des xmlfichiers. Tout d'abord, supprimez entièrement la setOnTouchListenerméthode que vous fournissez onCreate. Ensuite, définissez une couleur de sélecteur dans le res/colorrépertoire comme suit. (si le répertoire n'existe pas, créez-le)

res / color / button_tint_color.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="#e0f47521" android:state_pressed="true" />
    <item android:color="?attr/colorButtonNormal" android:state_pressed="false" />
</selector>

Maintenant, définissez-le sur l' app:backgroundTintattribut du bouton :

<androidx.appcompat.widget.AppCompatButton
    android:id="@+id/mybutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    app:backgroundTint="@color/button_tint_color" />


Résultat visuel:

entrez la description de l'image ici



ÉDITÉ: (pour résoudre le problème de l'événement tactile)

D'un point de vue global, le flux de l'événement tactile commence à partir du Activity, puis descend vers la disposition (du parent vers les dispositions enfant), puis vers les vues. (Flux LTR dans l'image suivante)

entrez la description de l'image ici

Lorsque l'événement tactile atteint la vue cible, la vue peut gérer l'événement, puis décider de le transmettre aux dispositions / activités précédentes ou non (retour falsede la méthode truein onTouch). (Flux RTL dans l'image ci-dessus)

Maintenant , nous allons jeter un oeil à la Voir le code source d » acquérir une connaissance plus approfondie des flux événement tactile. En jetant un œil à l'implémentation de la dispatchTouchEvent, nous verrions que si vous définissez un OnTouchListenersur la vue et que vous revenez ensuite truedans sa onTouchméthode, la onTouchEventde la vue ne sera pas appelée.

public boolean dispatchTouchEvent(MotionEvent event) {
    // removed lines for conciseness...
    boolean result = false;    
    // removed lines for conciseness...
    if (onFilterTouchEventForSecurity(event)) {
        // removed lines for conciseness...
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) { // <== right here!
            result = true;
        }
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // removed lines for conciseness...
    return result;
}

Maintenant, regardez la onTouchEventméthode où se trouve l'action d'événement MotionEvent.ACTION_UP. Nous voyons que l'action effectuer un clic s'y produit. Donc, retourner truedans le OnTouchListener's onTouchet par conséquent ne pas appeler le' onTouchEvent, provoque ne pas appeler le OnClickListener's onClick.

Il y a un autre problème à ne pas appeler le onTouchEvent, qui est lié à l'état pressé et que vous avez mentionné dans la question. Comme nous pouvons le voir dans le bloc de code ci-dessous, il existe une instance de UnsetPressedStatecet appel lors de son exécution. Le résultat de ne pas appeler que la vue se coince dans l'état pressé et son état drawable ne change pas. setPressed(false)setPressed(false)

public boolean onTouchEvent(MotionEvent event) {
    // removed lines for conciseness...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // removed lines for conciseness...
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // removed lines for conciseness...
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // removed lines for conciseness...
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                    // removed lines for conciseness...
                }
                // removed lines for conciseness...
                break;
            // removed lines for conciseness...
        }
        return true;
    }
    return false;
}

UnsetPressedState :

private final class UnsetPressedState implements Runnable {
    @Override
    public void run() {
        setPressed(false);
    }
}


Concernant les descriptions ci-dessus, vous pouvez changer le code en setPressed(false)vous appelant pour changer l'état de dessin où l'action d'événement est MotionEvent.ACTION_UP:

button.setOnTouchListener(new View.OnTouchListener() {

    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                v.invalidate();
                break;
            }
            case MotionEvent.ACTION_UP: {
                v.getBackground().clearColorFilter();
                // v.invalidate();
                v.setPressed(false);
                v.performClick();
                Log.d("Test", "Performing click");
                return true;
            }
        }
        return false;
    }
});
aminographie
la source
Ce n'est pas ce que je cherche mon ami. Je cherche à comprendre le changement de comportement dans les deux situations que j'ai décrites ci-dessus. S'il y a quelque chose que je peux développer, faites-le moi savoir. Je ne veux pas supprimer le gestionnaire tactile ni le gestionnaire de clic. Veuillez consulter mon commentaire à la réponse ci-dessus.
Whitebear
@Whitebear: J'ai mis à jour la réponse. Veuillez vérifier, mec.
aminographie
C'est une bonne réponse détaillée et je l'accepterais. Quelques conseils pour changer: Now, look at the onTouchEvent method where the event action is MotionEvent.ACTION_UP. We see that perform-click action happens there. So, returning true in the OnTouchListener's onTouch and consequently not calling the onTouchEvent, causes not calling the OnClickListener's onClick.dans mon cas, onClick est appelé. mUnsetPressedState vérifie s'il est nul avant de le définir sur false et la runnable n'est pas sûre de s'exécuter si nous sommes prépresse. Je ne comprends pas très bien comment vous déduisez qu'il doit être défini sur faux
Whitebear
Le onClickest appelé parce que vous appelez v.performClick();. Veuillez vérifier à nouveau le code ci-dessus dans la MotionEvent.ACTION_UPsection, il setPressed(false)est appelé de toute façon, qu'il mUnsetPressedStatesoit nul ou non, prepressedvrai ou non. La différence réside dans la manière d'appeler setPressed(false)qui pourrait se faire par post/ postDelayedou directement.
aminographie
2

Vous dérangez touchet les focusévénements. Commençons par comprendre le comportement avec la même couleur. Par défaut, il est Selectorassigné comme arrière-plan à Buttondans Android. Donc, tout simplement en changeant la couleur d'arrière-plan, make est statique (la couleur ne changera pas). Mais ce n'est pas un comportement natif.

Selector pourrait ressembler à celui-ci.

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_focused="true"
        android:state_pressed="true"
        android:drawable="@drawable/bgalt" />

    <item
        android:state_focused="false"
        android:state_pressed="true"
        android:drawable="@drawable/bgalt" />

    <item android:drawable="@drawable/bgnorm" />
</selector>

Comme vous pouvez le voir ci-dessus, il y a état focusedet état pressed. En définissant, onTouchListenervous gérerez les événements tactiles, qui n'ont rien à voir avec focus.

Selectordu bouton doit remplacer l' focusévénement par un événement de touchclic sur le bouton. Mais dans la première partie de votre code, vous avez intercepté des événements pour le touch(retour vrai du rappel). Le changement de couleur ne peut pas continuer et gèle avec la même couleur. Et c'est pourquoi la deuxième variante (sans interception) fonctionne bien et c'est votre confusion.

MISE À JOUR

Il vous suffit de changer le comportement et la couleur du Selector. Par exemple. en utilisant le fond suivant pour le Button. ET supprimez onTouchListenerdu tout votre implémentation.

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_pressed="true"
        android:drawable="@color/color_pressed" />

    <item android:drawable="@color/color_normal" />
</selector>
GensaGames
la source
Comment changeriez-vous le premier exemple pour bien fonctionner alors?
Whitebear
Je ne cherche pas à supprimer l'écouteur tactile de mon implémentation. comme je veux attraper des clics sur une vue spécifique en utilisant le gestionnaire tactile, puis les gérer moi-même (en utilisant performClick) et retourner true à partir de l'écouteur tactile afin de dire aux autres gestionnaires qu'aucune autre manipulation n'est effectuée. Les journaux sont bien imprimés dans mon exemple, mais le bouton reste toujours en surbrillance.
Whitebear
@Whitebear Vous ne l'avez pas mentionné dans vos questions. De toute façon, vous pouvez utiliser autant de onTouchListeners que vous le souhaitez. Vous n'avez tout simplement pas besoin de consommer un événement return true.
GensaGames
@Whitebear OU Retirez le sélecteur et définissez la couleur brute sur le bouton via backgroundColor.
GensaGames
les gars, vous ne
Whitebear
0

si vous attribuez un arrière-plan au bouton, il ne changera pas la couleur au clic.

 <color name="myColor">#000000</color>

et définissez-le en arrière-plan sur votre bouton

android:background="@color/myColor"
Haider Saleem
la source
0

vous pouvez simplement utiliser des puces matérielles pour au lieu de la vue Bouton. référez-vous: https://material.io/develop/android/components/chip là, ils gèrent ces événements heureux et vous pouvez personnaliser en appliquant les thèmes.

Prabudda Fernando
la source