My activity would show a ShowcaseView which add to decor view((ViewGroup) activity.getWindow().getDecorView()).addView(mShowcaseView);
,and i wanna detect the key event to handle something,so i override the dispatchKeyEvent() to do what i desired. but it seem like dispatchKeyEvent() method never get called,whats worse, PhoneWindow.DecorView#dispatchKeyEvent() neither get called,i don't know why,please help me.
Thanks.:)
here is my ShowcaseView briefly source code.
public class ShowcaseView extends FrameLayout {
public ShowcaseView(Context context) {
super(context);
init(context);
}
public ShowcaseView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ShowcaseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
setWillNotDraw(false);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setFocusable(true);
setEnabled(true);
}
#Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.d("Showcase", "dispatchKeyEvent: called");
super.dispatchKeyEvent(event);
return executeKeyEvent(event);
}
private boolean executeKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP
&& event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
hide();
}
return true;
}
}
According https://developer.android.com/training/keyboard-input/commands.html:
Note: When handling keyboard events with the KeyEvent class and related APIs, you should expect that such keyboard events come only from a hardware keyboard. You should never rely on receiving key events for any key on a soft input method (an on-screen keyboard).
To dispatch key events from on-screen keyboard you can add textWatcher to editText.
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
}
});
But i cannot see input fields in your code snippet.
I inflate a ListView inside a PopupWindow and I want the popup to behave like this:
wrap the listview when its height is < x
set the popup's height = x when the listiew's height > x (the listview being scrollable)
the popup is showed by a TextWatcher attached to an EditText, as it is meant to show search suggestions.
The adapter underlying the ListView is managed by a custom Loader, started whenever the user has typed something in the EditText.
I tried to override onMeasure() on the listview and pass the measured height to a listener which calls PopupWindow.update(), but this creates a loop since the latter would end up calling the first.
With the following code, the popup wraps the contained ListView as wanted, but the height is not restricted to any value. I need a solution to restrict the height to a maximum value, let's say 300dp.
etFiltro.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
/...
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
Log.i("loader","text changed");
filtro = String.valueOf(charSequence);
getSupportLoaderManager().restartLoader(0,null,loaderCallbacks);
if (popupWindow.isShowing()) popupWindow.dismiss();
}
#Override
public void afterTextChanged(Editable editable) {
popupWindow.showAsDropDown(etFiltro);
}
});
private LoaderManager.LoaderCallbacks<Cursor> loaderCallbacks = new LoaderManager.LoaderCallbacks<Cursor>() {
MyListView suggestionList;
SimpleCursorAdapter adapter;
int mHeight;
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
SuggestionLoader loader = new SuggestionLoader(MainActivity.this, databaseConnector, filtro);
loader.setUpdateThrottle(500);
View view = ((LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(R.layout.popup_window,null);
suggestionList = (MyListView) view.findViewById(R.id.suggestionList);
adapter = new SimpleCursorAdapter(MainActivity.this,android.R.layout.simple_list_item_1,null,
new String[]{"note"},new int[]{android.R.id.text1},0);
suggestionList.setAdapter(adapter);
suggestionList.setEmptyView(view.findViewById(R.id.tvNoSuggestions));
//ensure previous popup is dismissed
if (popupWindow!=null) popupWindow.dismiss();
popupWindow = new PopupWindow(view,etFiltro.getWidth(),0);
popupWindow.setWindowLayoutMode(0,WindowManager.LayoutParams.WRAP_CONTENT);
popupWindow.setAnimationStyle(0);//0 = no animation; -1 = default animation
Log.i("loader","onCreateLoader");
return loader;
}
#Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.changeCursor(cursor);
Log.i("loader","onLoadFinished");
}
#Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
Log.i("loader", "onLoaderReset");
adapter.changeCursor(null);
}
};
I have found the solution by myself. For anyone who will ever stumble upon this problem, the answer is to override the method onMeasure() of the ListView like this:
public class MyListView extends ListView {
public MyListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//set your custom height. AT_MOST means it can be as tall as needed,
//up to the specified size.
int height = MeasureSpec.makeMeasureSpec(300,MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec,height);
}
}
I use dropdown spinner with cursor adapter. It contains e.g 1 - 100 items.
I select e.g. item 50. Item is selected. Next time when I open spinner first visible row is item 50. How can I achieve that when I open spinner it will focus to first item/first visible item will be item 1?
I mean like autoscroll up in the list, so first visible item in dropdown is 1st one and not selected one.
You can make the Spinner do what you want by extending it and overriding the two methods that are responsible for setup/showing the list of values:
public class CustomSpinnerSelection extends Spinner {
private boolean mToggleFlag = true;
public CustomSpinnerSelection(Context context, AttributeSet attrs,
int defStyle, int mode) {
super(context, attrs, defStyle, mode);
}
public CustomSpinnerSelection(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public CustomSpinnerSelection(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSpinnerSelection(Context context, int mode) {
super(context, mode);
}
public CustomSpinnerSelection(Context context) {
super(context);
}
#Override
public int getSelectedItemPosition() {
// this toggle is required because this method will get called in other
// places too, the most important being called for the
// OnItemSelectedListener
if (!mToggleFlag) {
return 0; // get us to the first element
}
return super.getSelectedItemPosition();
}
#Override
public boolean performClick() {
// this method shows the list of elements from which to select one.
// we have to make the getSelectedItemPosition to return 0 so you can
// fool the Spinner and let it think that the selected item is the first
// element
mToggleFlag = false;
boolean result = super.performClick();
mToggleFlag = true;
return result;
}
}
It should work just fine for what you want to do.
You can set the selection of a Spinner to the first item like this:
yourspinner.setSelection(0);
You might want to do this in the onStart() method.
This short of code will do the work for you.
int prevSelection=0;
spSunFrom = (Spinner) findViewById(R.id.spTimeFromSun);
spSunFrom.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
prevSelection = spSunFrom.getSelectedItemPosition();
spSunFrom.setSelection(0);
return false;
}
});
spSunFrom.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
if(arg2==0)
spSunFrom.setSelection(prevSelection);
prevSelection = arg2;
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
spSunFrom.setSelection(prevSelection);
}
});
How can I detect delete (backspace) key event for a editText? I've tried using TextWatcher, but when the editText is empty, when I press delete key, nothing happens. I want to detect delete key press foe an editText even if it has no text.
NOTE: onKeyListener doesn't work for soft keyboards.
You can set OnKeyListener for you editText so you can detect any key press
EDIT: A common mistake we are checking KeyEvent.KEYCODE_BACK for backspace, but really it is KeyEvent.KEYCODE_DEL (Really that name is very confusing! )
editText.setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
//You can identify which key pressed by checking keyCode value with KeyEvent.KEYCODE_
if(keyCode == KeyEvent.KEYCODE_DEL) {
//this is for backspace
}
return false;
}
});
It's been a while since you asked but I just had the same issue. As already mentioned by Estel the problem with key listeners is that they only work with hardware keyboards. To do this with an IME (soft keyboard), the solution is a bit more elaborate.
The single method we actually want to override is sendKeyEvent in the EditText's InputConnection class. This method is called when key events occur in an IME. But in order to override this, we need to implement a custom EditText which overrides the onCreateInputConnection method, wrapping the default InputConnection object in a proxy class! :|
Sounds complicated, but here's the simplest example I could contrive:
public class ZanyEditText extends EditText {
private Random r = new Random();
public ZanyEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ZanyEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ZanyEditText(Context context) {
super(context);
}
public void setRandomBackgroundColor() {
setBackgroundColor(Color.rgb(r.nextInt(256), r.nextInt(256), r
.nextInt(256)));
}
#Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new ZanyInputConnection(super.onCreateInputConnection(outAttrs),
true);
}
private class ZanyInputConnection extends InputConnectionWrapper {
public ZanyInputConnection(InputConnection target, boolean mutable) {
super(target, mutable);
}
#Override
public boolean sendKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
ZanyEditText.this.setRandomBackgroundColor();
// Un-comment if you wish to cancel the backspace:
// return false;
}
return super.sendKeyEvent(event);
}
}
}
The line with the call to setRandomBackgroundColor is where my special backspace action occurs. In this case, changing the EditText's background colour.
If you're inflating this from XML remember to use the full package name as the tag:
<cc.buttfu.test.ZanyEditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/somefield"
></cc.buttfu.test.ZanyEditText>
This is just an addition to Idris's answer, adding in the override to deleteSurroundingText as well. I found more info on that here: Android: Backspace in WebView/BaseInputConnection
package com.elavon.virtualmerchantmobile.utils;
import java.util.Random;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.widget.EditText;
public class ZanyEditText extends EditText {
private Random r = new Random();
public ZanyEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ZanyEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ZanyEditText(Context context) {
super(context);
}
public void setRandomBackgroundColor() {
setBackgroundColor(Color.rgb(r.nextInt(256), r.nextInt(256), r
.nextInt(256)));
}
#Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new ZanyInputConnection(super.onCreateInputConnection(outAttrs),
true);
}
private class ZanyInputConnection extends InputConnectionWrapper {
public ZanyInputConnection(InputConnection target, boolean mutable) {
super(target, mutable);
}
#Override
public boolean sendKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
ZanyEditText.this.setRandomBackgroundColor();
// Un-comment if you wish to cancel the backspace:
// return false;
}
return super.sendKeyEvent(event);
}
#Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
// magic: in latest Android, deleteSurroundingText(1, 0) will be called for backspace
if (beforeLength == 1 && afterLength == 0) {
// backspace
return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
&& sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
}
}
Here is my easy solution, which works for all the API's:
private int previousLength;
private boolean backSpace;
// ...
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
previousLength = s.length();
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
backSpace = previousLength > s.length();
if (backSpace) {
// do your stuff ...
}
}
UPDATE 17.04.18 .
As pointed out in comments, this solution doesn't track the backspace press if EditText is empty (the same as most of the other solutions).
However, it's enough for most of the use cases.
P.S. If I had to create something similar today, I would do:
public abstract class TextWatcherExtended implements TextWatcher {
private int lastLength;
public abstract void afterTextChanged(Editable s, boolean backSpace);
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
lastLength = s.length();
}
#Override
public void afterTextChanged(Editable s) {
afterTextChanged(s, lastLength > s.length());
}
}
Then just use it as a regular TextWatcher:
editText.addTextChangedListener(new TextWatcherExtended() {
#Override
public void afterTextChanged(Editable s, boolean backSpace) {
// Here you are! You got missing "backSpace" flag
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Do something useful if you wish.
// Or override it in TextWatcherExtended class if want to avoid it here
}
});
I sent 2 days to find a solution and I figured out a working one :) (on soft keys)
public TextWatcher textWatcher = 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) {
if (count == 0) {
//Put your code here.
//Runs when delete/backspace pressed on soft key (tested on htc m8)
//You can use EditText.getText().length() to make if statements here
}
}
#Override
public void afterTextChanged(Editable s) {
}
}
After add the textwatcher to your EditText:
yourEditText.addTextChangedListener(textWatcher);
I hope it works on another android devices too (samsung, LG, etc).
My simple solution which works perfectly. You should to add a flag. My code snippet:
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (after < count) {
isBackspaceClicked = true;
} else {
isBackspaceClicked = false;
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) { }
#Override
public void afterTextChanged(Editable s) {
if (!isBackspaceClicked) {
// Your current code
} else {
// Your "backspace" handling
}
}
Example of creating EditText with TextWatcher
EditText someEdit=new EditText(this);
//create TextWatcher for our EditText
TextWatcher1 TW1 = new TextWatcher1(someEdit);
//apply our TextWatcher to EditText
someEdit.addTextChangedListener(TW1);
custom TextWatcher
public class TextWatcher1 implements TextWatcher {
public EditText editText;
//constructor
public TextWatcher1(EditText et){
super();
editText = et;
//Code for monitoring keystrokes
editText.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_DEL){
editText.setText("");
}
return false;
}
});
}
//Some manipulation with text
public void afterTextChanged(Editable s) {
if(editText.getText().length() == 12){
editText.setText(editText.getText().delete(editText.getText().length() - 1, editText.getText().length()));
editText.setSelection(editText.getText().toString().length());
}
if (editText.getText().length()==2||editText.getText().length()==5||editText.getText().length()==8){
editText.setText(editText.getText()+"/");
editText.setSelection(editText.getText().toString().length());
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after){
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}
for some one who's using Kotlin
addOnTextChanged is not flexible enought to handle some cases (ex: detect if user press delete when edit text was empty)
setOnkeyListener worked even soft keyboard or hardkeyboard! but just on some devices. In my case, it work on Samsung s8 but not work on Xiaomi mi8 se.
if you using kotlin, you can use crossline function doOnTextChanged, it's the same as addOnTextChanged but callback is triggered even edit text was empty.
NOTE: doOnTextChanged is a part of Android KTX library
Based on #Jiff ZanyEditText here is WiseEditText with setSoftKeyListener(OnKeyListener)
package com.locopixel.seagame.ui.custom;
import java.util.Random;
import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.AppCompatEditText;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
public class WiseEditText extends AppCompatEditText {
private Random r = new Random();
private OnKeyListener keyListener;
public WiseEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public WiseEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WiseEditText(Context context) {
super(context);
}
#Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new MyInputConnection(super.onCreateInputConnection(outAttrs),
true);
}
private class MyInputConnection extends InputConnectionWrapper {
public MyInputConnection(InputConnection target, boolean mutable) {
super(target, mutable);
}
#Override
public boolean sendKeyEvent(KeyEvent event) {
if (keyListener != null) {
keyListener.onKey(WiseEditText.this,event.getKeyCode(),event);
}
return super.sendKeyEvent(event);
}
#Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
// magic: in latest Android, deleteSurroundingText(1, 0) will be called for backspace
if (beforeLength == 1 && afterLength == 0) {
// backspace
return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
&& sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
}
public void setSoftKeyListener(OnKeyListener listener){
keyListener = listener;
}
}
This seems to be working for me :
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (before - count == 1) {
onBackSpace();
} else if (s.subSequence(start, start + count).toString().equals("\n")) {
onNewLine();
}
}
I am also faced same issue in Dialog.. because I am using setOnKeyListener.. But I set default return true. After change like below code it working fine for me..
mDialog.setOnKeyListener(new Dialog.OnKeyListener() {
#Override
public boolean onKey(DialogInterface arg0, int keyCode,
KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
mDialog.dismiss();
return true;
}
return false;//this line is important
}
});
My problem was, that I had custom Textwatcher, so I didn't want to add OnKeyListener to an EditText as well as I didn't want to create custom EditText. I wanted to detect if backspace was pressed in my afterTextChanged method, so I shouldn't trigger my event.
This is how I solved this. Hope it would be helpful for someone.
public class CustomTextWatcher extends AfterTextChangedTextWatcher {
private boolean backspacePressed;
#Override
public void afterTextChanged(Editable s) {
if (!backspacePressed) {
triggerYourEvent();
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
super.onTextChanged(s, start, before, count);
backspacePressed = count == 0; //if count == 0, backspace is pressed
}
}
I have tested #Jeff's solution on version 4.2, 4.4, 6.0. On 4.2 and 6.0, it works well. But on 4.4, it doesn't work.
I found an easy way to work around this problem. The key point is to insert an invisible character into the content of EditText at the begining, and don't let user move cursor before this character. My way is to insert a white-space character with an ImageSpan of Zero Width on it. Here is my code.
#Override
public void afterTextChanged(Editable s) {
String ss = s.toString();
if (!ss.startsWith(" ")) {
int selection = holder.editText.getSelectionEnd();
s.insert(0, " ");
ss = s.toString();
holder.editText.setSelection(selection + 1);
}
if (ss.startsWith(" ")) {
ImageSpan[] spans = s.getSpans(0, 1, ImageSpan.class);
if (spans == null || spans.length == 0) {
s.setSpan(new ImageSpan(getResources().getDrawable(R.drawable.zero_wdith_drawable)), 0 , 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
And we need custom an EditText which has a SelectionChangeListener
public class EditTextSelectable extends android.support.v7.widget.AppCompatEditText {
public interface OnSelectChangeListener {
void onSelectChange(int start, int end);
}
private OnSelectChangeListener mListener;
public void setListener(OnSelectChangeListener listener) {
mListener = listener;
}
...constructors...
#Override
protected void onSelectionChanged(int selStart, int selEnd) {
if (mListener != null) {
mListener.onSelectChange(selStart, selEnd);
}
super.onSelectionChanged(selStart, selEnd);
}
}
And the last step
holder.editText.setListener(new EditTextSelectable.OnSelectChangeListener() {
#Override
public void onSelectChange(int start, int end) {
if (start == 0 && holder.editText.getText().length() != 0) {
holder.editText.setSelection(1, Math.max(1, end));
}
}
});
And now, we are done~ We can detect backspace key event when EditText has no actual content, and user will know nothing about our trick.
This question may be old but the answer is really simple using a TextWatcher.
int lastSize=0;
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
//2. compare the old length of the text with the new one
//3. if the length is shorter, then backspace was clicked
if (lastSize > charSequence.length()) {
//4. Backspace was clicked
//5. perform action
}
//1. get the current length of of the text
lastSize = charSequence.length();
}
I have found a really simple solution which works with a soft keyboard.
override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) {
text?.let {
if(count < before) {
Toast.makeText(context, "backspace pressed", Toast.LENGTH_SHORT).show()
// implement your own code
}
}
}
Belated but it may help new visitors, use TextWatcher() instead will help alot and also it will work for both soft and hard keyboard as well.
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (charSequence.length() > 0) {
//Here it means back button is pressed and edit text is now empty
} else {
//Here edit text has some text
}
}
#Override
public void afterTextChanged(Editable editable) {
}
});
You could set a key listener on the activity, and in the callback method, you could detect
which key the user hit. The code below is for your reference. Hope it helps.
//after user hits keys, this method would be called.
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (editText.isFocused()) {
switch (keyCode) {
case KeyEvent.KEYCODE_DEL: //delete key
Log.i("INFO", "delete key hit"); //you should see this log in ddms after you hit delete key
break;
}
}
return super.onKeyUp(keyCode, event);
}