Android onClick method doesn't work on a custom view - android
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.
The touches should more naturally flow down to the buttons, allowing your click events to trigger
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 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!
You can just call performClick() in onTouchEvent of your custom view.
Use this in you custom view:
#Override
public boolean onTouchEvent(final MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP){
return performClick();
}
return true;
}
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);
}
}
You have to call setOnClickListener(this) in contructor(s) and implement 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();
}
}
I had the same problem. In my case I had a LinearLayout as a root element of my custom view, with clickable and focusable set to true, and the custom view tag itself (used in a fragment's layout) was also set to be clickable and focusable. Turns out that the only thing I had to do to get it working was to remove all the clickable and focusable attributes from within the XML :) Counter-intuitive, but it worked.
Implement the onClickListener in the MainMenuObjectView class, since those are the objects that will respond to clicks.
Another alternative would be to extend Button instead of View, because you are using only buttons in there
Update: Full example
This is the idea to implement it directly into the clickable views. There is a TestView class that extends View and overrides onDraw, as you need it to, and also responds to clicks. I left out any animation implementation as you have that part and it's not relevant 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 and some not clickable, you can add a constructor with a
boolean argument to determine whether the ClickListener is attached or not to the View:
public TestView(Context context, boolean clickable) {
super(context);
this.context = context;
if (clickable)
setOnClickListener(this);
}
I've got a solution!
It's not really a solution for this specific issue, but a whole new approach.
I sent this thread to somebody I know and he told me to use the Animation SDK the android has (like Wireless Designs mentioned), so instead of doing the main menu page with 4 classes, I'm doing it only with one class that extends Activity, and the Animation class offers many animation options.
I want to thank you both for helping me, you are great.
I'm adding the code if someone will encounter this thread with 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);
}
}
In my case I had a RelativeLayout as a parent in my custom view and the only way to make it work was to set focusable and clickable to true in the RelativeLayout and in the constructor of the custom view, after 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();
}
});
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); }
}
implement View.OnClickListener, and hence have an
onClick(View v)
in setOnClickListener, "remember" the listener set from the outside world
set the actual listener to be us (using "super." ...)
in onClick do you custom stuff, and then call the outside world's onClick
just add callOnClick() to your onTouchEvent() method
public boolean onTouchEvent(MotionEvent event) {
.....YOURCODE.....
callOnClick();
return boolean;
}
Related
Android Fragment Animation for custom views
I can't seem to find an answer for my question on google or SO so let me try to explain it properly. I have A Main Activity, which creates a Custom Fragment. This CustomFragment then contains several Custom "extended" Buttons (views) in which An ObjectAnimator is applied to them. That ObjectAnimator does not update on the main thread (I'm assuming), even though isRunning() returns true. ObjectAnimator works perfectly fine inside of the CustomFragment, but when using it inside of the Extended Button class, it fails. I'm thinking I need to be using runOnUiThread() or some other way to get it to run on the proper thread, but I haven't been able to find a good solution. Here's some psuedocode: public class TestButton extends Button{ private AnimatorSet as; public TestButton(Context context, AttributeSet attrs){ super(context, attrs); init(); } private void init(){ ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 0.2f, 1.0f); as = new AnimatorSet(); as.play(alphaAnimator); as.setDuration(1000); } public void startAnimation(){ as.start(); //tried adding invalidate(); here but that didn't seem to fix it either } } public class CustomFragment extends android.support.v4.app.Fragment implements Runnable { private View root; private ArrayList<TestButton> buttons; public View onCreateView(LayoutInflater inflater, ViewGroup container, bundle SavedInstanceState){ root = inflater.inflate(R.layout.fraglayout, container, false); initButtons(); return view; } private void initButtons(){ buttons = new ArrayList<TestButton>(); //Code here to initialize and find the buttons in the layout, this works properly //so I won't define it here because it is long, the list does contain its members // } private void startTest(View view){ //An actual "Button" controls this, and is called properly //THE FOLLOWING WORKS PERFECTLY FINE ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(view, "alpha", 0.2f, 1.0f); alphaAnimator.setDuration(1000); alphaAnimator.start(); //THIS DOES NOT WORK AT ALL, JUST CHANGES THE VIEW ONCE BUT ANIMATION DOESNT 'RUN' for(TestButton b : buttons){ b.startAnimation(); } } #Override public void run(){ root.postDelayed(this,1000); } } It must be something with the threading, but I'm not sure what it could be. Any help is appreciated, Thanks --- Edit --- Okay, So I was just messing around with the thread and added a simple boolean to test whether or not to start the animations in the Fragment's Run() function. And it does work for animation, however this doesn't seem like the proper way to do it because I'm gonna have tons of booleans to check so there must be some other way to run a function on a thread, going back to runOnUiThread() But I'm not sure how to use it. This is what I did that works in premise, but isn't proper programming. private void startTest(View view){ tester = true; } #Override public void run(){ if(tester){ for(TestButton b : buttons) b.startAnimation(); tester = false; } root.postDelayed(this,1000); } --- Edit 2 --- This seems to work, and helped me out. If anyone else needs this in the future.
Following this guide, this seems to work perfectly. public class TestButton extends Button{ private AnimatorSet as; private Handler handler; public TestButton(Context context, AttributeSet attrs){ super(context, attrs); handler = new Handler(context.getMainLooper()); init(); } private void init(){ //Does get called properly ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 0.2f, 1.0f); as = new AnimatorSet(); as.play(alphaAnimator); as.setDuration(1000); } public void startAnimation(){ runOnUiThread(new Runnable(){ #Override public void run(){ as.start(); } }); as.start(); } private void runOnUiThread(Runnable r){ handler.post(r); } }
The setOnClickListener method for each View
I wrote the following code and it works well. But I have other purpose. I want to click only on a view to doing the operations.First, Please see the following image: MY CODE IS AS FOLLOWS: public class MainActivity extends Activity { RelativeLayout relativeLayout; #Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); relativeLayout = new RelativeLayout(getApplicationContext()); setContentView(relativeLayout); A a = new A(this); relativeLayout.addView(a); a.setOnClickListener(new View.OnClickListener() { #Override public void onClick(View v) { B b = new B(getApplicationContext()); relativeLayout.addView(b); } }); } } class A extends View { Paint paint; A(Context context) { super(context); paint = new Paint(); } #Override protected void onDraw(Canvas canvas) { paint.setAntiAlias(true); paint.setColor(Color.RED); canvas.drawRect(20,60,100,150,paint); } } class B extends View { Paint paint; B(Context context){ super(context); paint = new Paint(); } #Override protected void onDraw(Canvas canvas){ paint.setAntiAlias(true); paint.setColor(Color.GREEN); canvas.drawRect(100,150,200,250,paint); } } when I run the above code I can see the green rectangle after press on the red rectangle. But the problem is that when I press another places on the screen I can do this operations also. I want that only I can see the green rectangle to press on the red rectangle and not in the another places on the screen to doing this operations.
Use onTouch event a.setOnTouchListener(new View.OnTouchListener() { #Override public boolean onTouch(View v, MotionEvent event) { if (event.getX(0)>=20 && event.getY(0)>=60 && event.getX(0)<=160 && event.getY(0)<=150) { B b = new B(getApplicationContext()); relativeLayout.addView(b); } return true; } });
You are defining the red square's parameters but not the parameters of the canvas in which you are drawing. You are creating the view (A) without defining the width and height of it, so it is set to match_parent by default, which means it will take the whole size of your RelativeLayout (the whole screen). So, when you click "outside" the red square you are actually clicking the view (A). Try to define an specific height and width for the view in which you are drawing, like this. A a = new A(this); a.setLayoutParams(new RelativeLayout.LayoutParams(300,300)); Remember that LayoutParams takes pixels as parameters, so you should really convert the dps to px as specified here Also, setting some background colors to your views (relativeLayout, A) will help you visualize what you are doing.
# nukeforum, your guess helped me very much. I thank all of you. My problem was exactly from the canvas and its size. I added the following operation in my code and solved my problem. relativeLayout.addView(a,70,70); For A class, I changed as follows: canvas.drawRect(10,20,30,40,paint);
how to display ToolTip in android?
I want to display the ToolTip(QuickAction View) when I am moving my cursor on the view. Can any one please give me the simple example for it? tooltip will only contains the text value.
Possibly using myView.setTooltipText(CharSequence) (from API-level 26) or TooltipCompat (prior to API-level 26) is an additonal option: TooltipCompat.setTooltipText(myView, context.getString(R.string.myString)); Documentation says: Helper class used to emulate the behavior of {#link View#setTooltipText(CharSequence)} prior to API level 26.
Using AndroidX is the recommended way. Android 4.0 (API 14) and higher AndroidX support Library added support for tooltips (small popup windows with descriptive text) for views and menu items. Use setTooltipText to set the tooltip text which will be displayed in a small popup next to the view. See the following example: FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); TooltipCompat.setTooltipText(fab, "Send an email"); The tooltip will be displayed: On long click, unless it is handled otherwise (by OnLongClickListener or a context menu). On hover, after a brief delay since the pointer has stopped moving To add the Appcompat library into your project, Open the build.gradle file for your application. Add the support library to the dependencies section. dependencies { compile "androidx.appcompat:appcompat:1.1.0" } Android 8.0 (API level 26) and higher If your minimum supported version is Android 8.0 (API level 26) and higher, you can specify the tooltip text in a View by calling the setTooltipText() method. You can also set the tooltipText property using the corresponding XML. To specify the tooltip text in your XML files, set the android:tooltipText attribute, as shown in the following example: <android.support.design.widget.FloatingActionButton android:id="#+id/fab" android:tooltipText="Send an email" /> To specify the tooltip text in your code, use the setTooltipText(CharSequence) method, as shown in the following example: FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setTooltipText("Send an email");
Android supports "tool-tip" only for ActionBar buttons from Android 4.0 on. But as Jaguar already mentioned, tool-tips in Android doesnt make so much sense, since there is no concept of hovering. From Android 4.0 the normal title text (that you set in the xml file or via code) will appear if you make a long click on the button. But if enough space is on the screen, it will be visible in the ActionBar all the time beside the icon. If you want to have it for a custom view, you need to implement it yourself by adding a LongClickListener to your view, and show a Toast when pressed long: view.setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { Toast.makeText(v.getContext(), "My tool-tip text", Toast.LENGTH_SHORT).show(); return true; } } Of course you should use a resource for the string, and not the hard coded string.
Starting with Android API 14+, there is an event for hovering. You can do, view.setOnHoverListener(...) and listen for MotionEvents such as ACTION_HOVER_ENTER and ACTION_HOVER_EXIT, instead of onLongClick.
Based on GregoryK's answer, I've created a new ImageButton class - see code below. To use it, all you need to do is replace the ImageButton in your layouts with com.yourpackage.ImageButtonWithToolTip and give it an android:contentDescription attribute (as that is the text that will be shown in the tool tip). package com.yourpackage; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Rect; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.ImageButton; import android.widget.Toast; public class ImageButtonWithToolTip extends ImageButton { private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48; public ImageButtonWithToolTip(Context context) { super(context); init(); } public ImageButtonWithToolTip(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ImageButtonWithToolTip(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } #TargetApi(21) public ImageButtonWithToolTip(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { setOnLongClickListener(new View.OnLongClickListener() { #Override public boolean onLongClick(View view) { /** * You should set the android:contentDescription attribute in this view's XML layout file. */ String contentDescription = getContentDescription().toString(); if (TextUtils.isEmpty(contentDescription)) { /** * There's no content description, so do nothing. */ return false; // Not consumed } else { final int[] screenPos = new int[2]; // origin is device display final Rect displayFrame = new Rect(); // includes decorations (e.g. status bar) view.getLocationOnScreen(screenPos); view.getWindowVisibleDisplayFrame(displayFrame); final Context context = view.getContext(); final int viewWidth = view.getWidth(); final int viewHeight = view.getHeight(); final int viewCenterX = screenPos[0] + viewWidth / 2; final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; final int estimatedToastHeight = (int) (ESTIMATED_TOAST_HEIGHT_DIPS * context.getResources().getDisplayMetrics().density); Toast toolTipToast = Toast.makeText(context, contentDescription, Toast.LENGTH_SHORT); boolean showBelow = screenPos[1] < estimatedToastHeight; if (showBelow) { // Show below // Offsets are after decorations (e.g. status bar) are factored in toolTipToast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, viewCenterX - screenWidth / 2, screenPos[1] - displayFrame.top + viewHeight); } else { // Show above // Offsets are after decorations (e.g. status bar) are factored in // NOTE: We can't use Gravity.BOTTOM because when the keyboard is up // its height isn't factored in. toolTipToast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, viewCenterX - screenWidth / 2, screenPos[1] - displayFrame.top - estimatedToastHeight); } toolTipToast.show(); return true; // Consumed } } }); } } You can use the same approach for extending other views - for example, Button.
Android doesn't have tool tips. It is a touch-based UI. Current touch sensors can't generally detect hovering in a way that tool tips would be useful. There's no concept of "hovering" in a touch screen, but you could set a LongClickListener for your View, and have a Toast appear after a long press.
If you need to show tool tip for any view, you can use CheatSheet util class from Roman Nurik. (Uses Toast and optionally content description to show tooltip.) It is Android helper class for showing cheat sheets (tooltips) for icon-only UI elements on long-press. This is already default platform behavior for icon-only action bar items and tabs. This class provides this behavior for any other such UI element
package com.nbfc.tekis.tooltipexample; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.GridView; import it.sephiroth.android.library.tooltip.Tooltip; public class MainActivity extends AppCompatActivity { /*Button button1,button2,button3,button4;*/ #Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void bottomTooltip(View view) { Button button1=(Button)findViewById(R.id.button1); Tooltip.make(this,new Tooltip.Builder() .anchor(button1, Tooltip.Gravity.BOTTOM) .closePolicy(new Tooltip.ClosePolicy() .insidePolicy(true,false) .outsidePolicy(true,false),4000) .activateDelay(900) .showDelay(400) .text("Android tooltip bottom") .maxWidth(600) .withArrow(true) .withOverlay(true) .build()) .show(); } public void topTooltip(View view) { Button button3=(Button)findViewById(R.id.button3); Tooltip.make(this,new Tooltip.Builder() .anchor(button3, Tooltip.Gravity.TOP) .closePolicy(new Tooltip.ClosePolicy() .insidePolicy(true,false) .outsidePolicy(true,false),4000) .activateDelay(900) .showDelay(400) .text("Android tooltip top") .maxWidth(600) .withOverlay(true) .withArrow(true) .build()) .show(); } public void rightTooltip(View view) { Button button2=(Button)findViewById(R.id.button2); Tooltip.make(this,new Tooltip.Builder() .anchor(button2, Tooltip.Gravity.RIGHT) .closePolicy(new Tooltip.ClosePolicy() .insidePolicy(true,false) .outsidePolicy(true,false),4000) .activateDelay(900) .showDelay(400) .text("Android tooltip right") .maxWidth(600) .withArrow(true) .withOverlay(true) .build()) .show(); } public void leftTooltip(View view) { Button button4=(Button)findViewById(R.id.button4); Tooltip.make(this,new Tooltip.Builder() .anchor(button4, Tooltip.Gravity.LEFT) .closePolicy(new Tooltip.ClosePolicy() .insidePolicy(true,false) .outsidePolicy(true,false),4000) .text("Android tooltip left") .maxWidth(600) .withArrow(true) .withOverlay(true) .build()) .show(); } }
add this to your button android:tooltipText="Tooltip text goes here"
Based on ban's Answer, I've created this method. It does not assume anything about the toast size. Simply places the tool tip gravity based on where the view is relative to the window (i.e. left/right/above/below the center of the window). The toast always starts from center of the view and will stretch towards the right/left/bottom/top respectively. See Example private static void setToastGravity(View view, Toast toast) { final Rect viewRect = new Rect(); // view rect final Rect windowRect = new Rect(); // window rect view.getGlobalVisibleRect(viewRect); view.getWindowVisibleDisplayFrame(windowRect); int offsetX; int offsetY; int gravityX; int gravityY; if (viewRect.centerY() > windowRect.centerY()) { // above offsetY = windowRect.bottom - viewRect.centerY(); gravityY = Gravity.BOTTOM; } else { // tooltip below the view offsetY = viewRect.centerY() - windowRect.top; gravityY = Gravity.TOP; } if (viewRect.centerX() > windowRect.centerX()) { // tooltip right of the view offsetX = windowRect.right - viewRect.centerX(); gravityX = Gravity.END; } else { // tooltip left of the view offsetX = viewRect.centerX() - windowRect.left; gravityX = Gravity.START; } toast.setGravity(gravityX | gravityY, offsetX, offsetY); }
https://github.com/skydoves/Balloon This library provides a lightweight, popup like tooltips, fully customizable with an arrows and animations. 100% Kotlin with all necessary documentation. It is actively being managed as well. Here are some gif from their page; another,
I will Happy to help you Please sir Try this -> android-simple-tooltip I hope that will work for you Example : Release Add it in your root build.gradle at the end of repositories: allprojects { repositories { ... maven { url "https://jitpack.io" } } } Add the dependency dependencies { implementation 'com.github.douglasjunior:android-simple-tooltip:0.2.3' } Add this code in your MainActivity class and make an object for your view which will you want to bind with tooltip View yourView = findViewById(R.id.your_view); new SimpleTooltip.Builder(this) .anchorView(yourView) .text("Texto do Tooltip") .gravity(Gravity.END) .animated(true) .transparentOverlay(false) .build() .show();
How do I position ZoomButtonsController in custom ImageView?
Layouting in Android is getting me rather perplexed. I'm slowly implementing a custom ImageView where I'd like to make use of the ZoomButtonsController. However, I would like to decide where the zoom buttons go in the layout and I can't figure out how to move them from the default bottom center position. I have been experimenting with layouting simple views such as buttons in the main activity and this seems to be working as I would guess and expect. In the case of the ZoomButtonsController I would however like to reposition them. I'm using a RelativeLayout as the mail layout and add the ZoomButtonsController within the custom ImageView. The Activity code public class ImageViewActivity extends Activity { /** Called when the activity is first created. */ #Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); CustomImageView imageView = new CustomImageView(this); relativeLayout.addView(imageView); } } The CustomImageView code public class CustomImageView extends ImageView { private ZoomButtonsController mZoomButtons; public CustomImageView(Context context) { super(context); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.FILL_PARENT); mZoomButtons = new ZoomButtonsController(this); mZoomButtons.getZoomControls(); layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL); setLayoutParams(layoutParams); } #Override public boolean onTouchEvent(MotionEvent ev) { Log.d("TAG", "touch"); mZoomButtons.setVisible(true); return true; } } I've tested with WRAP_CONTENT in the parameters, but this only makes the zoom buttons disappear.
As a matter of fact, I couldn't position the ZoomButtonsController in any way and in the end had to accept the default placement.
Android: Change APIDemo example AnimateDrawable.java to have a ClickEvent-Handler
I love the API Demo examples from the Android webpage and used the AnimateDrawable.java to get started with a WaterfallView with several straight falling images which works great. Now I like the images to stop when they are clicked. I found out that Drawables can't handle events so I changed AnimateDrawable and ProxyDrawable to be extended from View instead and added a Click-Event-Listener and Handler on the parent WaterfallView. The animation still works great, but the handler doesn't, probably because in AnimateDrawable the whole canvas is shifted when the drawabled are animated. How can I change that example so that I can implement an event handler? Or is there a way to find out where exactly my AnimateDrawables are in the view? So the more general question is: How to add an Event Listener / Handler to an animated View? Here are my changes to the example above: AnimateView and ProxyView instead of AnimateDrawable and ProxyDrawable ProxyView extended from View and all super calls changed to mProxy I commented out mutate() The context is still the main Activity which is passed down in the constructors In the constructors of AnimateView setClickable(true) and setFocusable(true) are called And here is the important source code of the parent/main WaterfallView: public class WaterfallView extends View implements OnClickListener { private Context mContext; // PictureEntry is just a value object to manage the pictures private Vector<PictureEntry> pictures = new Vector<PictureEntry>(); public WaterfallView(Context context) { super(context); mContext = context; pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_0))); pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_1))); pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_2))); pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_3))); pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_4))); pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_5))); pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_6))); pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_7))); } #Override protected void onDraw(Canvas canvas) { if(!setup) { for(PictureEntry pic : pictures) pic.setAnimation(createAnimation(pic)); setup = true; } canvas.drawColor(Color.BLACK); for(PictureEntry pic : pictures) pic.getAnimateView().draw(canvas); invalidate(); } private Animation createAnimation(PictureEntry picture) { Drawable dr = picture.getDrawable(); dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); Animation an = new TranslateAnimation(0, 0, -1*dr.getIntrinsicHeight(), this.getHeight()); an.setRepeatCount(-1); an.initialize(10, 10, 10, 10); AnimateView av = new AnimateView(mContext, dr, an); av.setOnClickListener(this); picture.setAnimateView(av); an.startNow(); return an; } #Override public void onClick(View v) { Log.i("MyLog", "clicked "+v); } }
Are you going to be clicking widgets(buttons, checkboxes, etc)? Or do you want to be able to click anywhere? I think you want the latter. So in that case you'll need this method: #Override public boolean onTouchEvent(MotionEvent event) { // do stuff with your event } This method is ONLY called when the event is NOT handled by a view, so I think you may have to remove some of your onClickListener stuff. Refer to here for more info. And as always, experiment.