Android recently added the support for resizing TextViews text size based on the view size and the min and max text size.
https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html
Unfortunately, they don't support EditTexts, so Is there any other alternatives for EditText?
I was stuck as you, how EditText is child of TextView but don't support autosize ¿?¿?
I have achieve this with some kind of hack.
First I saw the TextView code to copy and implement as extension (in Kotlin) on EditTextView, but.. there is a bulk of methods, so endly I discard that option.
What I have do, it's to use a TextView invisible (yes I know is a complete hack, am not very happy with that but Google should be ashamed about this)
This is my xmls
<TextView android:id="#+id/invisibleTextView"
android:layout_height="0dp"
android:layout_width="match_parent"
android:focusable="false"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="#dimen/text_min"
app:autoSizeMaxTextSize="#dimen/text_max"
app:autoSizeStepGranularity="#dimen/text_step"
android:textAlignment="center"
app:layout_constraintLeft_toLeftOf="#id/main"
app:layout_constraintRight_toRightOf="#id/main"
app:layout_constraintTop_toBottomOf="#id/textCount"
app:layout_constraintBottom_toBottomOf="#id/main"
android:visibility="invisible"
tool:text="This is a Resizable Textview" />
<EditText android:id="#+id/resizableEditText"
android:layout_height="0dp"
android:layout_width="match_parent"
android:textAlignment="center"
app:layout_constraintLeft_toLeftOf="#id/main"
app:layout_constraintRight_toRightOf="#id/main"
app:layout_constraintTop_toBottomOf="#id/textCount"
app:layout_constraintBottom_toBottomOf="#id/main"
android:maxLength="#integer/max_text_length"
tool:text="This is a Resizable EditTextView" />
Note: It's important both view have the same width/height
Then on my code I use the autocalculations from this textview to use on my EditTextView.
private fun setupAutoresize() {
invisibleTextView.setText("a", TextView.BufferType.EDITABLE) //calculate the right size for one character
resizableEditText.textSize = autosizeText(invisibleTextView.textSize)
resizableEditText.setHint(R.string.text_hint)
resizableEditText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
resizableEditText.textSize = autosizeText(invisibleTextView.textSize)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
textCount.text = currentCharacters.toString()
val text = if (s?.isEmpty() ?: true) getString(R.string.text_hint) else s.toString()
invisibleTextView.setText(text, TextView.BufferType.EDITABLE)
}
})
}
private fun autosizeText(size: Float): Float {
return size / (resources.displayMetrics.density + MARGIN_FACTOR /*0.2f*/)
}
As note, for change the size of hint i use this Android EditText Hint Size.
I know it's a hard workaround, but at least we are sure this will continue working even when resizable change on future versions, while a propetary or abandoned github lib will fail.
I hope some day, google hear us and implement on childs this wonderfull feature, and we could avoid all this stuff
Hope this helps
This library is based on:
AutoFitTextView https://github.com/ViksaaSkool/AutoFitEditText
Please try this one
You can use a RelativeLayout to hide a TextView behind an EditText, both of which will have equal height and width.
The TextView will have android:autoSizeTextType="uniform"
By using setOnTextChangedListener on the EditText, you can set the text of the TextView to whatever is in the EditText. Then the text size of the TextView will be adjusted automatically. Then you have to set the text size of the EditText as same as that of the TextView. Here is the layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools: context=".MainActivity">
<TextView
android:id="#+id/test"
android:layout_width="match_parent" android:layout_height="match_parent" android:padding="0dp"
android:layout_margin="0dp"
android: autoSizeTextType="uniform"
android: autosizeMaxTextSize="500sp" android: autosizestepGranularity="1sp"/>
<EditText
android:id="#+id/edit"
android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="0dp"/>
android:padding="0dp"
</RelativeLayout>
And the code:
public void code(){
edit.setMovement Method (null);
edit.addTextChangedListener(new Textwatcher() {
#Override
public void onTextChanged (CharSequence s, int start, int before, int count) { test.setText (edit.getText().tostring()); edit.setTextSize(pixel2dip(test.getTextSize()));
}
public static float pixel2dip(float a) {
int b = (int) (a);
int c = (int) (b / Resources.getSystem().getDisplayMetrics ().scaledDensity); return (float) (c);
});
}
Here is an option for single-line EditText views that computes the text width after every change and scales up/down the textScale as needed, adhering to a max and min size.
Perhaps someone can improve it to have it work for multiline EditTexts.
// set up auto-text-size
val maxTextScale = binding.editText.textSize
val minTextScale = 0.2 * maxTextScale // ensure text doesn't get too small.
binding.editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val paint = TextPaint(binding.editText.paint)
val desiredTextWidth = StaticLayout.getDesiredWidth(s, paint)
// added so that the text isn't slightly too big
val ensureWiggleRoom = 0.95F
val scaleFactor = binding.editText.width / desiredTextWidth
val candidateTextSize = truncate(binding.editText.textSize * scaleFactor * ensureWiggleRoom)
if (candidateTextSize > minTextScale && candidateTextSize < maxTextScale) {
binding.editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, candidateTextSize)
}
}
override fun afterTextChanged(s: Editable?) {}
})
It works for me. In xml add:
<EditText
android:id="#+id/editTextMsg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0.1"
android:background="#drawable/edit_corner_white"
android:gravity="start|top"
android:inputType="textMultiLine"
android:minLines="1"
android:lines="1"
android:maxLines="20"
android:textSize="18dp"
android:padding="10dp"
android:hint="#string/napisz_cos"
android:imeOptions="actionSend"
/>
and then in activity code:
editTextMsg.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
int div = editTextMsg.getText().toString().length() % 25; //max 25chars in line
if(div == 0 ) {
String tx = editTextMsg.getText().toString().trim() + "\n";
editTextMsg.setText(tx);
int pxH = (int)( editTextMsg.getLineCount() * 24 * getResources().getDisplayMetrics().density);
editTextMsg.setHeight (pxH );
//set cursor to end of text..
editTextMsg.setSelection(editTextMsg.length());
}
}
});
You can try my AutoSizeEditText:
/**
* Wrapper class for {#link EditText}.
* It helps to achieve auto size behaviour which exists in {#link AppCompatTextView}.
* The main idea of getting target text size is measuring {#link AppCompatTextView} and then
* extracting from it text size and then applying extracted text size to target {#link EditText}.
*/
public class AutoSizeEditText extends FrameLayout {
private static final String TEST_SYMBOL = "T";
private static final boolean TEST = false;
/**
* Vertical margin which is applied by default in {#link EditText} in
* comparison to {#link AppCompatTextView}
*/
private static final float VERTICAL_MARGIN = convertDpToPixel(4);
/**
* {#link TextMeasure} which helps to get target text size for {#link #wrappedEditTex}
* via its auto size behaviour.
*/
#NonNull
private final TextMeasure textMeasurer;
/**
* {#link AppCompatEditText} we want to show to the user
*/
#NonNull
private final EditText wrappedEditTex;
public AutoSizeEditText(Context context) {
this(context, null);
}
public AutoSizeEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoSizeEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// don't clip children
setClipChildren(false);
setClipToOutline(false);
setClipToPadding(false);
// using AttributeSet of TextView in order to apply it our text views
wrappedEditTex = createWrappedEditText(context, attrs);
textMeasurer = createTextMeasure(context, attrs, wrappedEditTex);
addView(wrappedEditTex, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
addView(textMeasurer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
#NonNull
private TextMeasure createTextMeasure(Context context, AttributeSet attrs, EditText editText) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.AutoSizeEditText);
final int minSize = (int) typedArray.getDimension(R.styleable.AutoSizeEditText_autoSizeMinTextSize, convertDpToPixel(10));
final int maxSize = (int) typedArray.getDimension(R.styleable.AutoSizeEditText_autoSizeMaxTextSize, convertDpToPixel(18));
final int step = (int) typedArray.getDimension(R.styleable.AutoSizeEditText_autoSizeStepGranularity, convertDpToPixel(1));
typedArray.recycle();
TextMeasure textMeasurer = new TextMeasure(context);
final Editable text = editText.getText();
final CharSequence hint = editText.getHint();
if (!TextUtils.isEmpty(text)) {
textMeasurer.setText(text);
} else if (!TextUtils.isEmpty(hint)) {
textMeasurer.setText(hint);
} else {
textMeasurer.setText(TEST_SYMBOL);
}
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(
textMeasurer, minSize, maxSize, step, TypedValue.COMPLEX_UNIT_PX);
textMeasurer.setVisibility(View.INVISIBLE);
textMeasurer.setPadding(0, 0, 0, 0);
if (TEST) {
textMeasurer.setTextColor(Color.RED);
final ColorDrawable background = new ColorDrawable(Color.YELLOW);
background.setAlpha(50);
textMeasurer.setBackground(background);
textMeasurer.setAlpha(0.2f);
textMeasurer.setVisibility(View.VISIBLE);
}
return textMeasurer;
}
/**
* Creating {#link EditText} which user will use and see
*
* #param attrs {#link AttributeSet} which comes from most likely from xml, which can be user for {#link EditText}
* if attributes of {#link TextView} were declared in xml
* #return created {#link EditText}
*/
#NonNull
protected EditText createWrappedEditText(Context context, AttributeSet attrs) {
return new AppCompatEditText(context, attrs);
}
#NonNull
public EditText getWrappedEditTex() {
return wrappedEditTex;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
wrappedEditTex.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
final int targetHeight = (int) (height
- VERTICAL_MARGIN * 2
- wrappedEditTex.getPaddingTop()
- wrappedEditTex.getPaddingBottom());
final int targetWidth = (width
- wrappedEditTex.getTotalPaddingStart()
- wrappedEditTex.getTotalPaddingEnd());
textMeasurer.measure(
MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(targetHeight, MeasureSpec.EXACTLY)
);
setMeasuredDimension(width, height);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int layoutHeight = getMeasuredHeight();
final int layoutWidth = getMeasuredWidth();
wrappedEditTex.layout(0, 0, layoutWidth, layoutHeight);
if (changed) {
final int measuredHeight = textMeasurer.getMeasuredHeight();
final int measuredWidth = textMeasurer.getMeasuredWidth();
final int topCoordinate = (int) (wrappedEditTex.getPaddingTop() + VERTICAL_MARGIN);
final int leftCoordinate = wrappedEditTex.getTotalPaddingStart();
textMeasurer.layout(
leftCoordinate,
topCoordinate,
measuredWidth + leftCoordinate,
topCoordinate + measuredHeight);
wrappedEditTex.setTextSize(TypedValue.COMPLEX_UNIT_PX, textMeasurer.getTextSize());
}
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return wrappedEditTex.dispatchTouchEvent(ev);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return wrappedEditTex.onTouchEvent(event);
}
/**
* Adjust text size due to the fact we want hint to be always visible
*
* #param hint Hint for {#link #wrappedEditTex}
*/
public void setHint(CharSequence hint) {
wrappedEditTex.setHint(hint);
textMeasurer.setText(hint);
}
/**
* Adjust text size for TypeFace, because it can change calculations
*
* #param typeface for {#link #wrappedEditTex}
*/
public void setTypeface(Typeface typeface) {
wrappedEditTex.setTypeface(typeface);
textMeasurer.setTypeface(typeface);
}
public void setTextColor(Integer textColor) {
wrappedEditTex.setTextColor(textColor);
}
public void setHintTextColor(Integer hintTextColor) {
wrappedEditTex.setHintTextColor(hintTextColor);
}
public void setText(CharSequence text) {
wrappedEditTex.setText(text);
}
private static class TextMeasure extends AppCompatTextView {
public TextMeasure(Context context) {
super(context);
}
#Override
public void setInputType(int type) {
}
#Override
public void setRawInputType(int type) {
}
#Override
public int getInputType() {
return EditorInfo.TYPE_NULL;
}
#Override
public int getMaxLines() {
return 1;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
#Override
public int getMinLines() {
return 1;
}
}
}
Example of using my component as below:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.vladislavkarpman.autosizeedittext.AutoSizeEditText
android:layout_width="300dp"
android:layout_height="100dp"
app:autoSizeMaxTextSize="50dp"
app:autoSizeMinTextSize="4dp"
app:autoSizeStepGranularity="1dp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Related
I want to fit the text size of a TextView and an EditText to to match their bounds.
I searched a bit and found the code below. So I created a new class with the code below:
package com.example.test;
import android.content.Context;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 20;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
* #param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
#Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
* #param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
* #return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
* #param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
* #return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add ellipsis to text that overflows at the smallest text size
* #param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add ellipsis to text that overflows at the smallest text size
* #return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if(mTextSize > 0) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
mMaxTextSize = mTextSize;
}
}
/**
* Resize text after measuring
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if(changed || mNeedsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
* #param width
* #param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no text
if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
return;
}
// Get the text view's paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append an ellipsis
if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, textPaint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if(layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if(lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the ellipsis
while(width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
textPaint.setTextSize(targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if(mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
// Update the text paint object
paint.setTextSize(textSize);
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}
But what do I have to do from there in order for the code to work? This is my code and my XML file. What do I do wrong and what should I do?
Java:
package com.example.test;
import android.app.Activity;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.os.Bundle;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.VideoView;
import com.example.test.AutoResizeTextView;
public class MainActivity extends Activity {
public VideoView vv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, // Set Fullscreen mode, overiding title and BATTERY
WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // SCREEN NEVER GOES TO SLEEP MODE
//*** BACKGROUND MOVIE, LOOPING AND SETTING LOCAL PATH ***
vv = (VideoView) findViewById(R.id.videoView);
vv.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
}
});
Uri url=Uri.parse("android.resource://" + getPackageName() +"/"+ R.raw.bubblessd );
vv.setVideoURI(url);
vv.start();
vv.requestFocus();
//*** BACKGROUND MOVIE, LOOPING AND SETTING LOCAL PATH ***
}
final EditText myet = (EditText) findViewById(R.id.editText1);
myet.resizeText();
}
and XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="#style/AppTheme"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="true"
android:gravity="center"
android:soundEffectsEnabled="false"
tools:context=".MainActivity" >
<VideoView
android:id="#+id/videoView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true" />
<LinearLayout
android:id = "#+id/main_menu_wrapper"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical"
android:weightSum="100">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="20"
android:fitsSystemWindows="true"
android:gravity="center"
android:orientation="horizontal"
android:weightSum="100" >
<com.example.test.AutoResizeTextView
android:id="#+id/textView1"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="20"
android:background="#55000000"
android:text="TextView"
/>
<EditText
android:id="#+id/editText1"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="20"
android:background="#55000000"
android:inputType="text"
android:maxLength="10"
android:shadowColor="#000"
android:textColor="#FFFFFF"
android:textStyle="bold" >
</EditText>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
You are already using it correctly. By defining it in your XML, you tell the inflater to use that class.
If you want to access it in Java, treat it like any other view:
AutoResizeTextView textView = (AutoResizeTextView) findViewById(R.id.textView1);
textView.setText("Hello, user!");
It will behave just like any other text view, except it is supposed to automatically resize the text.
I'm trying the (surely simple?) task of having a TextView follow the thumb on a progress bar and show the progress in the TextView.
The problem is that for progress values of less than half max, the TextView drifts to the left of the thumb, getting more and more left of the correct place and vice versa with progress values more than half max the TextView drifts more and more to the right.
Below is a version of code that reproduces the problem...
Layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".Seekbar_test" >
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="59"
android:layout_marginTop="24dp" />
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />
And .java
package com.example.seekbar_test;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
public class Seekbar_test extends Activity {
SeekBar fade_seek;
TextView fade_text;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_seekbar_test);
fade_seek = (SeekBar) findViewById(R.id.seekBar1);
fade_text = (TextView) findViewById(R.id.textView1);
fade_seek.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
say_minutes_left(progress);
}
});
}
private void say_minutes_left(int how_many)
{
String what_to_say = String.valueOf(how_many);
fade_text.setText(what_to_say);
int seek_label_pos = (int)((float)(fade_seek.getMeasuredWidth()) * ((float)how_many / 60f));
fade_text.setX(seek_label_pos);
}
}
This works for me:
private void say_minutes_left(int how_many)
{
String what_to_say = String.valueOf(how_many);
fade_text.setText(what_to_say);
int seek_label_pos = (((fade_seek.getRight() - fade_seek.getLeft()) * fade_seek.getProgress()) / fade_seek.getMax()) + fade_seek.getLeft();
if (how_many <=9)
{
fade_text.setX(seek_label_pos - 6);
}
else
{
fade_text.setX(seek_label_pos - 11);
}
}
Thanks to Pushpendra Kuntal & flightplanner # unable to get right position of texBox in Thumb of seekbar
The following solution works for API >= 15:
Override SeekBar to create the getThumb() method that is not available on api 15:
public class CustomSeekBar extends SeekBar {
private Drawable thumb;
public CustomSeekBar(Context context) {
super(context);
}
public CustomSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void setThumb(Drawable thumb) {
super.setThumb(thumb);
this.thumb = thumb;
}
#Override
public Drawable getThumb() {
return thumb;
}
}
And use it Like this:
correctX = customSeekBar.getThumb().getBounds().left;
//Get the thumb bound and get its left value
int x = seekBar.getThumb().getBounds().left;
//set the left value to textview x value
textView.setX(x);
use this to move text view according to progress
Works good with Ken Nichols code, but here is a better decision, for example, to center you TextView to current position in ProgressBar(SeekBar):
private void say_minutes_left(int how_many)
{
String what_to_say = String.valueOf(how_many);
fade_text.setText(what_to_say);
int seek_label_pos = (((fade_seek.getRight() - fade_seek.getLeft()) * fade_seek.getProgress()) / fade_seek.getMax()) + fade_seek.getLeft();
fade_text.setX(seek_label_pos - fade_text.getWidth() / 2);
}
If you have padding in your ProgressBar(SeekBar), you can use more general version of this code:
private void say_minutes_left(int how_many)
{
String what_to_say = String.valueOf(how_many);
fade_text.setText(what_to_say);
int left = fade_seek.getLeft() + fade_seek.getPaddingLeft();
int right = fade_seek.getRight() - fade_seek.getPaddingRight();
int seek_label_pos = (((right - left * fade_seek.getProgress()) / fade_seek.getMax()) + left;
fade_text.setX(seek_label_pos - fade_text.getWidth() / 2);
}
Use this , we are calculating the x position based on progress and setting it to textview
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
var xPosition= (((seekBar1.right - seekBar1.left) / seekBar1.max) * seekBar1.progress ) + seekBar1.left
textView1.translationX = xPosition.toFloat() - (textView1.width/2)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
})
TextView must be
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textAppearance="?android:attr/textAppearanceLarge"
/>
and remove it:
int seek_label_pos = (int)((float)(fade_seek.getMeasuredWidth()) * ((float)how_many / 60f));
fade_text.setX(seek_label_pos);
Hope it's help.
I'm trying to make this custom SeekBar in Android 2.2 and everything I do seems to be wrong! I'm trying to display the value of the seekbar over the thumb image of the SeekBar. Does anybody have some experiences with this?
I have followed a different approach which provides more possibilities to customize the thumb. Final output will look like following:
First you have to design the layout which will be set as thumb drawable.
layout_seekbar_thumb.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="#dimen/seekbar_thumb_size"
android:layout_height="#dimen/seekbar_thumb_size"
android:background="#drawable/ic_seekbar_thumb_back"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="#+id/tvProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textColor="#000000"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
Here seekbar_thumb_size can be any small size as per your requirement. I have used 30dp here. For background you can use any drawable/icon of your choice.
Now you need this view to be set as thumb drawable so get it with following code:
View thumbView = LayoutInflater.from(YourActivity.this).inflate(R.layout.layout_seekbar_thumb, null, false);
Here I suggest to initialize this view in onCreate() so no need to inflate it again and again.
Now set this view as thumb drawable when seekBar progress is changed. Add the following method in your code:
public Drawable getThumb(int progress) {
((TextView) thumbView.findViewById(R.id.tvProgress)).setText(progress + "");
thumbView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
Bitmap bitmap = Bitmap.createBitmap(thumbView.getMeasuredWidth(), thumbView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
thumbView.layout(0, 0, thumbView.getMeasuredWidth(), thumbView.getMeasuredHeight());
thumbView.draw(canvas);
return new BitmapDrawable(getResources(), bitmap);
}
Now call this method from onProgressChanged().
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// You can have your own calculation for progress
seekBar.setThumb(getThumb(progress));
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
Note: Also call getThumb() method when you initialize seekBar to initialize it with default value.
With this approach, you can have any custom view on progress change.
I assume you've already extended the base class, so you have something like:
public class SeekBarHint extends SeekBar {
public SeekBarHint (Context context) {
super(context);
}
public SeekBarHint (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SeekBarHint (Context context, AttributeSet attrs) {
super(context, attrs);
}
}
Now you override the onDraw method with some of your own code. Insert the following:
#Override
protected void onDraw(Canvas c) {
super.onDraw(c);
}
Now, you want to draw some text near the thumb, but there isn't a convenient way to get the thumb's x-position. We just need a little math.
#Override
protected void onDraw(Canvas c) {
super.onDraw(c);
int thumb_x = ( (double)this.getProgress()/this.getMax() ) * (double)this.getWidth();
int middle = this.getHeight()/2;
// your drawing code here, ie Canvas.drawText();
}
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
int val = (progress * (seekBar.getWidth() - 2 * seekBar.getThumbOffset())) / seekBar.getMax();
text_seekbar.setText("" + progress);
text_seekbar.setX(seekBar.getX() + val + seekBar.getThumbOffset() / 2);
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
text_seekbar.setVisibility(View.VISIBLE);
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
text_seekbar.setVisibility(View.GONE);
}
});
This worked for me
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int val = (progress * (seekBar.getWidth() - 2 * seekBar.getThumbOffset())) / seekBar.getMax();
_testText.setText("" + progress);
_testText.setX(seekBar.getX() + val + seekBar.getThumbOffset() / 2);
}
Hey I found another solution, seems simpler:
private void setText(){
int progress = mSeekBar.getProgress();
int max= mSeekBar.getMax();
int offset = mSeekBar.getThumbOffset();
float percent = ((float)progress)/(float)max;
int width = mSeekBar.getWidth() - 2*offset;
int answer =((int)(width*percent +offset - mText.getWidth()/2));
mText.setX(answer);
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
setText();
mText.setText(""+progress);
}
This follow code aligns your TextView center to your SeekBar thumb center.
YOUR_TEXT_VIEW width must be wrap_content in xml.
Hope this code will help you.
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
YOUR_TEXT_VIEW.setText(Integer.toString(progress));
double pourcent = progress / (double) seekBar.getMax();
int offset = seekBar.getThumbOffset();
int seekWidth = seekBar.getWidth();
int val = (int) Math.round(pourcent * (seekWidth - 2 * offset));
int labelWidth = YOUR_TEXT_VIEW.getWidth();
YOUR_TEXT_VIEW.setX(offset + seekBar.getX() + val
- Math.round(pourcent * offset)
- Math.round(pourcent * labelWidth/2));
}
I used this library to create drawable text view and put that drawable into thumb programmatically.
https://github.com/amulyakhare/TextDrawable
Code is something like this:
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
String dynamicText = String.valueOf(progress);
TextDrawable drawable = TextDrawable.builder()
.beginConfig()
.endConfig()
.buildRoundRect(dynamicText , Color.WHITE ,20);
seekBar.setThumb(drawable);
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
Works not bad for me
there is a little hardcode)
please write improvements which smbd may has
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v7.widget.AppCompatSeekBar;
import android.util.AttributeSet;
import android.util.TypedValue;
public class CustomSeekBar extends AppCompatSeekBar {
#SuppressWarnings("unused")
private static final String TAG = CustomSeekBar.class.getSimpleName();
private Paint paint;
private Rect bounds;
public String dimension;
public CustomSeekBar(Context context) {
super(context);
init();
}
public CustomSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init(){
paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setTextSize(sp2px(14));
bounds = new Rect();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String label = String.valueOf(getProgress()) + dimension;
paint.getTextBounds(label, 0, label.length(), bounds);
float x = (float) getProgress() * (getWidth() - 2 * getThumbOffset()) / getMax() +
(1 - (float) getProgress() / getMax()) * bounds.width() / 2 - bounds.width() / 2
+ getThumbOffset() / (label.length() - 1);
canvas.drawText(label, x, paint.getTextSize(), paint);
}
private int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}
}
IMO best way is to do it through code. It is really not that scary and we are all programmers after all :)
class ThumbDrawable(context: Context) : Drawable() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val textBounds = Rect()
private var shadowColor = context.resources.getColor(R.color.wallet_screen_option_shadow)
private val size = context.resources.getDimensionPixelSize(R.dimen.thumbRadius).toFloat()
private val textSize = context.resources.getDimensionPixelSize(R.dimen.thumbTextSize).toFloat()
var progress: Int = 0
init {
textPaint.typeface = Typeface.createFromAsset(context.assets, "font/avenir_heavy.ttf")
val accentColor = context.resources.getColor(R.color.accent)
paint.color = accentColor
textPaint.color = accentColor
textPaint.textSize = textSize
paint.setShadowLayer(size / 2, 0f, 0f, shadowColor)
}
override fun draw(canvas: Canvas) {
Timber.d("bounds: $bounds")
val progressAsString = progress.toString()
canvas.drawCircle(bounds.left.toFloat(), bounds.top.toFloat(), size, paint)
textPaint.getTextBounds(progressAsString, 0, progressAsString.length, textBounds)
//0.6f is cause of the avenirs spacing, should be .5 for proper font
canvas.drawText(progressAsString, bounds.left.toFloat() - textBounds.width() * 0.6f, bounds.top.toFloat() - size * 2, textPaint)
}
override fun setAlpha(alpha: Int) {
}
override fun getOpacity(): Int {
return PixelFormat.OPAQUE
}
override fun setColorFilter(colorFilter: ColorFilter?) {
}
}
and in your seekbar implementation
class CustomSeekBar #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
SeekBar(context, attrs, defStyleAttr) {
init {
thumb = ThumbDrawable(context)
setPadding(0, 0, 0, 0);
}
override fun invalidate() {
super.invalidate()
if (thumb is ThumbDrawable) (thumb as ThumbDrawable).progress = progress
}
}
final result is something like this
I created this example to show how textview should be supported to different types of screen size and how to calculate the real position of Thumb because sometimes the position could be 0.
public class CustomProgressBar extends RelativeLayout implements AppCompatSeekBar.OnSeekBarChangeListener {
#BindView(R.id.userProgressBar)
protected AppCompatSeekBar progressSeekBar;
#BindView(R.id.textPorcent)
protected TextView porcent;
#BindView(R.id.titleIndicator)
protected TextView title;
public CustomProgressBar(Context context) {
super(context);
init();
}
public CustomProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.custom_progressbar_view, this, true);
ButterKnife.bind(this);
setColors(R.color.green, R.color.progress_bar_remaining);
progressSeekBar.setPadding(0, 0, 0, 0);
progressSeekBar.setOnSeekBarChangeListener(this);
}
private void setPorcentTextViewPosition(float widthView) {
int width = CoreUtils.getScreenSize().x;
float xPosition = ((float) progressSeekBar.getProgress() / 100) * width;
float finalPosition = xPosition - (widthView / 2f);
if (width - xPosition < widthView) {
porcent.setX(width - widthView);
} else if (widthView < finalPosition) {
porcent.setX(finalPosition);
}
}
public void setColors(int progressDrawable, int remainingDrawable) {
LayerDrawable layer = (LayerDrawable) progressSeekBar.getProgressDrawable();
Drawable background = layer.getDrawable(0);
Drawable progress = layer.getDrawable(1);
background.setColorFilter(ContextCompat.getColor(getContext(), remainingDrawable), PorterDuff.Mode.SRC_IN);
progress.setColorFilter(ContextCompat.getColor(getContext(), progressDrawable), PorterDuff.Mode.SRC_IN);
}
public void setValues(int progress, int remaining) {
int value = (progress * remaining) / 100;
progressSeekBar.setMax(remaining);
porcent.setText(String.valueOf(value).concat("%"));
porcent.post(new Runnable() {
#Override
public void run() {
setPorcentTextViewPosition(porcent.getWidth());
}
});
progressSeekBar.setProgress(value);
}
public void setTitle(String title) {
this.title.setText(title);
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
Add a TextView to your layout. Add onSeekBarChangeListener.
You will want precision so that the text is exactly in the middle of your seek bar thumb, you have to a little calculation. This is because the width of the text is different. Say, you want to show numbers from 0 to 150. Width of 188 will be different from 111. Because of this, the text you are showing will always tilt to some side.
The way to solve it is to measure the width of the text, remove that from the width of the seekbar thumb, divide it by 2, and add that to the result that was given in the accepted answer. Now you would not care about how large the number range. Here is the code:
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
val value = progress * (seekBar.width - 2 * seekBar.thumbOffset) / seekBar.max
label.text = progress.toString()
label.measure(0, 0)
val textWidth = label.measuredWidth
val firstRemainder = seekThumbWidth - textWidth
val result = firstRemainder / 2
label.x = (seekBar.x + value + result)
}
<EditText
android:id="#+id/editText2"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:maxLines="5"
android:lines="5">
</EditText>
User can input more than 5 lines, by pressing enter/next row key. How can I limit user input to fixed amount of rows with EditText?
<EditText
android:id="#+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"
/>
You just need to make sure you have the attribute "inputType" set. It doesn't work without this line.
android:inputType="text"
The attribute maxLines corresponds to the maximum height of the EditText, it controls the outer boundaries and not inner text lines.
This does not solve the general issue of limiting to n lines. If you want to limit your EditText to take just 1 line of text, this can be very easy.
You can set this in the xml file.
android:singleLine="true"
or programmatically
editText.setSingleLine(true);
#Cedekasem you are right, there isn't a built in "row limiter". But I did built one my self, so if anyone is interested the code is below. Cheers.
et.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// if enter is pressed start calculating
if (keyCode == KeyEvent.KEYCODE_ENTER
&& event.getAction() == KeyEvent.ACTION_UP) {
// get EditText text
String text = ((EditText) v).getText().toString();
// find how many rows it cointains
int editTextRowCount = text.split("\\n").length;
// user has input more than limited - lets do something
// about that
if (editTextRowCount >= 7) {
// find the last break
int lastBreakIndex = text.lastIndexOf("\n");
// compose new text
String newText = text.substring(0, lastBreakIndex);
// add new text - delete old one and append new one
// (append because I want the cursor to be at the end)
((EditText) v).setText("");
((EditText) v).append(newText);
}
}
return false;
}
});
I did something like you guys have been looking for. Here's my LimitedEditText class.
Features:
you can limit lines count in your LimitedEditText component
you can limit characters count in your LimitedEditText component
if you exceed the limit of characters or lines somewhere in the middle of text, cursor won't bring you to the end - it will stay where have you been.
Im turning off listener, because every call of setText() method would recursively call these 3 callback methods in case when user exceeded characters or lines limit.
Code:
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
/**
* EditText subclass created to enforce limit of the lines number in editable
* text field
*/
public class LimitedEditText extends EditText {
/**
* Max lines to be present in editable text field
*/
private int maxLines = 1;
/**
* Max characters to be present in editable text field
*/
private int maxCharacters = 50;
/**
* application context;
*/
private Context context;
public int getMaxCharacters() {
return maxCharacters;
}
public void setMaxCharacters(int maxCharacters) {
this.maxCharacters = maxCharacters;
}
#Override
public int getMaxLines() {
return maxLines;
}
#Override
public void setMaxLines(int maxLines) {
this.maxLines = maxLines;
}
public LimitedEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
}
public LimitedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public LimitedEditText(Context context) {
super(context);
this.context = context;
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
TextWatcher watcher = new TextWatcher() {
private String text;
private int beforeCursorPosition = 0;
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
//TODO sth
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
text = s.toString();
beforeCursorPosition = start;
}
#Override
public void afterTextChanged(Editable s) {
/* turning off listener */
removeTextChangedListener(this);
/* handling lines limit exceed */
if (LimitedEditText.this.getLineCount() > maxLines) {
LimitedEditText.this.setText(text);
LimitedEditText.this.setSelection(beforeCursorPosition);
}
/* handling character limit exceed */
if (s.toString().length() > maxCharacters) {
LimitedEditText.this.setText(text);
LimitedEditText.this.setSelection(beforeCursorPosition);
Toast.makeText(context, "text too long", Toast.LENGTH_SHORT)
.show();
}
/* turning on listener */
addTextChangedListener(this);
}
};
this.addTextChangedListener(watcher);
}
}
I've made simpler solution for this :D
// set listeners
txtSpecialRequests.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
lastSpecialRequestsCursorPosition = txtSpecialRequests.getSelectionStart();
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
txtSpecialRequests.removeTextChangedListener(this);
if (txtSpecialRequests.getLineCount() > 3) {
txtSpecialRequests.setText(specialRequests);
txtSpecialRequests.setSelection(lastSpecialRequestsCursorPosition);
}
else
specialRequests = txtSpecialRequests.getText().toString();
txtSpecialRequests.addTextChangedListener(this);
}
});
You can change the value of 3 in txtSpecialRequests.getLineCount() > 3 to your needs.
android:inputType="text" (or something different to "none")
android:maxLines="1" (and this line)
set editText android:inputType="text"
Here is a InputFilter that limits allowed lines in EditText:
/**
* Filter for controlling maximum new lines in EditText.
*/
public class MaxLinesInputFilter implements InputFilter {
private final int mMax;
public MaxLinesInputFilter(int max) {
mMax = max;
}
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
int newLinesToBeAdded = countOccurrences(source.toString(), '\n');
int newLinesBefore = countOccurrences(dest.toString(), '\n');
if (newLinesBefore >= mMax - 1 && newLinesToBeAdded > 0) {
// filter
return "";
}
// do nothing
return null;
}
/**
* #return the maximum lines enforced by this input filter
*/
public int getMax() {
return mMax;
}
/**
* Counts the number occurrences of the given char.
*
* #param string the string
* #param charAppearance the char
* #return number of occurrences of the char
*/
public static int countOccurrences(String string, char charAppearance) {
int count = 0;
for (int i = 0; i < string.length(); i++) {
if (string.charAt(i) == charAppearance) {
count++;
}
}
return count;
}
}
To add it to EditText:
editText.setFilters(new InputFilter[]{new MaxLinesInputFilter(2)});
This is what i used in my project:
editText.addTextChangedListener(new TextWatcher() {
private String text;
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
text = arg0.toString();
}
public void afterTextChanged(Editable arg0) {
int lineCount = editText.getLineCount();
if(lineCount > numberOfLines){
editText.setText(text);
}
}
});
editText.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
// if enter is pressed start calculating
if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN){
int editTextLineCount = ((EditText)v).getLineCount();
if (editTextLineCount >= numberOfLines)
return true;
}
return false;
}
});
And it worked in all scenarios
Simplest solution:
android:maxLines="3"
...
#Override
public void afterTextChanged(Editable editable) {
// limit to 3 lines
if (editText.getLayout().getLineCount() > 3)
editText.getText().delete(editText.getText().length() - 1, editText.getText().length());
}
You can limit your text according to your no of lines i say around 37 alphabets in one line
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="4"
android:maxLines="4"
android:minLines="4"
android:maxLength="150"
android:gravity="start"
android:background="#efeef5"
android:layout_marginTop="#dimen/pad_10dp"/>
this is one approach. Might help someone.
android:lines="1"
android:maxLines="1"
android:inputType="text
This is an extension of Indrek Kõue answer to Kotlin
input_name.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
#SuppressLint("SetTextI18n")
override fun onTextChanged(
s: CharSequence?,
start: Int,
before: Int,
count: Int
) {
val text = (input_name as EditText).text.toString()
val editTextRowCount = input_name.lineCount
if (editTextRowCount > 15) {
val newText = text.substring(0, text.length - 1)
input_name.setText("")
input_name.append(newText)
}
}
})
<EditText
android:id="#+id/usrusr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:lines="1"
android:maxLines="1"
android:inputType="text"
android:hint="#string/inventory_no" />
Another idea: each time after typing, new text would be saved to a String lastText, only if the number of lines does not exeed the MAX_LINES. If it does, we would set the text of EditText to the last added text (so the changes would be deleted) and notify user to keep it short.
// Set listener to wishDescriptionEditText in order to limit line number
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
// If line account is higher than MAX_LINES, set text to lastText
// and notify user (by Toast)
if (editText.getLineCount() > MAX_LINES) {
editText.setText(lastText);
Toast.makeText(getContext(), "Please keep it short", Toast.LENGTH_LONG).show();
} else {
lastText = editText.getText().toString();
}
}
});
getLineCount() is one option; if you want non-zero values there make sure your view is measured. For soft keyboard onKeyListener won't work so you have to add
addTextChangedListener() that will track text changes as you type. As soon as you get enough lines inside its call backs do whatever you want to limit it: delete characters with getText(), setText() or something more fancy. You can even limit the number of characters using a filter.
Another option is to monitor size of the text with getLineBounds(). This will interact with text gravity/paddign so be careful.
For limit number of characters we can simply use maxLength property of EditText as it will not allow user to enter more characters.
Another way to limit your EditText to one line is the following:
editText2.setTransformationMethod(new SingleLineTransformationMethod());
Note that after applying this transformation method, the enter key creates spaces when pressed. That still satisfies TS' question.
I have added an image right of the text in an EditText widget, using the following XML:
<EditText
android:id="#+id/txtsearch"
...
android:layout_gravity="center_vertical"
android:background="#layout/shape"
android:hint="Enter place,city,state"
android:drawableRight="#drawable/cross" />
But I want to clear the EditText when the embedded image is clicked. How can I do this?
Actually you don't need to extend any class. Let's say I have an EditText editComment with a drawableRight
editComment.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_BOTTOM = 3;
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() >= (editComment.getRight() - editComment.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
// your action here
return true;
}
}
return false;
}
});
we getRawX() because we want to get the actual position of touch on screen, not relative to parent.
To get left side click
if(event.getRawX() <= (editComment.getCompoundDrawables()[DRAWABLE_LEFT].getBounds().width()))
Very, very good, thanks to everyone who contributed to this discussion. So if you don't want to deal with inconvenience of extending the class you can do the following (implemented for the right drawable only)
this.keyword = (AutoCompleteTextView) findViewById(R.id.search);
this.keyword.setOnTouchListener(new RightDrawableOnTouchListener(keyword) {
#Override
public boolean onDrawableTouch(final MotionEvent event) {
return onClickSearch(keyword,event);
}
});
private boolean onClickSearch(final View view, MotionEvent event) {
// do something
event.setAction(MotionEvent.ACTION_CANCEL);
return false;
}
And here's bare-bone listener implementation based on #Mark's answer
public abstract class RightDrawableOnTouchListener implements OnTouchListener {
Drawable drawable;
private int fuzz = 10;
/**
* #param keyword
*/
public RightDrawableOnTouchListener(TextView view) {
super();
final Drawable[] drawables = view.getCompoundDrawables();
if (drawables != null && drawables.length == 4)
this.drawable = drawables[2];
}
/*
* (non-Javadoc)
*
* #see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent)
*/
#Override
public boolean onTouch(final View v, final MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN && drawable != null) {
final int x = (int) event.getX();
final int y = (int) event.getY();
final Rect bounds = drawable.getBounds();
if (x >= (v.getRight() - bounds.width() - fuzz) && x <= (v.getRight() - v.getPaddingRight() + fuzz)
&& y >= (v.getPaddingTop() - fuzz) && y <= (v.getHeight() - v.getPaddingBottom()) + fuzz) {
return onDrawableTouch(event);
}
}
return false;
}
public abstract boolean onDrawableTouch(final MotionEvent event);
}
Consider the following. It's not the most elegant solution but it works, I just tested it.
Create a customized EditText class CustomEditText.java:
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.EditText;
public class CustomEditText extends EditText
{
private Drawable dRight;
private Rect rBounds;
public CustomEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomEditText(Context context) {
super(context);
}
#Override
public void setCompoundDrawables(Drawable left, Drawable top,
Drawable right, Drawable bottom)
{
if(right !=null)
{
dRight = right;
}
super.setCompoundDrawables(left, top, right, bottom);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_UP && dRight!=null)
{
rBounds = dRight.getBounds();
final int x = (int)event.getX();
final int y = (int)event.getY();
//System.out.println("x:/y: "+x+"/"+y);
//System.out.println("bounds: "+bounds.left+"/"+bounds.right+"/"+bounds.top+"/"+bounds.bottom);
//check to make sure the touch event was within the bounds of the drawable
if(x>=(this.getRight()-rBounds.width()) && x<=(this.getRight()-this.getPaddingRight())
&& y>=this.getPaddingTop() && y<=(this.getHeight()-this.getPaddingBottom()))
{
//System.out.println("touch");
this.setText("");
event.setAction(MotionEvent.ACTION_CANCEL);//use this to prevent the keyboard from coming up
}
}
return super.onTouchEvent(event);
}
#Override
protected void finalize() throws Throwable
{
dRight = null;
rBounds = null;
super.finalize();
}
}
Change your layout XML to this (where com.example is your actual project package name):
<com.example.CustomEditText
android:id="#+id/txtsearch"
…
android:layout_gravity="center_vertical"
android:background="#layout/shape"
android:hint="Enter place,city,state"
android:drawableRight="#drawable/cross"
/>
Finally, add this (or something similar) to your activity:
…
CustomEditText et = (CustomEditText) this.findViewById(R.id.txtsearch);
…
I might be a bit off with the calculation of the touch bounds for the nested drawable but you get the idea.
I hope this helps.
I created a useful abstract class DrawableClickListener which implements OnTouchListener.
In addition to the DrawableClickListener class, I also created 4 additional abstract classes which extend the DrawableClickListener class and handle the clicking of the drawable area for the correct quadrant.
LeftDrawableClickListener
TopDrawableClickListener
RightDrawableClickListener
BottomDrawableClickListener
Point to Consider
One thing to consider is that the images are not resized if done this way; thus the images must be scaled correctly before being put into the res/drawable folder(s).
If you define a LinearLayout containing an ImageView and a TextView, it's a lot easier to manipulate the size of the image being displayed.
activity_my.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/myTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="replace this with a variable"
android:textSize="30sp"
android:drawableLeft="#drawable/my_left_image"
android:drawableRight="#drawable/my_right_image"
android:drawablePadding="9dp" />
</RelativeLayout>
MyActivity.java
package com.company.project.core;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MyActivity extends Activity
{
#Override
protected void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_my );
final TextView myTextView = (TextView) this.findViewById( R.id.myTextView );
myTextView.setOnTouchListener( new DrawableClickListener.LeftDrawableClickListener(myTextView)
{
#Override
public boolean onDrawableClick()
{
// TODO : insert code to perform on clicking of the LEFT drawable image...
return true;
}
} );
myTextView.setOnTouchListener( new DrawableClickListener.RightDrawableClickListener(myTextView)
{
#Override
public boolean onDrawableClick()
{
// TODO : insert code to perform on clicking of the RIGHT drawable image...
return true;
}
} );
}
}
DrawableClickListener.java
package com.company.project.core;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.TextView;
/**
* This class can be used to define a listener for a compound drawable.
*
* #author Matthew Weiler
* */
public abstract class DrawableClickListener implements OnTouchListener
{
/* PUBLIC CONSTANTS */
/**
* This represents the left drawable.
* */
public static final int DRAWABLE_INDEX_LEFT = 0;
/**
* This represents the top drawable.
* */
public static final int DRAWABLE_INDEX_TOP = 1;
/**
* This represents the right drawable.
* */
public static final int DRAWABLE_INDEX_RIGHT = 2;
/**
* This represents the bottom drawable.
* */
public static final int DRAWABLE_INDEX_BOTTOM = 3;
/**
* This stores the default value to be used for the
* {#link DrawableClickListener#fuzz}.
* */
public static final int DEFAULT_FUZZ = 10;
/* PRIVATE VARIABLES */
/**
* This stores the number of pixels of "fuzz" that should be
* included to account for the size of a finger.
* */
private final int fuzz;
/**
* This will store a reference to the {#link Drawable}.
* */
private Drawable drawable = null;
/* CONSTRUCTORS */
/**
* This will create a new instance of a {#link DrawableClickListener}
* object.
*
* #param view
* The {#link TextView} that this {#link DrawableClickListener}
* is associated with.
* #param drawableIndex
* The index of the drawable that this
* {#link DrawableClickListener} pertains to.
* <br />
* <i>use one of the values:
* <b>DrawableOnTouchListener.DRAWABLE_INDEX_*</b></i>
*/
public DrawableClickListener( final TextView view, final int drawableIndex )
{
this( view, drawableIndex, DrawableClickListener.DEFAULT_FUZZ );
}
/**
* This will create a new instance of a {#link DrawableClickListener}
* object.
*
* #param view
* The {#link TextView} that this {#link DrawableClickListener}
* is associated with.
* #param drawableIndex
* The index of the drawable that this
* {#link DrawableClickListener} pertains to.
* <br />
* <i>use one of the values:
* <b>DrawableOnTouchListener.DRAWABLE_INDEX_*</b></i>
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public DrawableClickListener( final TextView view, final int drawableIndex, final int fuzz )
{
super();
this.fuzz = fuzz;
final Drawable[] drawables = view.getCompoundDrawables();
if ( drawables != null && drawables.length == 4 )
{
this.drawable = drawables[drawableIndex];
}
}
/* OVERRIDDEN PUBLIC METHODS */
#Override
public boolean onTouch( final View v, final MotionEvent event )
{
if ( event.getAction() == MotionEvent.ACTION_DOWN && drawable != null )
{
final int x = (int) event.getX();
final int y = (int) event.getY();
final Rect bounds = drawable.getBounds();
if ( this.isClickOnDrawable( x, y, v, bounds, this.fuzz ) )
{
return this.onDrawableClick();
}
}
return false;
}
/* PUBLIC METHODS */
/**
*
* */
public abstract boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz );
/**
* This method will be fired when the drawable is touched/clicked.
*
* #return
* <code>true</code> if the listener has consumed the event;
* <code>false</code> otherwise.
* */
public abstract boolean onDrawableClick();
/* PUBLIC CLASSES */
/**
* This class can be used to define a listener for a <b>LEFT</b> compound
* drawable.
* */
public static abstract class LeftDrawableClickListener extends DrawableClickListener
{
/* CONSTRUCTORS */
/**
* This will create a new instance of a
* {#link LeftDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link LeftDrawableClickListener} is associated with.
*/
public LeftDrawableClickListener( final TextView view )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_LEFT );
}
/**
* This will create a new instance of a
* {#link LeftDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link LeftDrawableClickListener} is associated with.
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public LeftDrawableClickListener( final TextView view, final int fuzz )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_LEFT, fuzz );
}
/* PUBLIC METHODS */
public boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz )
{
if ( x >= ( view.getPaddingLeft() - fuzz ) )
{
if ( x <= ( view.getPaddingLeft() + drawableBounds.width() + fuzz ) )
{
if ( y >= ( view.getPaddingTop() - fuzz ) )
{
if ( y <= ( view.getHeight() - view.getPaddingBottom() + fuzz ) )
{
return true;
}
}
}
}
return false;
}
}
/**
* This class can be used to define a listener for a <b>TOP</b> compound
* drawable.
* */
public static abstract class TopDrawableClickListener extends DrawableClickListener
{
/* CONSTRUCTORS */
/**
* This will create a new instance of a {#link TopDrawableClickListener}
* object.
*
* #param view
* The {#link TextView} that this
* {#link TopDrawableClickListener} is associated with.
*/
public TopDrawableClickListener( final TextView view )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_TOP );
}
/**
* This will create a new instance of a {#link TopDrawableClickListener}
* object.
*
* #param view
* The {#link TextView} that this
* {#link TopDrawableClickListener} is associated with.
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public TopDrawableClickListener( final TextView view, final int fuzz )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_TOP, fuzz );
}
/* PUBLIC METHODS */
public boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz )
{
if ( x >= ( view.getPaddingLeft() - fuzz ) )
{
if ( x <= ( view.getWidth() - view.getPaddingRight() + fuzz ) )
{
if ( y >= ( view.getPaddingTop() - fuzz ) )
{
if ( y <= ( view.getPaddingTop() + drawableBounds.height() + fuzz ) )
{
return true;
}
}
}
}
return false;
}
}
/**
* This class can be used to define a listener for a <b>RIGHT</b> compound
* drawable.
* */
public static abstract class RightDrawableClickListener extends DrawableClickListener
{
/* CONSTRUCTORS */
/**
* This will create a new instance of a
* {#link RightDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link RightDrawableClickListener} is associated with.
*/
public RightDrawableClickListener( final TextView view )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_RIGHT );
}
/**
* This will create a new instance of a
* {#link RightDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link RightDrawableClickListener} is associated with.
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public RightDrawableClickListener( final TextView view, final int fuzz )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_RIGHT, fuzz );
}
/* PUBLIC METHODS */
public boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz )
{
if ( x >= ( view.getWidth() - view.getPaddingRight() - drawableBounds.width() - fuzz ) )
{
if ( x <= ( view.getWidth() - view.getPaddingRight() + fuzz ) )
{
if ( y >= ( view.getPaddingTop() - fuzz ) )
{
if ( y <= ( view.getHeight() - view.getPaddingBottom() + fuzz ) )
{
return true;
}
}
}
}
return false;
}
}
/**
* This class can be used to define a listener for a <b>BOTTOM</b> compound
* drawable.
* */
public static abstract class BottomDrawableClickListener extends DrawableClickListener
{
/* CONSTRUCTORS */
/**
* This will create a new instance of a
* {#link BottomDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link BottomDrawableClickListener} is associated with.
*/
public BottomDrawableClickListener( final TextView view )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_BOTTOM );
}
/**
* This will create a new instance of a
* {#link BottomDrawableClickListener} object.
*
* #param view
* The {#link TextView} that this
* {#link BottomDrawableClickListener} is associated with.
* #param fuzzOverride
* The number of pixels of "fuzz" that should be
* included to account for the size of a finger.
*/
public BottomDrawableClickListener( final TextView view, final int fuzz )
{
super( view, DrawableClickListener.DRAWABLE_INDEX_BOTTOM, fuzz );
}
/* PUBLIC METHODS */
public boolean isClickOnDrawable( final int x, final int y, final View view, final Rect drawableBounds, final int fuzz )
{
if ( x >= ( view.getPaddingLeft() - fuzz ) )
{
if ( x <= ( view.getWidth() - view.getPaddingRight() + fuzz ) )
{
if ( y >= ( view.getHeight() - view.getPaddingBottom() - drawableBounds.height() - fuzz ) )
{
if ( y <= ( view.getHeight() - view.getPaddingBottom() + fuzz ) )
{
return true;
}
}
}
}
return false;
}
}
}
Kotlin is a great language where each class could be extended with new methods. Lets introduce new method for EditText class which will catch clicks to right drawable.
fun EditText.onRightDrawableClicked(onClicked: (view: EditText) -> Unit) {
this.setOnTouchListener { v, event ->
var hasConsumed = false
if (v is EditText) {
if (event.x >= v.width - v.totalPaddingRight) {
if (event.action == MotionEvent.ACTION_UP) {
onClicked(this)
}
hasConsumed = true
}
}
hasConsumed
}
}
You can see it takes callback function as argument which is called when user clicks to right drawable.
val username = findViewById<EditText>(R.id.username_text)
username.onRightDrawableClicked {
it.text.clear()
}
Its very simple.
Lets say you have a drawable on left side of your EditText 'txtsearch'.
Following will do the trick.
EditText txtsearch = (EditText) findViewById(R.id.txtsearch);
txtsearch.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() <= txtsearch.getTotalPaddingLeft()) {
// your action for drawable click event
return true;
}
}
return false;
}
});
If you want for right drawable change the if statement to:
if(event.getRawX() >= txtsearch.getRight() - txtsearch.getTotalPaddingRight())
Similarly, you can do it for all compound drawables.
txtsearch.getTotalPaddingTop()
txtsearch.getTotalPaddingBottom()
This method call returns all the padding on that side including any drawables. You can use this even for TextView, Button etc.
Click here for reference from android developer site.
I think it is much more easier if we use some tricks :)
Create a image button with your icon and set its background
color to be transparent.
Put the image button on the EditText and of coz the right hand side
Implement the onclick listener of the button to execute your
function
Done
That last contribution's use of contains(x,y) won't work directly on the result of getBounds() (except, by coincidence, when using "left" drawables). The getBounds method only provides the Rect defining points of the drawable item normalized with origin at 0,0 - so, you actually need to do the math of the original post to find out if the click is in the area of the drawable in the context of the containing EditText's dimensions, but change it for top, right, left etc. Alternatively you could describe a Rect that has coordinates actually relative to its position in the EditText container and use contains(), although in the end you're doing the same math.
Combining them both gives you a pretty complete solution, I only added an instance attribute consumesEvent that lets the API user decide if the click event should be passed on or not by using its result to set ACTION_CANCEL or not.
Also, I can't see why the bounds and actionX, actionY values are instance attributes rather than just local on the stack.
Here's a cutout from an implementation based on the above that I put together. It fixes an issue that to properly consume the event you need to return false. It adds a "fuzz" factor to. In my use case of a Voice control icon in an EditText field, I found it hard to click, so the fuzz increases the effective bounds that are considered clicking the drawable. For me 15 worked well. I only needed drawableRight so I didn't plug the math in the others, to save some space, but you see the idea.
package com.example.android;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.EditText;
import android.graphics.Rect;
import com.example.android.DrawableClickListener;
public class ClickableButtonEditText extends EditText {
public static final String LOG_TAG = "ClickableButtonEditText";
private Drawable drawableRight;
private Drawable drawableLeft;
private Drawable drawableTop;
private Drawable drawableBottom;
private boolean consumeEvent = false;
private int fuzz = 0;
private DrawableClickListener clickListener;
public ClickableButtonEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ClickableButtonEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ClickableButtonEditText(Context context) {
super(context);
}
public void consumeEvent() {
this.setConsumeEvent(true);
}
public void setConsumeEvent(boolean b) {
this.consumeEvent = b;
}
public void setFuzz(int z) {
this.fuzz = z;
}
public int getFuzz() {
return fuzz;
}
#Override
public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
if (right != null) {
drawableRight = right;
}
if (left != null) {
drawableLeft = left;
}
super.setCompoundDrawables(left, top, right, bottom);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
int x, y;
Rect bounds;
x = (int) event.getX();
y = (int) event.getY();
// this works for left since container shares 0,0 origin with bounds
if (drawableLeft != null) {
bounds = drawableLeft.getBounds();
if (bounds.contains(x - fuzz, y - fuzz)) {
clickListener.onClick(DrawableClickListener.DrawablePosition.LEFT);
if (consumeEvent) {
event.setAction(MotionEvent.ACTION_CANCEL);
return false;
}
}
} else if (drawableRight != null) {
bounds = drawableRight.getBounds();
if (x >= (this.getRight() - bounds.width() - fuzz) && x <= (this.getRight() - this.getPaddingRight() + fuzz)
&& y >= (this.getPaddingTop() - fuzz) && y <= (this.getHeight() - this.getPaddingBottom()) + fuzz) {
clickListener.onClick(DrawableClickListener.DrawablePosition.RIGHT);
if (consumeEvent) {
event.setAction(MotionEvent.ACTION_CANCEL);
return false;
}
}
} else if (drawableTop != null) {
// not impl reader exercise :)
} else if (drawableBottom != null) {
// not impl reader exercise :)
}
}
return super.onTouchEvent(event);
}
#Override
protected void finalize() throws Throwable {
drawableRight = null;
drawableBottom = null;
drawableLeft = null;
drawableTop = null;
super.finalize();
}
public void setDrawableClickListener(DrawableClickListener listener) {
this.clickListener = listener;
}
}
I have implemented in Kotlin
edPassword.setOnTouchListener { _, event ->
val DRAWABLE_RIGHT = 2
val DRAWABLE_LEFT = 0
val DRAWABLE_TOP = 1
val DRAWABLE_BOTTOM = 3
if (event.action == MotionEvent.ACTION_UP) {
if (event.rawX >= (edPassword.right - edPassword.compoundDrawables[DRAWABLE_RIGHT].bounds.width())) {
edPassword.setText("")
true
}
}
false
}
Extending on the idea by RyanM I have created a more flexible version, which supports all the drawable types (top, bottom, left, right). While the code below extends TextView, adapting it for an EditText is just a case of swapping "extends TextView" with "extends EditText". Instantiation the widget from XML is identical as in RyanM's example, bar the widget name.
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;
import com.example.DrawableClickListener.DrawablePosition;
public class ButtonTextView extends TextView {
private Drawable drawableRight;
private Drawable drawableLeft;
private Drawable drawableTop;
private Drawable drawableBottom;
private int actionX, actionY;
private DrawableClickListener clickListener;
public ButtonTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ButtonTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ButtonTextView(Context context) {
super(context);
}
#Override
public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
if (right != null) {
drawableRight = right;
}
if (left != null) {
drawableLeft = left;
}
if (top != null) {
drawableTop = top;
}
if (bottom != null) {
drawableBottom = bottom;
}
super.setCompoundDrawables(left, top, right, bottom);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
actionX = (int) event.getX();
actionY = (int) event.getY();
if (drawableBottom != null && drawableBottom.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawablePosition.BOTTOM);
return super.onTouchEvent(event);
}
if (drawableTop != null && drawableTop.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawablePosition.TOP);
return super.onTouchEvent(event);
}
if (drawableLeft != null && drawableLeft.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawablePosition.LEFT);
return super.onTouchEvent(event);
}
if (drawableRight != null && drawableRight.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawablePosition.RIGHT);
return super.onTouchEvent(event);
}
}
return super.onTouchEvent(event);
}
#Override
protected void finalize() throws Throwable {
drawableRight = null;
drawableBottom = null;
drawableLeft = null;
drawableTop = null;
super.finalize();
}
public void setDrawableClickListener(DrawableClickListener listener) {
this.clickListener = listener;
}}
The DrawableClickListener is as simple as this:
public interface DrawableClickListener {
public static enum DrawablePosition { TOP, BOTTOM, LEFT, RIGHT };
public void onClick(DrawablePosition target); }
And then the actual implementation:
class example implements DrawableClickListener {
public void onClick(DrawablePosition target) {
switch (target) {
case LEFT:
doSomethingA();
break;
case RIGHT:
doSomethingB();
break;
case BOTTOM:
doSomethingC();
break;
case TOP:
doSomethingD();
break;
default:
break;
}
}}
p.s.: If you don't set the listener, touching the TextView will cause a NullPointerException. You may want to add some more paranoia into the code.
its working for me,
mEditTextSearch.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(s.length()>0){
mEditTextSearch.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(android.R.drawable.ic_delete), null);
}else{
mEditTextSearch.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(R.drawable.abc_ic_search), null);
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
mEditTextSearch.setOnTouchListener(new OnTouchListener() {
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP) {
if(mEditTextSearch.getCompoundDrawables()[2]!=null){
if(event.getX() >= (mEditTextSearch.getRight()- mEditTextSearch.getLeft() - mEditTextSearch.getCompoundDrawables()[2].getBounds().width())) {
mEditTextSearch.setText("");
}
}
}
return false;
}
});
I know this is quite old, but I recently had to do something similar... After seeing how difficult this is, I came up with a much simpler solution:
Create an XML layout that contains the EditText and Image
Subclass FrameLayout and inflate the XML layout
Add code for the click listener and any other behavior you want
In my case, I needed an EditText that had the ability to clear the text with a button. I wanted it to look like SearchView, but for a number of reasons I didn't want to use that class. The example below shows how I accomplished this. Even though it doesn't have to do with focus change, the principles are the same and I figured it would be more beneficial to post actual working code than to put together an example that may not work exactly as I intended:
Here is my layout: clearable_edit_text.xml
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/edit_text_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<!-- NOTE: Visibility cannot be set to "gone" or the padding won't get set properly in code -->
<ImageButton
android:id="#+id/edit_text_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"
android:background="#drawable/ic_cancel_x"
android:visibility="invisible"/>
</merge>
And here is the Class that inflates that layout: ClearableEditText.java
public class ClearableEditText extends FrameLayout {
private boolean mPaddingSet = false;
/**
* Creates a new instance of this class.
* #param context The context used to create the instance
*/
public ClearableEditText (final Context context) {
this(context, null, 0);
}
/**
* Creates a new instance of this class.
* #param context The context used to create the instance
* #param attrs The attribute set used to customize this instance
*/
public ClearableEditText (final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Creates a new instance of this class.
* #param context The context used to create the instance
* #param attrs The attribute set used to customize this instance
* #param defStyle The default style to be applied to this instance
*/
public ClearableEditText (final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.clearable_edit_text, this, true);
}
#Override
protected void onFinishInflate () {
super.onFinishInflate();
final EditText editField = (EditText) findViewById(R.id.edit_text_field);
final ImageButton clearButton = (ImageButton) findViewById(R.id.edit_text_clear);
//Set text listener so we can show/hide the close button based on whether or not it has text
editField.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged (final CharSequence charSequence, final int i, final int i2, final int i3) {
//Do nothing here
}
#Override
public void onTextChanged (final CharSequence charSequence, final int i, final int i2, final int i3) {
//Do nothing here
}
#Override
public void afterTextChanged (final Editable editable) {
clearButton.setVisibility(editable.length() > 0 ? View.VISIBLE : View.INVISIBLE);
}
});
//Set the click listener for the button to clear the text. The act of clearing the text will hide this button because of the
//text listener
clearButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick (final View view) {
editField.setText("");
}
});
}
#Override
protected void onLayout (final boolean changed, final int left, final int top, final int right, final int bottom) {
super.onLayout(changed, left, top, right, bottom);
//Set padding here in the code so the text doesn't run into the close button. This could be done in the XML layout, but then if
//the size of the image changes then we constantly need to tweak the padding when the image changes. This way it happens automatically
if (!mPaddingSet) {
final EditText editField = (EditText) findViewById(R.id.edit_text_field);
final ImageButton clearButton = (ImageButton) findViewById(R.id.edit_text_clear);
editField.setPadding(editField.getPaddingLeft(), editField.getPaddingTop(), clearButton.getWidth(), editField.getPaddingBottom());
mPaddingSet = true;
}
}
}
To make this answer more in line with the question the following steps should be taken:
Change the drawable resource to whatever you want... In my case it was a gray X
Add a focus change listener to the edit text...
Simply copy paste the following code and it does the trick.
editMsg.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_BOTTOM = 3;
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() >= (editMsg.getRight() - editMsg.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
// your action here
Toast.makeText(ChatActivity.this, "Message Sent", Toast.LENGTH_SHORT).show();
return true;
}
}
return false;
}
});
None of the previous solutions worked for me in Xamarin Android. I was able to get the right drawable click listener working using the following:
Create the following OnEditTextTouch event listener:
private void OnEditTextTouch(object sender, View.TouchEventArgs e)
{
var rightDrawable = _autoCompleteTextViewSearch.GetCompoundDrawables()[2];
if (rightDrawable == null || e.Event.Action != MotionEventActions.Up)
{
e.Handled = false;
return;
}
if (e.Event.GetX() >= _autoCompleteTextViewSearch.Width - _autoCompleteTextViewSearch.TotalPaddingRight)
{
// Invoke your desired action here.
e.Handled = true;
}
// Forward the event along to the sender (crucial for default behaviour)
(sender as AutoCompleteTextView)?.OnTouchEvent(e.Event);
}
Subscribe to the Touch event:
_autoCompleteTextViewSearch.Touch += OnEditTextTouch;
I've taked the solution of #AZ_ and converted it in a kotlin extension function:
So copy this in your code:
#SuppressLint("ClickableViewAccessibility")
fun EditText.setDrawableRightTouch(setClickListener: () -> Unit) {
this.setOnTouchListener(View.OnTouchListener { _, event ->
val DRAWABLE_LEFT = 0
val DRAWABLE_TOP = 1
val DRAWABLE_RIGHT = 2
val DRAWABLE_BOTTOM = 3
if (event.action == MotionEvent.ACTION_UP) {
if (event.rawX >= this.right - this.compoundDrawables[DRAWABLE_RIGHT].bounds.width()
) {
setClickListener()
return#OnTouchListener true
}
}
false
})
}
You can use it just calling the setDrawableRightTouch function on your EditText:
yourEditText.setDrawableRightTouch {
//your code
}
A probable solution to the above problem could be using android's new material component TextInputLayout.
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/searchInput"
style="#style/Widget.App.TextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/search"
app:endIconMode="custom"
app:endIconContentDescription="Search"
app:endIconDrawable="#drawable/ic_search">
<EditText
android:id="#+id/et_search"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.google.android.material.textfield.TextInputLayout>
Here the TextInputLayout attribute endIconMode when set, places a button at the end of the enclosed EditText.
Additionally app:endIconMode = "custom" allows customization of the icon's click functonality
Finally to listen to the end icon clicks call setEndIconClickListener() on the enclosing TextInputLayout component.
#Override
public boolean onTouch(View v, MotionEvent event) {
Drawable drawableObj = getResources().getDrawable(R.drawable.search_btn);
int drawableWidth = drawableObj.getIntrinsicWidth();
int x = (int) event.getX();
int y = (int) event.getY();
if (event != null && event.getAction() == MotionEvent.ACTION_UP) {
if (x >= (searchPanel_search.getWidth() - drawableWidth - searchPanel_search.getPaddingRight())
&& x <= (searchPanel_search.getWidth() - searchPanel_search.getPaddingRight())
&& y >= searchPanel_search.getPaddingTop() && y <= (searchPanel_search.getHeight() - searchPanel_search.getPaddingBottom())) {
getSearchData();
}
else {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(searchPanel_search, InputMethodManager.SHOW_FORCED);
}
}
return super.onTouchEvent(event);
}
and if drawable is on the left, this will help you. (for those work with RTL layout)
editComment.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_BOTTOM = 3;
if(event.getAction() == MotionEvent.ACTION_UP) {
if (event.getRawX() <= (searchbox.getLeft() + searchbox.getCompoundDrawables()[DRAWABLE_LEFT].getBounds().width())) {
// your action here
return true;
}
}
return false;
}
});
It is all great but why not to make it really simple?
I have faced with that also not so long ago...and android touchlistiner works great but gives limitation in usage..and I came to another solution and I hope that will help you:
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/zero_row">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ProgressBar
android:id="#+id/loadingProgressBar"
android:layout_gravity="center"
android:layout_width="28dp"
android:layout_height="28dp" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:background="#drawable/edittext_round_corners"
android:layout_height="match_parent"
android:layout_marginLeft="5dp">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
app:srcCompat="#android:drawable/ic_menu_search"
android:id="#+id/imageView2"
android:layout_weight="0.15"
android:layout_gravity="center|right"
android:onClick="OnDatabaseSearchEvent" />
<EditText
android:minHeight="40dp"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/edittext_round_corners"
android:inputType="textPersonName"
android:hint="Search.."
android:textColorHint="#color/AndroidWhite"
android:textColor="#color/AndroidWhite"
android:ems="10"
android:id="#+id/e_d_search"
android:textCursorDrawable="#color/AndroidWhite"
android:layout_weight="1" />
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
app:srcCompat="#drawable/ic_oculi_remove2"
android:id="#+id/imageView3"
android:layout_gravity="center|left"
android:layout_weight="0.15"
android:onClick="onSearchEditTextCancel" />
</LinearLayout>
<!--android:drawableLeft="#android:drawable/ic_menu_search"-->
<!--android:drawableRight="#drawable/ic_oculi_remove2"-->
</LinearLayout>
</LinearLayout>
Now you can create ImageClick listener or event and do what ever you want with text. This edittext_round_corners.xml file
<item android:state_pressed="false" android:state_focused="false">
<shape>
<gradient
android:centerY="0.2"
android:startColor="#color/colorAccent"
android:centerColor="#color/colorAccent"
android:endColor="#color/colorAccent"
android:angle="270"
/>
<stroke
android:width="0.7dp"
android:color="#color/colorAccent" />
<corners
android:radius="5dp" />
</shape>
</item>
Better to have ImageButton on Right of edit text and give negative layout margin to overlap with edit text. Set listener on ImageButton and perform operations.
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp" >
<EditText
android:id="#+id/edt_status_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:background="#drawable/txt_box_blank"
android:ems="10"
android:hint="#string/statusnote"
android:paddingLeft="5dp"
android:paddingRight="10dp"
android:textColor="#android:color/black" />
<Button
android:id="#+id/note_del"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="1dp"
android:layout_marginTop="5dp"
android:background="#android:drawable/ic_delete" />
</FrameLayout>
Compound drawables are not supposed to be clickable.
It is cleaner to use separate views in a horizontal LinearLayout and use a click handler on them.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:background="#color/white"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp"
android:layout_gravity="center_horizontal"
android:orientation="horizontal"
android:translationZ="4dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#color/white"
android:minWidth="40dp"
android:scaleType="center"
app:srcCompat="#drawable/ic_search_map"/>
<android.support.design.widget.TextInputEditText
android:id="#+id/search_edit"
style="#style/EditText.Registration.Map"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:hint="#string/hint_location_search"
android:imeOptions="actionSearch"
android:inputType="textPostalAddress"
android:maxLines="1"
android:minHeight="40dp" />
<ImageView
android:id="#+id/location_gps_refresh"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#color/white"
android:minWidth="40dp"
android:scaleType="center"
app:srcCompat="#drawable/selector_ic_gps"/>
</LinearLayout>
For anyone who does not want to implement the monstrous click handling. You can achieve the same with a RelativeLayout. With that you even have free handling of the positioning of the drawable.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</android.support.design.widget.TextInputLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerInParent="true"
android:src="#drawable/ic_undo"/>
</RelativeLayout>
The ImageView position will be the same as you would use drawableEnd - plus you don't need all the touch listener handling. Just a click listener for the ImageView and you are good to go.
This works fro me:) may this help you as well
edit_account_name.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (event.getRawX() >= (edit_account_name.getRight())) {
//clicked
return true;
}
}
return false;
}
});
I've seen several solutions but I wasn't convinced by any of them. Either very complicated or too simple (non-reusable).
This is my favourite approach at the moment:
mEditText.setOnTouchListener(
new OnEditTextRightDrawableTouchListener(mEditText) {
#Override
public void OnDrawableClick() {
// The right drawable was clicked. Your action goes here.
}
});
And this is the reusable touch listener:
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.EditText;
public abstract class OnEditTextRightDrawableTouchListener implements OnTouchListener {
private final EditText mEditText;
public OnEditTextRightDrawableTouchListener(#NonNull final EditText editText) {
mEditText = editText;
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
final int DRAWABLE_RIGHT_POSITION = 2;
final Drawable drawable = mEditText.getCompoundDrawables()[DRAWABLE_RIGHT_POSITION];
if (drawable != null) {
final float touchEventX = motionEvent.getX();
final int touchAreaRight = mEditText.getRight();
final int touchAreaLeft = touchAreaRight - drawable.getBounds().width();
if (touchEventX >= touchAreaLeft && touchEventX <= touchAreaRight) {
view.performClick();
OnDrawableClick();
}
return true;
}
}
return false;
}
public abstract void OnDrawableClick();
}
You can look at the Gist here.
Follow below code for drawable right,left,up,down click:
edittextview_confirmpassword.setOnTouchListener(new View.OnTouchListener() {
#Override public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_BOTTOM = 3;
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() >= (edittextview_confirmpassword.getRight() - edittextview_confirmpassword.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
// your action here edittextview_confirmpassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
return true;
}
}else{
edittextview_confirmpassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
}
return false;
}
});
}
Here's my simple solution, just place ImageButton over EditText:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText android:id="#+id/editTextName"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:imeOptions="actionSearch"
android:inputType="text"/>
<ImageButton android:id="#+id/imageViewSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_action_search"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"/>
</RelativeLayout>
for left drawable click listener
txt.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
if (event.getAction() == MotionEvent.ACTION_UP) {
if (event.getRawX() <= (txt
.getCompoundDrawables()[DRAWABLE_LEFT].getBounds().width() +
txt.getPaddingLeft() +
txt.getLeft())) {
//TODO do code here
}
return true;
}
}
return false;
}
});
I would like to suggest a way for drawable left!
I tried this code and works.
txtsearch.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
final int DRAWABLE_LEFT = 0;
int start=txtsearch.getSelectionStart();
int end=txtsearch.getSelectionEnd();
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() <= (txtsearch.getLeft() + txtsearch.getCompoundDrawables()[DRAWABLE_LEFT].getBounds().width())) {
//Do your action here
return true;
}
}
return false;
}
});
}
I implemented #aristo_sh answer in Mono.Droid (Xamarin), since it's a delegate anonymous method you can't return true or false you have to take take of e.Event.Handled. I am also hiding the keyboard on click
editText.Touch += (sender, e) => {
e.Handled = false;
if (e.Event.Action == MotionEventActions.Up)
{
if (e.Event.RawX >= (bibEditText.Right - (bibEditText.GetCompoundDrawables()[2]).Bounds.Width()))
{
SearchRunner();
InputMethodManager manager = (InputMethodManager)GetSystemService(InputMethodService);
manager.HideSoftInputFromWindow(editText.WindowToken, 0);
e.Handled = true;
}
}
};