web-dev-qa-db-fra.com

La méthode Android onClick ne fonctionne pas sur une vue personnalisée

J'ai commencé à travailler sur une application. J'ai construit le menu hier mais la méthode onClick ne fonctionne pas! J'ai créé une classe qui étend View et l'a appelée MainMenuObject - cette classe concerne tout objet du menu principal (boutons, logos, etc.). J'ai créé une classe spéciale pour eux parce que je fais une animation au démarrage du menu. Après avoir construit la classe MainMenuObject, j'ai construit une autre classe (OpeningTimesView) qui étend View et contiendra tous les boutons du menu principal et fonctionnera comme la disposition de l'activité principale.

Tout était bon, l'animation s'est très bien déroulée et je voulais mettre des écouteurs sur mes boutons. J'ai donc ajouté une implémentation de onClickListener à la classe OpeningTimesView et redéfini la méthode onClick. Ensuite, j'ai ajouté l'auditeur aux boutons avec setOnClickListener (this) et setClickable (true), mais cela ne fonctionne pas! J'ai tout essayé! S'il vous plaît, aidez-moi à comprendre ce que je fais mal. J'ai ajouté un toast à la méthode onClick qui ne dépend d'aucun "si" mais ne montre pas non plus.

(BTW existe-t-il un moyen de définir la largeur et la hauteur de l'écran en tant que variable accessible à toutes les classes? Elle ne peut pas être statique car vous obtenez la hauteur et la largeur d'un objet d'affichage, mais il doit y avoir un autre moyen)

c'est le code:

public class OpeningTimesView extends View implements OnClickListener{
    private MainMenuObjectView searchButton;
    private MainMenuObjectView supportButton;
    private MainMenuObjectView aboutButton;
    private int screenWidth;
    private int screenHeight;
    public OpeningTimesView(Context context, Display dis) {
        super(context);

        this.screenWidth = dis.getWidth();
        this.screenHeight = dis.getHeight();

        searchButton = new MainMenuObjectView(context, 200, MovingMode.RIGHT, R.drawable.search, dis);
        supportButton = new MainMenuObjectView(context, 400, MovingMode.LEFT, R.drawable.support, dis);
        aboutButton = new MainMenuObjectView(context, 600, MovingMode.RIGHT, R.drawable.about, dis);

        searchButton.setClickable(true);
        supportButton.setClickable(true);
        aboutButton.setClickable(true);

        searchButton.setOnClickListener(this);
        supportButton.setOnClickListener(this);
        aboutButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View view){
        Toast.makeText(getContext(), "Search button pressed", Toast.LENGTH_SHORT).show();
        if(view == searchButton){
            Toast.makeText(getContext(), "Search button pressed", Toast.LENGTH_SHORT).show();
        }
        else if(view == supportButton){
            Toast.makeText(getContext(), "Support button pressed", Toast.LENGTH_SHORT).show();
        }
        else Toast.makeText(getContext(), "About button pressed", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void onDraw(Canvas canvas)
    {
        // Drawing the buttons
        this.searchButton.onDraw(canvas);
        this.aboutButton.onDraw(canvas);
        this.supportButton.onDraw(canvas);
    }

Merci d'avance, Elad!

25
Elad92

J'ai une solution! Ce n'est pas vraiment une solution à ce problème spécifique, mais une toute nouvelle approche. J'ai envoyé ce fil à quelqu'un que je connais et il m'a dit d'utiliser le SDK Animation. Android a (comme Wireless Designs mentionné), donc au lieu de faire la page du menu principal avec 4 classes, je le fais seulement avec une classe qui étend l'activité, et la classe d'animation offre de nombreuses options d'animation. I tiens à vous remercier tous les deux pour votre aide, vous êtes génial. J'ajoute le code si quelqu'un rencontre ce fil avec le même problème ou quelque chose du genre:

package elad.openapp;

import Android.app.Activity;
import Android.content.Intent;
import Android.os.Bundle;
import Android.view.HapticFeedbackConstants;
import Android.view.View;
import Android.view.View.OnClickListener;
import Android.view.Window;
import Android.view.animation.Animation;
import Android.view.animation.AnimationSet;
import Android.view.animation.ScaleAnimation;
import Android.view.animation.TranslateAnimation;
import Android.widget.ImageView;
import Android.widget.Toast;

public class OpeningTimes extends Activity implements OnClickListener{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    // Disabling the title bar..
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.main);

    // Create the buttons and title objects
    ImageView title = (ImageView)findViewById(R.id.title_main);
    ImageView search = (ImageView)findViewById(R.id.search_button_main);
    ImageView support = (ImageView)findViewById(R.id.support_button_main);
    ImageView about = (ImageView)findViewById(R.id.about_button_main);

    // Setting the onClick listeners
    search.setOnClickListener(this);
    support.setOnClickListener(this);
    about.setOnClickListener(this);

    setButtonsAnimation(title, search, support, about);

}

@Override
public void onClick(View v) {
    if(v.getId()==R.id.search_button_main){
        v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        startActivity(new Intent(this,SearchPage.class));
    }
    else if(v.getId()==R.id.support_button_main){
        v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        Toast.makeText(this, "Coming soon...", Toast.LENGTH_LONG).show();
    }
    else if(v.getId()==R.id.about_button_main){

        v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        Toast.makeText(this, "Coming soon...", Toast.LENGTH_LONG).show();
    }
}

// Setting the animation on the buttons
public void setButtonsAnimation(ImageView title, ImageView search, ImageView support, ImageView about){
    // Title animation (two animations - scale and translate)
    AnimationSet animSet = new AnimationSet(true);

    Animation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f);
    anim.setDuration(750);
    animSet.addAnimation(anim);

    anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f,
            Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f);
    anim.setDuration(750);
    animSet.addAnimation(anim);

    title.startAnimation(animSet);

    // Search button animation
    anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -1.5f, Animation.RELATIVE_TO_SELF, 0.0f,
            Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
    anim.setDuration(750);

    search.startAnimation(anim);

    // Support button animation
    anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1.5f, Animation.RELATIVE_TO_SELF, 0.0f,
            Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
    anim.setDuration(750);

    support.startAnimation(anim);

    // About button animation
    anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f,
            Animation.RELATIVE_TO_SELF, 3f, Animation.RELATIVE_TO_SELF, 0.0f);
    anim.setDuration(750);

    about.startAnimation(anim);
}
}
3
Elad92

Je viens d'avoir le même problème - j'ai créé une vue personnalisée et lorsque j'ai enregistré un nouvel auditeur dans l'activité en appelant v.setOnClickListener(new OnClickListener() {...});, l'écouteur ne s'est tout simplement pas appelé.

Dans ma vue personnalisée, j'ai également remplacé la méthode public boolean onTouchEvent(MotionEvent event) {...}. Le problème était que je n’appelais pas la méthode de la classe View - super.onTouchEvent(event). Cela a résolu le problème. Donc, si vous vous demandez pourquoi votre auditeur ne s'appelle pas, vous avez probablement oublié d'appeler la méthode superclass'es onTouchEvent

20
maddob

La création de contrôles personnalisés dans Android peut être délicate si vous n'êtes pas à l'aise avec le fonctionnement du framework UI. Si vous ne l'avez pas déjà fait, je vous recommanderais de lire ceci:

http://developer.Android.com/guide/topics/ui/declaring-layout.html

http://developer.Android.com/guide/topics/ui/custom-components.html

http://developer.Android.com/guide/topics/ui/layout-objects.html

Notez que lorsque les mises en forme sont déclarées en XML, les éléments sont imbriqués. Cela crée une hiérarchie de présentation que vous devez créer vous-même lorsque vous personnalisez un composant à l'aide de code Java uniquement.

Très probablement, vous êtes pris dans la hiérarchie tactile d'Android. Contrairement à d'autres plates-formes mobiles populaires, Android propose des événements tactiles en commençant par le haut de la hiérarchie de View, puis en descendant. Les classes qui occupent traditionnellement les niveaux les plus élevés de la hiérarchie (Activité et Layouts) ont une logique en elles pour transmettre les contacts qu’elles ne consomment pas elles-mêmes.

Donc, ce que je recommanderais, c’est de changer votre OpeningTimesView pour étendre une ViewGroup (la superclasse de toutes les présentations Android) ou une présentation spécifique (LinearLayout, RelativeLayout, etc.) et d’ajouter vos boutons en tant qu’enfants. À l'heure actuelle, il ne semble pas exister de hiérarchie définie (les boutons ne sont pas vraiment "contenus" dans le conteneur, ils sont juste des membres), ce qui peut prêter à confusion pour savoir où les événements se déroulent réellement.

  1. Les touches doivent plus naturellement descendre jusqu'aux boutons, permettant ainsi à vos événements de clic de se déclencher.
  2. Vous pouvez tirer parti des mécanismes de présentation d'Android pour dessiner votre vue au lieu de vous fier au code de dessin pour faire tout cela.

Choisissez un cours de mise en page pour commencer qui vous aidera à placer vos boutons dans leur emplacementFINAL. Vous pouvez utiliser la structure d'animation sous Android ou du code de dessin personnalisé (comme vous l'avez maintenant) pour les animer comme bon vous semble jusqu'à ce point. L'emplacement d'un bouton et l'emplacement de ce bouton - dessiné peuvent être très différents si nécessaire, et c'est ainsi que l'animation Framework actuel fonctionne sous Android (avant la version 3.0) ... mais c'est un problème distinct . Vous avez également AbsoluteLayout, qui vous permet de placer et de remplacer des objets où bon vous semble ... mais faites attention à l'apparence de votre application sur tous les appareils Android dotés de celui-ci (en fonction des différentes tailles d'écran).

Concernant votre deuxième point concernant les informations d’affichage. La méthode la plus simple consiste probablement à utiliser Context.getResources().getDisplayMetrics() partout où vous en avez besoin. Activity hérite de Context, ils peuvent donc appeler cette méthode directement. Les vues ont toujours une Context à laquelle vous pouvez accéder avec getContext(). Toutes les autres classes pour lesquelles vous pouvez simplement passer la Context en tant que paramètre de construction (il s'agit d'un modèle courant dans Android, vous verrez que de nombreux objets nécessitent une Context, principalement pour accéder à Resources).

Voici un exemple squelette pour lancer des choses. Cela aligne les trois horizontalement une fois comme emplacement final:

Public class OpeningTimesView extends LinearLayout implements OnClickListener {
    private MainMenuObjectView searchButton;
    private MainMenuObjectView supportButton;
    private MainMenuObjectView aboutButton;
    private int screenWidth;
    private int screenHeight;

    public OpeningTimesView(Context context) {
        this(context, null);
    }

    //Thus constructor gets used if you ever instantiate your component from XML
    public OpeningTimesView(Context context, AttributeSet attrs) {
        super(context, attrs);

        /* This is a better way to obtain your screen info
        DisplayMetrics display = context.getResources().getDisplayMetrics();
        screenWidth = display.widthPixels;
        screenHeight = display.heightPixels;
        */
        //This way works also, without needing to customize the constructor
        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        Display dis = wm.getDefaultDisplay();
        screenWidth = dis.getWidth();
        screenHeight = dis.getHeight();

        searchButton = new MainMenuObjectView(context, 200, MovingMode.RIGHT, R.drawable.search, dis);
        supportButton = new MainMenuObjectView(context, 400, MovingMode.LEFT, R.drawable.support, dis);
        aboutButton = new MainMenuObjectView(context, 600, MovingMode.RIGHT, R.drawable.about, dis);

        //Even if they don't extend button, if MainMenuObjectView is always clickable
        // this should probably be brought into that class's constructor
        searchButton.setClickable(true);
        supportButton.setClickable(true);
        aboutButton.setClickable(true);

        searchButton.setOnClickListener(this);
        supportButton.setOnClickListener(this);
        aboutButton.setOnClickListener(this);

        //Add the buttons to the layout (the buttons are now children of the container)
        setOrientation(LinearLayout.HORIZONTAL);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        addView(searchButton, params);
        addView(supportButton, params);
        addView(aboutButton, params);       
    }

    @Override
    public void onClick(View view){
        Toast.makeText(getContext(), "Search button pressed", Toast.LENGTH_SHORT).show();
        if(view == searchButton){
            Toast.makeText(getContext(), "Search button pressed", Toast.LENGTH_SHORT).show();
        }
        else if(view == supportButton){
            Toast.makeText(getContext(), "Support button pressed", Toast.LENGTH_SHORT).show();
        }
        else Toast.makeText(getContext(), "About button pressed", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onDraw(Canvas canvas)
    {
        //Drawing the buttons
        // This may only be necessary until they are in place, then just call super.onDraw(canvas)
        this.searchButton.onDraw(canvas);
        this.aboutButton.onDraw(canvas);
        this.supportButton.onDraw(canvas);
    }    
}

Vous pouvez personnaliser cela à partir de là. Vous pouvez éventuellement démarrer les boutons avec la visibilité définie sur View.INVISIBLE jusqu'à ce que vous les animiez avec votre code de dessin ou un objet Animation personnalisé, puis les rendre visibles dans leur dernier emplacement.

La clé ici, cependant, est la disposition est suffisamment intelligente pour savoir que lorsqu’il reçoit un événement tactile, il est supposé le transmettre à l’enfant correspondant. Vous pouvez créer une vue personnalisée sans cela, mais vous devrez intercepter tous les contacts sur le conteneur et faire le calcul pour déterminer à quelle sous-vue transférer manuellement l'événement. Si vous ne pouvez vraiment pas faire fonctionner un gestionnaire de mise en page, c'est votre recours.

J'espère que cela pourra aider!

15
Devunwired

Vous pouvez simplement appeler performClick () dans onTouchEvent de votre vue personnalisée.

Utilisez ceci dans votre vue personnalisée:

    @Override
    public boolean onTouchEvent(final MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_UP){
            return performClick();
        }
        return true;
    }
9
theJosh

Vous devez appeler setOnClickListener(this) dans le (s) constructeur (s) et implémenter View.OnClickListener sur self. 

De cette façon:

public class MyView extends View implements View.OnClickListener {

    public MyView(Context context) {
        super(context);
        setOnClickListener(this);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Toast.makeText(getContext(), "On click.", Toast.LENGTH_SHORT).show();
    }
}
6

Je fais ça alors:

public class YourView extends LinearLayout implements OnClickListener {
    OnClickListener listener;

    //... constructors

    public void setOnClickListener(OnClickListener listener) {
        this.listener = listener;
    }

    @Override
    public void onClick(View v) {
        if (listener != null)
             listener.onClick(v);
    }
}
5
user1795683

Implémentez onClickListener dans la classe MainMenuObjectView, car ce sont les objets qui répondront aux clics.
Une autre alternative serait d’étendre Button au lieu de View, car vous n’utilisez que des boutons


Mise à jour: exemple complet


C'est l'idée de l'implémenter directement dans les vues cliquables. Il existe une classe TestView qui étend View et remplace onDraw, selon vos besoins, et répond également aux clics. J'ai omis toute implémentation d'animation car vous en avez la partie et ce n'est pas pertinent pour la discussion ClickListener.
Je l'ai testé dans un émulateur Eclair et il fonctionne comme prévu (message Toast après un clic).

fichier: Test.Java

package com.aleadam.test;

import Android.app.Activity;
import Android.os.Bundle;
import Android.widget.LinearLayout;
import Android.widget.TextView;

public class Test extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout ll = new LinearLayout(this);
        ll.setOrientation(LinearLayout.VERTICAL);
        TextView label = new TextView(this);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
             LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        label.setText("Click the circle!");
        TestView testView = new TestView(this);
        ll.addView(label, layoutParams);
        ll.addView(testView, layoutParams);
        setContentView(ll);
    }
}

fichier: TestView.Java

package com.aleadam.test;

import Android.content.Context;
import Android.graphics.Canvas;
import Android.graphics.Color;
import Android.graphics.Paint;
import Android.view.View;
import Android.view.View.OnClickListener;
import Android.widget.Toast;

public class TestView extends View implements OnClickListener {
    Context context;

    public TestView(Context context) {
        super(context);
        this.context = context;
        setOnClickListener(this);
    }

    public void onClick(View arg0) {
        Toast.makeText(context, "View clicked.", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onDraw (Canvas canvas) {
        super.onDraw(canvas);
        this.setBackgroundColor(Color.LTGRAY);
        Paint paint = new Paint (Paint.ANTI_ALIAS_FLAG);
        Paint.setColor(Color.RED);
        canvas.drawCircle(20, 20, 20, Paint);
    }
}

Si vous avez besoin d'éléments cliquables et d'autres non cliquables, vous pouvez ajouter un constructeur avec un argument booléen Pour déterminer si ClickListener est lié ou non à la vue:

public TestView(Context context, boolean clickable) {
    super(context);
    this.context = context;
    if (clickable)
        setOnClickListener(this);
}
3
Aleadam

J'ai eu le même problème. Dans mon cas, j'avais LinearLayout comme élément racine de ma vue personnalisée, avec clickable et focusable définis sur true et la balise de vue personnalisée elle-même (utilisée dans la présentation d'un fragment) était également définie sur clickable et focusable. Il s'avère que la seule chose à faire pour que cela fonctionne est de supprimer tous les attributs clickable et focusable du code XML :) Contre-intuitif, mais cela a fonctionné.

0
Hayuki

ajoutez simplement callOnClick() à votre méthode onTouchEvent() 

public boolean onTouchEvent(MotionEvent event) {
        .....YOURCODE.....
        callOnClick();
        return boolean;
    }
0
Mahmood L Ansary