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();
Related
I have used android.support.design.widget.TextInputLayout to make a password input that allows the user to toggle readability on the password. The xml is as follows:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:hintEnabled="false"
app:passwordToggleDrawable="#drawable/password_toggle_selector"
app:passwordToggleEnabled="true" >
<android.support.design.widget.TextInputEditText
android:id="#+id/password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Password"
android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout>
The drawable selector is as described by How to customize android passwordToggleDrawable
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/password_toggle_show"
android:state_checked="true"/>
<item android:drawable="#drawable/password_toggle_hide"/>
</selector>
The issue is that the custom drawable becomes really large. Not larger than the edittext, but rather it seems to maximize its size while still fitting inside it (so, it seems to be bounded by the height of the element). However, if I leave the passwordToggleDrawable property unset, the drawable for the toggle is sized as is normal for android (I am sure you have seen the icon in other apps before). After much searching I have found a way to resize the custom one, but I am not happy with how its done (requires 2 extra xml files per drawable) and it only works for API 23+.
I would like to know if there is a good way to set the size of the drawable, or better yet, make it target the size of the default drawable?
I have tried setting the padding of the EditText as the source of TextInputLayout says that it gets the four paddings from it and apply to the mPasswordToggleView (line 1143), but it made no change on the icon and (as expected) also affected the padding of the EditText. I have tried setting minheight to 0. I have also tried changing between EditText and TextInputEditText (using the latter now as it seems to be recommended). I have tried switching the layout_height properties to wrap_content. I have tried scaling the drawable using xml's <scale> tag with the scale properties set. I have tried similarly with the <inset> tag. But none of those methods works.
The way I found (and am currently using) to resize the drawable that actually works is by using the xml tag <layer-list>, while setting the width and height properties. Then the <selector> xml file references those resized drawables instead of the png ones. But I don't like this solution because as I mentioned it requires API 23 and because of that results in a total of 4 extra xml files. It also sets the width and height by themselves, instead of keeping the ratio locked.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="#drawable/password_toggle_hide"
android:width="22dp"
android:height="15dp"/>
</layer-list>
TL;DR
How do I set the size of a custom passwordToggleDrawable in TextInputLayout? Preferably to same size as the default drawable.
I know this is an old question, but I faced the same problem and I believe I figure out a simple solution for this.
I'm using the TextInputLayout for the newest material library, and the only thing that I did was to find the reference for the endIcon from the TextInputLayout and change it's minimum dimensions.
val dimension = //here you get the dimension you want to
val endIconImageView = yourTextInputLayout.findViewById<ImageView>(R.id.text_input_end_icon)
endIconImageView.minimumHeight = dimension
endIconImageView.minimumWidth = dimension
yourTextInputLayout.requestLayout()
Important things to notice:
I did this on the OnFinishedInflated from a custom TextInputLayout, but I believe it will work fine on some activity class.
Cheers!
I face same problem. To avoid this situation I used png and set them based dpi like drawable-hdpi, drawable-mdpi etc. Also make those drawable as per radio. Hope that this tricks also work for you.
I were unable to find any solution to the question I actually asked, but I decided to instead solve the issue by disregarding the "in InputTextLayout" part of the question and implemented my own version of the class.
Mostly it is just a copy of InputTextLayout (sadly that class doesnt translate well for subclassing as everything is private) but with most of the stuff I dont need removed, and more importantly, with the CheckableImageButton mPasswordToggleView changed to a ViewGroup containing a View.
The ViewGroup is the clickable button, and handles setMinimumDimensions to keep the clickable area at min 48 dp, like the original did through design_text_input_password_icon.xml. This also makes small drawables not hug the right side of the screen as they are centered in the clickable area, giving the margin that the default drawable appears to have.
The View (or more precisely, a new subclass of it I called CheckableView) is the actual drawable (setBackground()), replacing the CheckableImageButton as the container of the drawable that lets it switch based on state_checked selector.
The xml-property passwordToggleSize allows a dimension to be set, which is used to scale the drawable. I opted to only have one value instead of width&height, and the drawable scales with its ratio locked such that its greatest dimension matches the dimension specified. I made the default size 24dp, as is specified for the default-drawable in design_ic_visibility.xml.
PasswordToggleLayout.java:
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.AbsSavedState;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.TextViewCompat;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.mylifediary.android.client.R;
public class PasswordToggleLayout extends LinearLayout {
// Default values from InputTextLayout's drawable and inflated layout
final int BUTTON_MIN_SIZE = 48; // The button is 48 dp at minimum.
final int DEFAULT_DRAWABLE_SIZE = 24; // The default drawable is 24 dp.
int mButtonMinSize;
final FrameLayout mInputFrame;
EditText mEditText;
private boolean mPasswordToggleEnabled;
private Drawable mPasswordToggleDrawable;
private CharSequence mPasswordToggleContentDesc;
ViewGroup mPasswordToggleViewGroup;
CheckableView mPasswordToggleView;
private boolean mPasswordToggledVisible;
private int mPasswordToggleSize;
private Drawable mPasswordToggleDummyDrawable;
private Drawable mOriginalEditTextEndDrawable;
private ColorStateList mPasswordToggleTintList;
private boolean mHasPasswordToggleTintList;
public PasswordToggleLayout(Context context) {
this(context, null);
}
public PasswordToggleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PasswordToggleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
setWillNotDraw(false);
setAddStatesFromChildren(true);
mButtonMinSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, BUTTON_MIN_SIZE,
getResources().getDisplayMetrics());
mInputFrame = new FrameLayout(context);
mInputFrame.setAddStatesFromChildren(true);
addView(mInputFrame);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PasswordToggleLayout, defStyleAttr,
R.style.Widget_Design_TextInputLayout);
mPasswordToggleEnabled = a.getBoolean(
R.styleable.PasswordToggleLayout_passwordToggleEnabled, false);
mPasswordToggleDrawable = a.getDrawable(
R.styleable.PasswordToggleLayout_passwordToggleDrawable);
mPasswordToggleContentDesc = a.getText(
R.styleable.PasswordToggleLayout_passwordToggleContentDescription);
if (a.hasValue(R.styleable.PasswordToggleLayout_passwordToggleTint)) {
mHasPasswordToggleTintList = true;
mPasswordToggleTintList = a.getColorStateList(
R.styleable.PasswordToggleLayout_passwordToggleTint);
}
mPasswordToggleSize = a.getDimensionPixelSize(
R.styleable.PasswordToggleLayout_passwordToggleSize,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_DRAWABLE_SIZE, getResources().getDisplayMetrics()));
a.recycle();
applyPasswordToggleTint();
}
private void setEditText(EditText editText) {
// If we already have an EditText, throw an exception
if (mEditText != null) {
throw new IllegalArgumentException(
"We already have an EditText, can only have one");
}
mEditText = editText;
final boolean hasPasswordTransformation = hasPasswordTransformation();
updatePasswordToggleView();
}
private void updatePasswordToggleView() {
if (mEditText == null) {
// If there is no EditText, there is nothing to update
return;
}
if (shouldShowPasswordIcon()) {
if (mPasswordToggleView == null) {
// Keep ratio
double w = mPasswordToggleDrawable.getIntrinsicWidth();
double h = mPasswordToggleDrawable.getIntrinsicHeight();
double scale = mPasswordToggleSize / Math.max(w,h);
int scaled_width = (int) (w * scale);
int scaled_height = (int) (h * scale);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_VERTICAL | Gravity.END | Gravity.RIGHT);
FrameLayout.LayoutParams lp2 = new FrameLayout.LayoutParams(
scaled_width, scaled_height, Gravity.CENTER);
mPasswordToggleViewGroup = new FrameLayout(this.getContext());
mPasswordToggleViewGroup.setMinimumWidth(mButtonMinSize);
mPasswordToggleViewGroup.setMinimumHeight(mButtonMinSize);
mPasswordToggleViewGroup.setLayoutParams(lp);
mInputFrame.addView(mPasswordToggleViewGroup);
mPasswordToggleViewGroup.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
passwordVisibilityToggleRequested(false);
}
});
mPasswordToggleView = new CheckableView(this.getContext());
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
mPasswordToggleView.setContentDescription(mPasswordToggleContentDesc);
mPasswordToggleView.setLayoutParams(lp2);
mPasswordToggleViewGroup.addView(mPasswordToggleView);
}
if (mEditText != null && ViewCompat.getMinimumHeight(mEditText) <= 0) {
// We should make sure that the EditText has the same min-height
// as the password toggle view. This ensure focus works properly,
// and there is no visual jump if the password toggle is enabled/disabled.
mEditText.setMinimumHeight(
ViewCompat.getMinimumHeight(mPasswordToggleViewGroup));
}
mPasswordToggleViewGroup.setVisibility(VISIBLE);
mPasswordToggleView.setChecked(mPasswordToggledVisible);
// Need to add a dummy drawable as the end compound drawable so that
// the text is indented and doesn't display below the toggle view.
if (mPasswordToggleDummyDrawable == null) {
mPasswordToggleDummyDrawable = new ColorDrawable();
}
// Important to use mPasswordToggleViewGroup, as mPasswordToggleView
// wouldn't replicate the margin of the default-drawable.
mPasswordToggleDummyDrawable.setBounds(
0, 0, mPasswordToggleViewGroup.getMeasuredWidth(), 1);
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
// Store the user defined end compound drawable so that we can restore it later
if (compounds[2] != mPasswordToggleDummyDrawable) {
mOriginalEditTextEndDrawable = compounds[2];
}
TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0],
compounds[1], mPasswordToggleDummyDrawable, compounds[3]);
// Copy over the EditText's padding so that we match
mPasswordToggleViewGroup.setPadding(mEditText.getPaddingLeft(),
mEditText.getPaddingTop(), mEditText.getPaddingRight(),
mEditText.getPaddingBottom());
} else {
if (mPasswordToggleViewGroup != null
&& mPasswordToggleViewGroup.getVisibility() == VISIBLE) {
mPasswordToggleViewGroup.setVisibility(View.GONE);
}
if (mPasswordToggleDummyDrawable != null) {
// Make sure that we remove the dummy end compound drawable if
// it exists, and then clear it
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
if (compounds[2] == mPasswordToggleDummyDrawable) {
TextViewCompat.setCompoundDrawablesRelative(mEditText,
compounds[0], compounds[1],
mOriginalEditTextEndDrawable, compounds[3]);
mPasswordToggleDummyDrawable = null;
}
}
}
}
private void applyPasswordToggleTint() {
if (mPasswordToggleDrawable != null && mHasPasswordToggleTintList) {
mPasswordToggleDrawable = DrawableCompat.wrap(mPasswordToggleDrawable).mutate();
DrawableCompat.setTintList(mPasswordToggleDrawable, mPasswordToggleTintList);
if (mPasswordToggleView != null
&& mPasswordToggleView.getBackground() != mPasswordToggleDrawable) {
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
}
}
}
private void passwordVisibilityToggleRequested(boolean shouldSkipAnimations) {
if (mPasswordToggleEnabled) {
// Store the current cursor position
final int selection = mEditText.getSelectionEnd();
if (hasPasswordTransformation()) {
mEditText.setTransformationMethod(null);
mPasswordToggledVisible = true;
} else {
mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
mPasswordToggledVisible = false;
}
mPasswordToggleView.setChecked(mPasswordToggledVisible);
if (shouldSkipAnimations) {
mPasswordToggleView.jumpDrawablesToCurrentState();
}
// And restore the cursor position
mEditText.setSelection(selection);
}
}
private boolean hasPasswordTransformation() {
return mEditText != null
&& mEditText.getTransformationMethod() instanceof PasswordTransformationMethod;
}
private boolean shouldShowPasswordIcon() {
return mPasswordToggleEnabled && (hasPasswordTransformation() || mPasswordToggledVisible);
}
#Override
public void addView(View child, int index, final ViewGroup.LayoutParams params) {
if (child instanceof EditText) {
// Make sure that the EditText is vertically at the bottom,
// so that it sits on the EditText's underline
FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params);
flp.gravity = Gravity.CENTER_VERTICAL
| (flp.gravity & ~Gravity.VERTICAL_GRAVITY_MASK);
mInputFrame.addView(child, flp);
// Now use the EditText's LayoutParams as our own and update them
// to make enough space for the label
mInputFrame.setLayoutParams(params);
setEditText((EditText) child);
} else {
// Carry on adding the View...
super.addView(child, index, params);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
updatePasswordToggleView();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.isPasswordToggledVisible = mPasswordToggledVisible;
return ss;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.isPasswordToggledVisible) {
passwordVisibilityToggleRequested(true);
}
requestLayout();
}
static class SavedState extends AbsSavedState {
boolean isPasswordToggledVisible;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel source, ClassLoader loader) {
super(source, loader);
isPasswordToggledVisible = (source.readInt() == 1);
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(isPasswordToggledVisible ? 1 : 0);
}
public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
#Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
#Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in, null);
}
#Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public static class CheckableView extends View {
private final int[] DRAWABLE_STATE_CHECKED =
new int[]{android.R.attr.state_checked};
private boolean mChecked;
public CheckableView(Context context) {
super(context);
}
public CheckableView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public CheckableView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
}
}
#Override
public int[] onCreateDrawableState(int extraSpace) {
if (mChecked) {
return mergeDrawableStates(
super.onCreateDrawableState(extraSpace
+ DRAWABLE_STATE_CHECKED.length), DRAWABLE_STATE_CHECKED);
} else {
return super.onCreateDrawableState(extraSpace);
}
}
}
}
And then in an attrs.xml:
<declare-styleable name="PasswordToggleLayout">
<attr name="passwordToggleEnabled" format="boolean"/>
<attr name="passwordToggleDrawable" format="reference"/>
<attr name="passwordToggleContentDescription" format="string"/>
<attr name="passwordToggleTint" format="color"/>
<attr name="passwordToggleSize" format="dimension"/>
</declare-styleable>
Same issue for me. The problem comes from the gradle material API implementation:
implementation 'com.google.android.material:material:1.1.0'
downgrade to version 1.0.0 fixes the issue:
implementation 'com.google.android.material:material:1.0.0'
So, I have a TextView like so:
<TextView
android:layout_width="fill_parent"
android:layout_height="140.7dp"
android:id="#+id/terminalOutput"
android:layout_marginBottom="0.0dp"
android:scrollbars="vertical"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:maxLines="8" />
I use it as a sort of running log, displayed to the user so they can monitor progress of a task that takes about 3 minutes. However, once I go over 8 lines, the text goes off screen. This is unintuitive to the user because they have no way of knowing that it went off screen, other than to manually poll by trying scroll down.
How can I make it so that every time I add some text to this TextView I make it scroll down as low as it can go?
Also, this is in Xamarin Android, but I don't think it's relevant. It's easy to translate between it and Java
From your Code, two steps have to do:
Step 1
Although you code in Xamarin Android, but as in Java in the xxxActivity.java for terminalOutput invoke must be like this
TextView outputText = (TextView) findViewById(R.id.terminalOutput);
outputText.setMovementMethod(new ScrollingMovementMethod());
Method setMovementMethod() by parameter ScrollingMovementMethod() is the gimmick.
Step 2
And in the layout as in Java-Android also in activity_xxx.xml for the TextView Declaration as above must have to add this
android:gravity="bottom"
When you add new line into the outputText like this:
outputText.append("\n"+"New text line.");
It will scroll to the last line automatically,
and these all the magic for your need.
As per answer here Making TextView Scrollable in Android
You don't need to use a ScrollView actually.
Just set the
android:maxLines = "AN_INTEGER"
android:scrollbars = "vertical"
properties of your TextView in your layout's xml file.
Then use:
yourTextView.setMovementMethod(new ScrollingMovementMethod());
in your code.
That will work..
None of these answers were quite what I wanted, so I came up with this.
textView.append(log);
while (textView.canScrollVertically(1)) {
textView.scrollBy(0, 10);
}
Do not forget to set movement method before scrolling
textView.setMovementMethod(new ScrollingMovementMethod());
Had the same question. Tried several decisions from this and similar discussions, nothing worked. Solved it this way:
edtConsoleText.setSelection(edtConsoleText.getText().length());
after every .append() .
You can try in 2 solutions:
Put TextView in a ScrollView
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/TextView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Your Text" >
</TextView>
</ScrollView>
Use custom scroll TextView (same as traditional TextView but it can scroll)
import android.content.Context;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
import android.widget.Scroller;
import android.widget.TextView;
public class ScrollTextView extends TextView {
// scrolling feature
private Scroller mSlr;
// milliseconds for a round of scrolling
private int mRndDuration = 250;
// the X offset when paused
private int mXPaused = 0;
// whether it's being paused
private boolean mPaused = true;
/*
* constructor
*/
public ScrollTextView(Context context) {
this(context, null);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}
/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}
/*
* constructor
*/
public ScrollTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// customize the TextView
setSingleLine();
setEllipsize(null);
setVisibility(INVISIBLE);
}
/**
* begin to scroll the text from the original position
*/
public void startScroll() {
// begin from the very right side
mXPaused = -1 * getWidth();
// assume it's paused
mPaused = true;
resumeScroll();
}
/**
* resume the scroll from the pausing point
*/
public void resumeScroll() {
if (!mPaused)
return;
// Do not know why it would not scroll sometimes
// if setHorizontallyScrolling is called in constructor.
setHorizontallyScrolling(true);
// use LinearInterpolator for steady scrolling
mSlr = new Scroller(this.getContext(), new LinearInterpolator());
setScroller(mSlr);
int scrollingLen = calculateScrollingLen();
int distance = scrollingLen - (getWidth() + mXPaused);
int duration = (new Double(mRndDuration * distance * 1.00000
/ scrollingLen)).intValue();
setVisibility(VISIBLE);
mSlr.startScroll(mXPaused, 0, distance, 0, duration);
mPaused = false;
}
/**
* calculate the scrolling length of the text in pixel
*
* #return the scrolling length in pixels
*/
private int calculateScrollingLen() {
TextPaint tp = getPaint();
Rect rect = new Rect();
String strTxt = getText().toString();
tp.getTextBounds(strTxt, 0, strTxt.length(), rect);
int scrollingLen = rect.width() + getWidth();
rect = null;
return scrollingLen;
}
/**
* pause scrolling the text
*/
public void pauseScroll() {
if (null == mSlr)
return;
if (mPaused)
return;
mPaused = true;
// abortAnimation sets the current X to be the final X,
// and sets isFinished to be true
// so current position shall be saved
mXPaused = mSlr.getCurrX();
mSlr.abortAnimation();
}
#Override
/*
* override the computeScroll to restart scrolling when finished so as that
* the text is scrolled forever
*/
public void computeScroll() {
super.computeScroll();
if (null == mSlr)
return;
if (mSlr.isFinished() && (!mPaused)) {
this.startScroll();
}
}
public int getRndDuration() {
return mRndDuration;
}
public void setRndDuration(int duration) {
this.mRndDuration = duration;
}
public boolean isPaused() {
return mPaused;
}
}
How to use:
ScrollTextView scrolltext = (ScrollTextView) findViewById(R.id.YourTextView);
(ScrollTextView class source: http://bear-polka.blogspot.com/2009/01/scrolltextview-scrolling-textview-for.html)
From "How to scroll to bottom in a ScrollView on activity startup":
final ScrollView scrollview = ((ScrollView) findViewById(R.id.scrollview));
scrollview.post(new Runnable() {
#Override
public void run() {
scrollview.fullScroll(ScrollView.FOCUS_DOWN);
}
});
What you need is to put fullScroll() after append operation.
I am using Xmarin.
My solution is as many people mentioned, to textView inside a ScrollView.
If you want to see the new line at the bottom of the view, use
android:layout_gravity="bottom"
This keeps the new line at the bottom until you scroll the view. The view stays wherever you have scrolled.
No code is needed.
However, if you want the contents is always at bottom after appending, you need to add code after append():
myText.Append(...);
myscroll.FullScroll(FocusSearchDirection.Down);
For me nothing worked perfectly except a combination of these two :-
Setting scroller.scrollTo(0, 70); in your java class.Use it before setting your textview but after appending that String. 52 dp is the height of my device.You can find it out using scroller.getBottom()); So I used 70 to adjust for the scroll view.
Setting android:scrollY="30dp" in your textview.
In my case, nothing worked at first because I was apparently attempting to make changes to the UI (auto scrolling up after adding text that is out of view), outside of the UI thread. The words would display but with no auto scroll. Correction, setText would work but not append.
My call for auto scroll was not in a UI thread because I coded the call to be made in response to a socket onMessage call; which I run on its own thread. So, I had to enclose my function call with the following code and everything worked.
runOnUiThread(new Runnable() {
#Override
public void run() {
// Stuff that updates the UI
}
});
I'm sure all of the methods would work in someway so long as you are running it on a UI thread. But, to achieve the autoscroll effect for a "chat room" i.e. new text at top of chat window but when amount of text is longer than the window - autoscroll up the new messages into view, i just used the ... fullScroll(View.FOCUS_DOWN) ... method.
i have been developing an android project using libgdx for these days . A question occurred during the period. when soft keyboard' appear , some views will be covered,so i want to get the height for solving this bug.
i know there is a soft input mode can be set to solve this issue when using android api to develop project ,does libgdx provide any way ?
I've got a working solution that I'd like to share.
First off, there is no way to get the soft keyboard height out of the libgdx api. You have to write platform specific code. Interfacing with platform specific code is rather simple - don't worry! Read this guide from the official libgdx wiki.
The following code is native android code, that should be placed in the libgdx android gradle module:
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window;
import com.badlogic.gdx.backends.android.AndroidApplication;
/**
* Container for platform-specific android implementation.
*/
public class PlatformSpecificAndroidImpl implements PlatformSpecificService {
private AndroidApplication androidApplication;
private AndroidGlobalLayoutListener globalLayoutListener;
public PlatformSpecificAndroidImpl(AndroidApplication androidApplication) {
this.androidApplication = androidApplication;
}
/**
* Initialize platform services. This method should be called from the gdx applications "create()" method.
*/
#Override
public void init() {
globalLayoutListener = new AndroidGlobalLayoutListener(androidApplication);
Window window = androidApplication.getWindow();
if (window != null) {
View decorView = window.getDecorView();
if (decorView != null) {
View rootView = decorView.getRootView();
if (rootView != null) {
ViewTreeObserver viewTreeObserver= rootView.getViewTreeObserver();
if (viewTreeObserver != null) {
viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener);
}
}
}
}
}
/**
* Get the window height that is really usable, subtracting the soft-keyboard if open.
* #return usable window height
*/
#Override
public int getUsableWindowHeight() {
if (globalLayoutListener != null) {
return globalLayoutListener.getHeight();
}
return 0;
}
private static class AndroidGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
private AndroidApplication androidApplication;
private int height;
private AndroidGlobalLayoutListener(AndroidApplication androidApplication) {
this.androidApplication = androidApplication;
}
#Override
public void onGlobalLayout() {
height = 0;
Window window = androidApplication.getWindow();
if (window != null) {
View currentFocus = window.getCurrentFocus();
if (currentFocus != null) {
View rootView = currentFocus.getRootView();
if (rootView != null) {
Rect rect = new Rect();
rootView.getWindowVisibleDisplayFrame(rect);
height = rect.bottom;
}
}
}
}
public int getHeight() {
return height;
}
}
}
From within the libgdx core module you can now call the method getUsableWindowHeight() via the PlatformSpecificService interface. It returns the display height (in pixels) minus the soft keyboard height.
Why did I use the OnGlobalLayoutListener and not calculate the height directly in the getter method, when it is requested? Well, in my option it is a slightly more elegant solution this way.
There is one more gotcha to be aware of:
Opening the soft keyboard via Gdx.input.setOnscreenKeyboardVisible(true); involves asynchronous communication. If you happen to call getUsableWindowHeight() directly after setOnscreenKeyboardVisible, you will still get the full display height, because the keyboard did not yet actually open.
Yes you can, with the help of Viewtree Observer and global layout listener, just try below mentioned steps
Get the root view of your layout
get the Viewtree observer for this root, and add a global layout listener on top of this.
now whenever soft keyboard is displayed android will re-size your screen and you will receive call on your listener. That's it only thing you now need to do is calculate difference between height which your root view has after re-size and original size. If difference is more then 150 consider this as a keyboard has been inflated.
Below is a sample code
root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){
public void onGlobalLayout(){
int heightDiff = root.getRootView().getHeight()- root.getHeight();
// IF height diff is more then 150, consider keyboard as visible.
}
});
My requirements are to display a vertical list of views with a scrollbar whenever the total height of the views is bigger than the allowed height for the list.
Additionally, I need to customize the scrollBar appearance (background and thumb) programmatically. (At runtime, my activity will receive all the data to do the rendering : the bitmap to use as scrollbar background, the scrollbar width and the bitmap to use as scrollbar's thumb.)
A ListView seems a good candidate for this, except that I can't find a way to customize the scrollBar programmatically.
I read this question scrollBar in a listView...customizing it. but, it's using a theme, and AFAIK it's not possible the create a new theme programmatically.
So my question(s):
Is there a way to customize the appearance of a scrollbar (within a ListView) programmatically ?
[only if the answer to the first one is "definitively not possible"] do you see any other way to achieve those requirements (and some working example of doing it)
Thanks.
EDIT (23/12)
I found this hidden class android.widget.ScrollBarDrawable. May be a good starting point to build a solution (no time to investigate it right now).
is there a way to customize the appearance of a scrollbar (within a ListView) programmatically ?
Literally, it does not look like it, simply because there are no setters for manipulating it.
It is conceivable that you could create your own BenView subclass of View that replaces the hidden ScrollBarDrawable with BenScrollBarDrawable that handles dynamic changes. But then you would need to create BenViewGroup, BenAbsListView, and BenListView, cloning their Ben-less counterparts' source code, to have them chain up to BenView to inherit this behavior. And, since one of ViewGroup, AbsListView, or ListView is bound to change in any Android release, you will actually need N copies of these classes and choose the right copy based on API level at runtime. And then your app may still behave oddly on some devices where the manufacturer went in and tinkered with ViewGroup, AbsListView, or ListView, and you will not be using their code.
I doubt that this is worth it, particularly since scrollbars are not visible by default except while scrolling.
do you see any other way to achieve those requirements (and some working example of doing it)
Handle your own touch events to do your own scrolling and do your own scrollbar stuff. Whether this is less work than the above solution is up for debate.
I just faced the same problem for RecyclerView and solved it like this
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
/**
* Created on 22.3.2016.
*
* #author Bojan Kseneman
* #description A recycler view that will draw the scroll bar with a different color
*/
public class CustomScrollBarRecyclerView extends RecyclerView {
private int scrollBarColor = Color.RED;
public CustomScrollBarRecyclerView(Context context) {
super(context);
}
public CustomScrollBarRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomScrollBarRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setScrollBarColor(#ColorInt int scrollBarColor) {
this.scrollBarColor = scrollBarColor;
}
/**
* Called by Android {#link android.view.View#onDrawScrollBars(Canvas)}
**/
protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) {
scrollBar.setColorFilter(scrollBarColor, PorterDuff.Mode.SRC_ATOP);
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
}
/**
* Called by Android {#link android.view.View#onDrawScrollBars(Canvas)}
**/
protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) {
scrollBar.setColorFilter(scrollBarColor, PorterDuff.Mode.SRC_ATOP);
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
}
}
These methods are defined in View class, so the same princible should work of other views like ScrollView and ListView.
Just change the android:scrollbarThumbVertical="#drawable/scrollbar_vertical_thumb"
and create a drawable as per your need.
Like
<ListView android:scrollbarThumbVertical="#drawable/scrollbar_vertical_thumb" />
in the Drawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient android:angle="0" android:endColor="#6699FF" android:startColor="#3333FF" />
<corners android:radius="1dp" />
<size android:width="1dp" />
</shape>
I think this is what you want!
If you only need to support API 24 or later, you can use a custom drawable to accomplish this task. You'll need to decide how you're going to allow the drawable to communicate with the activity. Here is one possible solution.
res/drawable/my_custom_scrollbar_thumb.xml
<com.example.RegisteredMutableDrawable android:id="#+id/scroll_thumb" />
res/drawable/my_custom_scrollbar_track.xml
<com.example.RegisteredMutableDrawable android:id="#+id/scroll_track" />
somewhere in your layout file
<ListView
android:scrollbarTrackVertical="#drawable/my_custom_scrollbar_track"
android:scrollbarThumbVertical="#drawable/my_custom_scrollbar_thumb"
the drawable implementation
public class RegisteredMutableDrawable extends DrawableWrapper
{
public static interface Registrar
{
public void register(int _id, RegisteredMutableDrawable _drawable);
}
public static Registrar registrar;
private static int[] ATTRS = new int[] { android.R.attr.id };
public RegisteredMutableDrawable()
{
super(null);
}
public void inflate(Resources _res, XmlPullParser _parser, AttributeSet _as, Theme _theme)
throws XmlPullParserException, IOException
{
super.inflate(_res, _parser, _as, _theme);
final Registrar r = registrar;
if (r != null)
{
final TypedArray ta = _res.obtainAttributes(_as, ATTRS);
final int id = ta.getResourceId(0, 0);
ta.recycle();
r.register(id, this);
}
}
}
in your activity
public class MyActivity extends Activity implements RegisteredMutableDrawable.Registrar
{
#Override public void onCreate(Bundle _icicle)
{
super.onCreate(_icicle);
RegisteredMutableDrawable.registrar = this;
setContentView(R.layout.whatever);
RegisteredMutableDrawable.registrar = null; // don't leak our activity!
}
#Override public void register(int _id, RegisteredMutableDrawable _drawable)
{
switch(_id)
{
case R.id.scroll_thumb:
_drawable.setDrawable(myThumbDrawable);
break;
case R.id.scroll_track:
_drawable.setDrawable(myTrackDrawable);
break;
}
}
}
You can use the jquery plugin(jquery.nicescroll.js) to achieve this. More details visit
http://areaaperta.com/nicescroll/
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;
}