[ACCEPTED]-Android onClick method doesn't work on a custom view-android-custom-view

Accepted answer
Score: 22

I just had the same Problem - I created 10 a custom view and when I registered a new 9 Listener for it in the activity by calling 8 v.setOnClickListener(new OnClickListener() {...}); the listener just did not get called.

In 7 my custom view I also overwrote the public boolean onTouchEvent(MotionEvent event) {...} method. The 6 problem was that I did not call the method 5 of the View class - super.onTouchEvent(event). That solved the problem. So 4 if you are wondering why your listener does 3 not get called you have probably forgotten 2 to call the superclass'es onTouchEvent method

Here is 1 a simple example:

private static class CustomView extends View implements View.OnClickListener {
    public CustomView(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);   // this super call is important !!!
        // YOUR LOGIC HERE
        return true;
    }

    @Override
    public void onClick(View v) {
        // DO SOMETHING HERE
    }
}
Score: 18

Creating custom controls in Android can 65 be tricky if you aren't comfortable with 64 how the UI Framework operates. If you haven't 63 already, I would recommend reading these:

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

Notice 62 that when layouts are declared in XML the 61 elements are nested. This creates a layout 60 hierarchy that you must create your self 59 when customizing a component using only 58 Java code.

Most likely you are getting caught 57 up in Android's touch hierarchy. Unlike 56 some other popular mobile platforms, Android 55 delivers touch events starting at the top 54 of the View hierarchy and works its way 53 down. The classes that traditionally occupy 52 the higher levels of the hierarchy (Activity 51 and Layouts) have logic in them to forward 50 touches they don't themselves consume.

So, what 49 I would recommend doing is changing your 48 OpeningTimesView to extend a ViewGroup (the superclass of all Android 47 layouts) or a specific layout (LinearLayout, RelativeLayout, etc.) and 46 add your buttons as children. Right now, there 45 does not seem to be a defined hierarchy 44 (the buttons aren't really "contained" in 43 the container, they're just members) which 42 may be confusing the issue as to where events 41 are really going.

  1. The touches should more naturally flow down to the buttons, allowing your click events to trigger
  2. You can take advantage of Android's layout mechanisms to draw your view instead of relying on drawing code to do all of that.

Pick a layout class to 40 start with that will help you place your 39 buttons in their FINAL locations. You can use 38 the animation framework in Android or custom 37 drawing code (like you have now) to animate 36 them anyway you like up to that point. The 35 location of a button and where that button 34 is currently drawn are allowed to be very different 33 if necessary, and that's how the current 32 Animation Framework works in Android (prior 31 to 3.0)...but that's a separate issue. You 30 also have AbsoluteLayout, which allows you to place and 29 replace objects anywhere you like...but 28 be careful of how your app looks on all 27 Android devices with this one (given the 26 different screen sizes).

As to your second point about display info. The simplest method 25 is probably just to use Context.getResources().getDisplayMetrics() wherever you need 24 it. Activity inherits from Context, so they can call this 23 method directly. Views always have a Context you 22 can access with getContext(). Any other classes you 21 can just pass the Context as a parameter in construction 20 (this is a common pattern in Android, you'll 19 see many objects require a Context, mainly to access 18 Resources).

Here's a skeleton example to jump start 17 things. This just lines the three up horizontally 16 once as a final location:

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

You can customize 15 this from there. Perhaps starting the buttons 14 with visibility set to View.INVISIBLE until 13 you animate them in with your drawing code 12 or a custom Animation object, then making 11 them visibile in their final resting place.

The 10 key here, though, is the the layout is smart 9 enough to know that when it receives a touch 8 event it is supposed to forward it to the 7 corresponding child. You can create a custom 6 view without this, but you will have to 5 intercept all touches on the container and 4 do the math to determine which subview to 3 manually forward the event to. If you truly 2 can't make any layout manager work, this 1 is your recourse.

Hope that Helps!

Score: 11

You can just call performClick() in onTouchEvent 2 of your custom view.

Use this in you custom 1 view:

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

I do this so:

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

0

Score: 9

You have to call setOnClickListener(this) in contructor(s) and implement 1 View.OnClickListener on self.

In this way:

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();
    }
}
Score: 7

I had the same problem. In my case I had 8 a LinearLayout as a root element of my custom 7 view, with clickable and focusable set to true, and the custom 6 view tag itself (used in a fragment's layout) was 5 also set to be clickable and focusable. Turns out that the 4 only thing I had to do to get it working 3 was to remove all the clickable and focusable attributes from 2 within the XML :) Counter-intuitive, but 1 it worked.

Score: 3

Implement the onClickListener in the MainMenuObjectView class, since those 14 are the objects that will respond to clicks.
Another 13 alternative would be to extend Button instead 12 of View, because you are using only buttons 11 in there


Update: Full example


This is the idea to implement it 10 directly into the clickable views. There 9 is a TestView class that extends View and overrides 8 onDraw, as you need it to, and also responds to 7 clicks. I left out any animation implementation 6 as you have that part and it's not relevant 5 to the ClickListener discussion.
I tested it in an Eclair emulator and it works as expected (a Toast message after a click).

file: 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);
    }
}

file: 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);
    }
}

If you need some clickable 4 and some not clickable, you can add a constructor 3 with a boolean argument to determine whether 2 the ClickListener is attached or not to 1 the View:

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

I've got a solution! It's not really a solution 12 for this specific issue, but a whole new 11 approach. I sent this thread to somebody 10 I know and he told me to use the Animation 9 SDK the android has (like Wireless Designs 8 mentioned), so instead of doing the main 7 menu page with 4 classes, I'm doing it only 6 with one class that extends Activity, and 5 the Animation class offers many animation 4 options. I want to thank you both for helping 3 me, you are great. I'm adding the code if 2 someone will encounter this thread with 1 the same problem or something:

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);
}
}
Score: 3

In my case I had a RelativeLayout as a parent 4 in my custom view and the only way to make 3 it work was to set focusable and clickable to true in the RelativeLayout 2 and in the constructor of the custom view, after 1 inflating the layout, add this:

View view = inflate(getContext(), R.layout.layout_my_custom_view, this);

view.findViewById(R.id.theparent).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            performClick();
        }
    });
Score: 2

It is this easy:

public class FancyButton
 extends FrameLayout
 implements View.OnClickListener { ..

    void yourSetupFunction(Context context, @Nullable AttributeSet attrs) {
        ..
        super.setOnClickListener(this); // NOTE THE SUPER
    }

    OnClickListener consumerListener = null;
    @Override
    public void setOnClickListener(@Nullable OnClickListener l) {
        consumerListener = l;
        // DO NOT CALL SUPER HERE
    }

    @Override
    public void onClick(View v) {
        Log.i("dev","perform my custom functions, and then ...");
        if (consumerListener != null) { consumerListener.onClick(v); }
    }
  1. us implements View.OnClickListener, and hence have an
  2. onClick(View v)
  3. in setOnClickListener, remember the listener set from the outside world
  4. set the actual listener to be us (using "super." ...)
  5. in onClick do you custom stuff, and then call the outside world's onClick

0

Score: 0

just add callOnClick() to your onTouchEvent() method

public boolean onTouchEvent(MotionEvent event) {
        .....YOURCODE.....
        callOnClick();
        return boolean;
    }

0

More Related questions