I've started working on an app. I build the menu yesterday but the onClick method doesn't work! I created a class that extends View and called her MainMenuObject - that class is for any object in the main menu (buttons, logos etc). I've created a special class for them because I'm doing an animation when the menu starts. After I've built the MainMenuObject class I've built another class (OpeningTimesView) that extends View and will have all the buttons of the main menu in it, and will function as the main activity's layout.
Everything was good, the animation went very well and I wanted to put listeners on my buttons, so I've added an implemention of onClickListener to the OpeningTimesView class, and overrided the onClick method. Then I've added the listener to the buttons with setOnClickListener(this) and setClickable(true), but it doesn't work! I've tried everything! Please help me figure out what I'm doing wrong. I've added a toast to the onClick method that doesn't depend on any "if" but it's won't show neither.
(BTW is there any way to define the screen width and height as variable that all classes can access? it can't be static because you get the height and width from a display object but there must be another way)
this is the 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); }
Thanks in advance, Elad!
I just had the same Problem - I created a custom view and when I registered a new Listener for it in the activity by calling v.setOnClickListener(new OnClickListener() {...});
the listener just did not get called.
In my custom view I also overwrote the public boolean onTouchEvent(MotionEvent event) {...}
method. The problem was that I did not call the method of the View class - super.onTouchEvent(event)
. That solved the problem. So if you are wondering why your listener does not get called you have probably forgotten to call the superclass'es onTouchEvent
method
Here is 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 } }
Creating custom controls in Android can be tricky if you aren't comfortable with how the UI Framework operates. If you haven't 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 that when layouts are declared in XML the elements are nested. This creates a layout hierarchy that you must create your self when customizing a component using only Java code.
Most likely you are getting caught up in Android's touch hierarchy. Unlike some other popular mobile platforms, Android delivers touch events starting at the top of the View hierarchy and works its way down. The classes that traditionally occupy the higher levels of the hierarchy (Activity and Layouts) have logic in them to forward touches they don't themselves consume.
So, what I would recommend doing is changing your OpeningTimesView
to extend a ViewGroup
(the superclass of all Android layouts) or a specific layout (LinearLayout
, RelativeLayout
, etc.) and add your buttons as children. Right now, there does not seem to be a defined hierarchy (the buttons aren't really "contained" in the container, they're just members) which may be confusing the issue as to where events are really going.
Pick a layout class to start with that will help you place your buttons in their FINAL locations. You can use the animation framework in Android or custom drawing code (like you have now) to animate them anyway you like up to that point. The location of a button and where that button is currently drawn are allowed to be very different if necessary, and that's how the current Animation Framework works in Android (prior to 3.0)...but that's a separate issue. You also have AbsoluteLayout
, which allows you to place and replace objects anywhere you like...but be careful of how your app looks on all Android devices with this one (given the different screen sizes).
As to your second point about display info. The simplest method is probably just to use Context.getResources().getDisplayMetrics()
wherever you need it. Activity
inherits from Context
, so they can call this method directly. Views always have a Context
you can access with getContext()
. Any other classes you can just pass the Context
as a parameter in construction (this is a common pattern in Android, you'll see many objects require a Context
, mainly to access Resources
).
Here's a skeleton example to jump start things. This just lines the three up horizontally 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 this from there. Perhaps starting the buttons with visibility set to View.INVISIBLE until you animate them in with your drawing code or a custom Animation object, then making them visibile in their final resting place.
The key here, though, is the the layout is smart enough to know that when it receives a touch event it is supposed to forward it to the corresponding child. You can create a custom view without this, but you will have to intercept all touches on the container and do the math to determine which subview to manually forward the event to. If you truly can't make any layout manager work, this is your recourse.
Hope that Helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With