I have a subclassed View that pops up the keyboard when it receives a 'touch up' in onTouchEvent. It shows this by requesting focus, retrieving the InputMethodManager, and then calling showSoftInput.
Now I need to figure out how to capture the tapped letters of the soft keyboard, as they are pressed. I am currently only getting a response when the Next/Done button is pressed on the soft keyboard.
Here is my class:
public class BigGrid extends View {
private static final String TAG = "BigGrid";
public BigGrid(Context context) {
super(context);
setFocusableInTouchMode(true); // allows the keyboard to pop up on
// touch down
setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.d(TAG, "onKeyListener");
if (event.getAction() == KeyEvent.ACTION_DOWN) {
// Perform action on key press
Log.d(TAG, "ACTION_DOWN");
return true;
}
return false;
}
});
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
Log.d(TAG, "onTOUCH");
if (event.getAction() == MotionEvent.ACTION_UP) {
// show the keyboard so we can enter text
InputMethodManager imm = (InputMethodManager) getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(this, InputMethodManager.SHOW_FORCED);
}
return true;
}
#Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Log.d(TAG, "onCreateInputConnection");
BaseInputConnection fic = new BaseInputConnection(this, true);
outAttrs.actionLabel = null;
outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
return fic;
}
#Override
public boolean onCheckIsTextEditor() {
Log.d(TAG, "onCheckIsTextEditor");
return true;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(R.color.grid_bg);
// .
// .
// alot more drawing code...
// .
}
}
The keyboard shows, but my onKeyListener only fires when I press the 'Next' button on the keyboard. I need which character is tapped, so that I can display it in my onDraw() method.
It is actually possible to handle the key events yourself without deriving your view from TextView.
To do this, just modify your original code as follows:
1) Replace the following line in onCreateInputConnection():
outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
with this one:
outAttrs.inputType = InputType.TYPE_NULL;
Per the documentation for InputType.TYPE_NULL: "This should be interpreted to mean that the target input connection is not rich, it can not process and show things like candidate text nor retrieve the current text, so the input method will need to run in a limited 'generate key events' mode."
2) Replace the following line in the same method:
BaseInputConnection fic = new BaseInputConnection(this, true);
with this one:
BaseInputConnection fic = new BaseInputConnection(this, false);
The false second argument puts the BaseInputConnection into "dummy" mode, which is also required in order for the raw key events to be sent to your view. In the BaseInputConnection code, you can find several comments such as the following: "only if dummy mode, a key event is sent for the new text and the current editable buffer cleared."
I have used this approach to have the soft keyboard send raw events to a view of mine that is derived from LinearLayout (i.e., a view not derived from TextView), and can confirm that it works.
Of course, if you didn't need to set the IME_ACTION_DONE imeOptions value to show a Done button on the keyboard, then you could just remove the onCreateInputConnection() and onCheckIsTextEditor() overrides entirely, and raw events would then be sent to your view by default, since no input connection capable of more sophisticated processing would have been defined.
But unfortunately, there does not seem to be a simple way to configure the EditorInfo attributes without overriding these methods and providing a BaseInputConnection object, and once you have done that you will have to dumb down the processing performed by that object as described above if you want to once again receive the raw key events.
WARNING: Two bugs were introduced in certain recent versions of the default LatinIME keyboard that ships with Android (Google Keyboard) that can impact keyboard event processing (as described above) when that keyboard is in use. I've devised some workarounds on the app side, with sample code, that appear to get around these problems. To view these workarounds, see the following answer:
Android - cannot capture backspace/delete press in soft. keyboard
According to the documentation, a View (editor) receives commands from the Keyboard (IME) through an InputConnection and sends commands to the Keyboard through an InputMethodManager.
I will show the entire code below, but here are the steps.
1. Make the Keyboard to appear
Since the view is sending a command to the keyboard it needs to use an InputMethodManager. For the sake of the example, we will say that when the view is tapped it will show the keyboard (or hide it if it is already showing).
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);
}
return true;
}
The view also needs to have had setFocusableInTouchMode(true) set previously.
2. Receive input from the keyboard
In order for the view to receive input from the keyboard, it needs to override onCreateInputConnection(). This returns the InputConnection that the Keyboard uses to communicate with the view.
#Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
return new MyInputConnection(this, true);
}
The outAttrs specify what kind of keyboard the view is requesting. Here we are just requesting a normal text keyboard. Choosing TYPE_CLASS_NUMBER would display a number pad (if available). There are lots of other options. See EditorInfo.
You must return an InputConnection, which is usually a custom subclass of BaseInputConnection. In that subclass you provide a reference to your editable string, which the keyboard will make updates to. Since a SpannableStringBuilder implements the Editable interface, we will use that in our basic example.
public class MyInputConnection extends BaseInputConnection {
private SpannableStringBuilder mEditable;
MyInputConnection(View targetView, boolean fullEditor) {
super(targetView, fullEditor);
MyCustomView customView = (MyCustomView) targetView;
mEditable = customView.mText;
}
#Override
public Editable getEditable() {
return mEditable;
}
}
All we did here was provide the input connection with a reference to the text variable in our custom view. The BaseInputConnection will take care of editing that mText. This could very well be all that you need to do. However, you can check out the source code and see which methods say "default implementation", especially "default implementation does nothing." These are other methods you may want to override depending on how involved your editor view is going to be. You should also look through all the method names in the documentation. A number of them have notes to "editor authors". Pay special attention to those.
Some keyboards don't send certain input through the InputConnection for some reason (for example delete, enter, and some number pad keys). For those I added an OnKeyListener. Testing this setup on five different soft keyboards, everything seemed to work. Supplemental answers related to this are here:
Differentiating text keycode from control keycode in Android KeyEvent
Need table of key codes for android and presenter
Input connection for numeric type keyboard
Full project code
Here is my full example for reference.
MyCustomView.java
public class MyCustomView extends View {
SpannableStringBuilder mText;
public MyCustomView(Context context) {
this(context, null, 0);
}
public MyCustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setFocusableInTouchMode(true);
mText = new SpannableStringBuilder();
// handle key presses not handled by the InputConnection
setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (event.getUnicodeChar() == 0) { // control character
if (keyCode == KeyEvent.KEYCODE_DEL) {
mText.delete(mText.length() - 1, mText.length());
Log.i("TAG", "text: " + mText + " (keycode)");
return true;
}
// TODO handle any other control keys here
} else { // text character
mText.append((char)event.getUnicodeChar());
Log.i("TAG", "text: " + mText + " (keycode)");
return true;
}
}
return false;
}
});
}
// toggle whether the keyboard is showing when the view is clicked
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);
}
return true;
}
#Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
// outAttrs.inputType = InputType.TYPE_CLASS_NUMBER; // alternate (show number pad rather than text)
return new MyInputConnection(this, true);
}
}
MyInputConnection.java
public class MyInputConnection extends BaseInputConnection {
private SpannableStringBuilder mEditable;
MyInputConnection(View targetView, boolean fullEditor) {
super(targetView, fullEditor);
MyCustomView customView = (MyCustomView) targetView;
mEditable = customView.mText;
}
#Override
public Editable getEditable() {
return mEditable;
}
// just adding this to show that text is being committed.
#Override
public boolean commitText(CharSequence text, int newCursorPosition) {
boolean returnValue = super.commitText(text, newCursorPosition);
Log.i("TAG", "text: " + mEditable);
return returnValue;
}
}
activity_main.xml
<?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="com.example.editorview.MainActivity">
<com.example.editorview.MyCustomView
android:id="#+id/myCustomView"
android:background="#android:color/holo_blue_bright"
android:layout_margin="50dp"
android:layout_width="300dp"
android:layout_height="150dp"
android:layout_centerHorizontal="true"
/>
</RelativeLayout>
There is nothing special in the MainActivity.java code.
Please leave a comment if this doesn't work for you. I am using this basic solution for a custom EditText in a library I am making and if there are any edge cases in which it doesn't work, I want to know. If you would like to view that project, the custom view is here. It's InputConnection is here.
Related
How to make a custom system keyboard
How to make a custom in-app keyboard
Turns out that I did in fact need to subclass TextView and the use addTextChangedListener() to add my own implementation of TextWatcher in order to listen to soft key events. I couldn't find a way to do this with a plain old View.
One other thing, for those who will try this technique; TextView is not able to edit text by default, so if you want to make your implementation editable (instead of subclassing EditText, which I didn't want to to do), you must also make a custom InputConnection, something like the following:
/**
* MyInputConnection
* BaseInputConnection configured to be editable
*/
class MyInputConnection extends BaseInputConnection {
private SpannableStringBuilder _editable;
TextView _textView;
public MyInputConnection(View targetView, boolean fullEditor) {
super(targetView, fullEditor);
_textView = (TextView) targetView;
}
public Editable getEditable() {
if (_editable == null) {
_editable = (SpannableStringBuilder) Editable.Factory.getInstance()
.newEditable("Placeholder");
}
return _editable;
}
public boolean commitText(CharSequence text, int newCursorPosition) {
_editable.append(text);
_textView.setText(text);
return true;
}
}
Then you override onCheckisTextEditor and onCreateInputConnection with something like the following:
#Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.actionLabel = null;
outAttrs.label = "Test text";
outAttrs.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
return new MyInputConnection(this, true);
}
#Override
public boolean onCheckIsTextEditor() {
return true;
}
After this, you should have a View that can listen to the soft keyboard and you can do whatever you want with the key input values.
My understanding is that your onKeyListener is only going to get hardware keyboard key events.
You will get access to all input key events if you override boolean View.onKeyPreIme(int keyCode, KeyEvent event)
This way you can elect to handle the key event action [ DOWN | MULTIPLE | UP ] and return true, or allow the normal key processing to deal with it (return super.onKeyPreIme())
Related
I want to mimic android keyboard behavior when handling touch event. The android keyboard always hide every time I click somewhere else other the edit text and the keyboard. So far, I could mimic that's behavior using dispatchTouchEvent
Since it will detect for any dispatch touch even, whenever I click my keyboard button then it will close the keyboard it self. I wanna check if I click somewhere else other than my keyboard then hide the keyboard, else keep the keyboard on. I think I just missed piece of code and I can't find it what i missed here. It just need one single step to accomplish this. Please help.
Here my keyboard looks like
and here what i did so far
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (getCurrentFocus() != null) {
View view = getCurrentFocus();
//im not sure this condition would help
if (view == null) {
keyboard.setVisibility(View.GONE);
}
}
return super.dispatchTouchEvent(ev);
}
I can get the view object but i cant determine which view that i need to keep my keyboard on.
The other code (variable declaration)
PhoneKeyboard pk;
InputConnection ic;
LinearLayout keyboard;
pk = findViewById(R.id.phone_keyboard);
ic = phone_no.onCreateInputConnection(new EditorInfo());
pk.setInputConnection(ic);
Handling back pressed (mimic the android keyboard behavior)
#Override
public void onBackPressed(){
if(keyboard.getVisibility() == View.GONE)
super.onBackPressed();
else
keyboard.setVisibility(View.GONE);
}
How I handle request on the editText to make my keyboard visible
phone_no.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
InputMethodManager imm = (InputMethodManager) getApplicationContext().getSystemService(
android.content.Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
if(view.equals(phone_no) && keyboard.getVisibility() == View.GONE) {
phone_no.requestFocus();
keyboard.setVisibility(View.VISIBLE);
}
return false;
}
});
How I handle submit / enter event at my custom keyboard (for who need the solution for custom keyboard, maybe this post can help other:) )
phone_no.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
InputMethodManager imm = (InputMethodManager) getApplicationContext().getSystemService(
android.content.Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
if(view.equals(phone_no) && keyboard.getVisibility() == View.GONE) {
phone_no.requestFocus();
keyboard.setVisibility(View.VISIBLE);
}
return false;
}
});
Here is my snippet code for the keyboard layout view
<include
layout="#layout/layout_keyboard_phone_no"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="false"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Ps. I also have base activity and it extends to all my activity (if your solution required the base activity)
Update 1
I have try this method
phone_no.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
keyboard.setVisibility(View.GONE);
}
}
});
Is doing the same thing with the onDispatch event, whenever I click number on my custom keyboard, it also hide the keyboard. I need to check if the view others than the edit text and my custom keyboard then hide the keyboard else keep the keyboard on. Thank you
Is an edittext cursor supposed to continue blinking after the soft keyboard is closed or is this a result of testing on an emulator and wouldn't happen on an actual device? -- as pointed out by the second post in this discussion
Update:
I know that the edittexts still have the cursor blinking because they're still in focus -- logged a message whenever edittext lost focus, but message was never logged when soft keyboard closed.
Update:
I've tried doing:
#Override
public void onBackPressed() {
super.onBackPressed();
getCurrentFocus().clearFocus();
}
So that every time the keyboard is closed, the EditText currently in focus loses that focus and onFocusChanged() is called. The problem is that onBackPressed() isn't called when the back button is pressed when the keyboard is up. I know this because I put a toast in onBackPressed(), and no toast shows when the back button is pressed whilst the keyboard is up.
First create a custom Edit text. Below is the example which has a call back when keyboard back is pressed to dismiss the keyboard
public class EdittextListner extends EditText {
private KeyImeChange keyImeChangeListener;
public EdittextListner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setKeyImeChangeListener(KeyImeChange listener) {
keyImeChangeListener = listener;
}
public interface KeyImeChange {
public boolean onKeyIme(int keyCode, KeyEvent event);
}
#Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyImeChangeListener != null) {
return keyImeChangeListener.onKeyIme(keyCode, event);
}
return false;
}
}
Secondly change your EditText to EdittextListner in you layout file.
Finally do the following
mLastNameEditText.setKeyImeChangeListener(new EdittextListner.KeyImeChange() {
#Override
public boolean onKeyIme(int keyCode, KeyEvent event) {
mLastNameEditText.clearFocus();
return true;
}
});
This worked for me. Hope this helps
Edittext is a View which accept input from user, so it is not related with keyborad open or close, when user will click on edittext, that edittext will get focus and cursor will start to blink for taking input,
So you can do one thing as when you are closing keyboard at the same time you can also set visibility of cursor for that edittext so it will stop to blink,
For that you need to write below line when you hide keyboard.
editTextObject.setCursorVisible(false);
This will stope cursor to blink.
As you said, the blinking cursor in the EditText is related to the EditText having focus, but showing or hiding the soft keyboard has no correlation to a View gaining or losing focus. Any View (EditText or otherwise) can be focused independent of whether or not a soft keyboard is showing and there is nothing intrinsic to EditText that would make it behave any differently.
If you want an EditText to lose focus whenever the soft keyboard is hidden, you will need to implement this functionality yourself by listening for changes in the soft keyboard visibility and updating the EditText as a result.
The only way to know keyboard is disappeared is to override
OnglobalLayout and check the height.
Based on that event you can "setCursorVisible(false)" on your edit text
For more information, check this Link.
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use the layout root
InputMethodManager im = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE);
/*
Instantiate and pass a callback
*/
SoftKeyboard softKeyboard;
softKeyboard = new SoftKeyboard(mainLayout, im);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{
#Override
public void onSoftKeyboardHide()
{
// Code here
EditText.clearFocus();
}
#Override
public void onSoftKeyboardShow()
{
// Code here
}
});
/*
Open or close the soft keyboard easily
*/
softKeyboard.openSoftKeyboard();
softKeyboard.closeSoftKeyboard();
/* Prevent memory leaks:
*/
#Override
public void onDestroy()
{
super.onDestroy();
softKeyboard.unRegisterSoftKeyboardCallback();
}
try this:
public class EditTextBackEvent extends EditText {
private EditTextImeBackListener mOnImeBack;
public EditTextBackEvent(Context context) {
super(context);
}
public EditTextBackEvent(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EditTextBackEvent(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
if (mOnImeBack != null) mOnImeBack.onImeBack(this, this.getText().toString());
}
return super.dispatchKeyEvent(event);
}
public void setOnEditTextImeBackListener(EditTextImeBackListener listener) {
mOnImeBack = listener;
}
public interface EditTextImeBackListener {
void onImeBack(EditTextBackEvent ctrl, String text);
}
}
in your layout:
<yourpackagename.EditTextBackEvent
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
and in your fragment:
edittext.setOnEditTextImeBackListener(new EditTextBackEvent.EditTextImeBackListener()
{
#Override
public void onImeBack(EditTextBackEvent ctrl, String text)
{
edittext.clearfocus();
}
});
Try keeping a view in your layout which is focusable above your editText.
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:focusable="true"
android:focusableInTouchMode="true" />
This should work as the blank focusable view should catch focus and not your edittext.
Found two solutions - please see selected answer
When the user clicks in a certain region of an EditText, I want to popup a dialog. I used onClick to capture the click. This partially works: the first time the user taps the EditText, the soft keyboard pops up and the dialog doesn't. Subsequent taps bring up the keyboard and then the dialog (and the keyboard disappears).
I suspect this has something to do with the EditText gaining focus.
Here's a code snip:
public class PrefixEditText extends EditText implements TextWatcher, OnClickListener
{
public PrefixEditText (Context context)
{
super (context);
setOnClickListener (this);
}
#Override
public void onClick(View v)
{
int selStart = getSelectionStart();
if (selStart < some_particular_pos)
bring_up_dialog();
}
}
IMPORTANT: I don't want to completely disable the normal EditText behavior. I want the user to be able to make region selections (for copy & paste). I probably still want it to gain focus (so I don't break the model when people with physical keyboards use the app). And it's ok for the click to set the cursor position. Thus, solutions that override onTouch and block all onTouch actions from the EditText will not work for me.
UPDATE I've discovered a bit more. If the EditText is gaining focus, onFocusChange gets called and onClick does not. If it already has focus, onClick gets called and onFocusChange does not.
Secondly, it's possible to hide the keyboard by calling
setInputType (InputType.TYPE_NULL);
Doing so in onFocusChange works - the keyboard never shows up. Doing so in onClick (assuming the keyboard was hidden before the click) apparently is too late - the keyboard shows up and then disappears.
The next idea to try would be to hide the keyboard during onTouch. However, I'm afraid to mess with that code - seems that whatever I figure out would be very fragile with respect to future versions of EditText.
Any thoughs on this?
May be this can work
EditText e = new EditText(context);
e.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
// TODO Auto-generated method stub
if(hasFocus)
{
//dialogue popup
}
}
});
or u can use e.hasFocus(); and then use e.setFocusable(false); to make it unfocus
/////////////// my code
e.setInputType(InputType.TYPE_NULL);
e.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
AlertDialog.Builder sa = new Builder(ctx);
sa.create().setOnDismissListener(new OnDismissListener() {
public void onDismiss(DialogInterface dialog) {
// TODO Auto-generated method stub
e.setInputType(InputType.TYPE_CLASS_TEXT);
}
});
sa.show();
}
});
try change capture click by onClick to onTouch
this.editText.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//dialogue popup
}
return false;
}
});
try this if it can help u.first time the edittext will behave as a normal editttext and on condition u can show the dialog as needed
EditText editText;
mTim_edittext.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if(!hasFocus){
//statement
if(condition){
AlertDialog diaBox = Utils.showErrorDialogBox( "Term in Months Cannot be 0", context);
diaBox.show();
}
}
}
});
After lots of experiments, here are two working solutions! I tested them on my two devices - Nexus 7 running 4.2.1, Kyocera C5170 runing 4.0.4. My preference is Solution 2.
SOLUTION 1
For the first, the trick was to determine the cursor position in onTouch instead of onClick, before EditText has a chance to do it's work - particularly before it pops up the keyboard.
One additional comment: be sure to set android:windowSoftInputMode="stateHidden" in your manifest for the popup, or you'll get the keyboard along with the popup.
Here's the whole code:
public class ClickText extends EditText implements OnTouchListener
{
public ClickText (Context context, AttributeSet attrs)
{
super (context, attrs);
setOnTouchListener (this);
}
#Override
public boolean onTouch (View v, MotionEvent event)
{
if (event.getActionMasked() == MotionEvent.ACTION_DOWN)
{
int line = getLayout().getLineForVertical ((int)event.getY());
int onTouchCursorPos = getLayout().getOffsetForHorizontal (line, event.getX());
if (onTouchCursorPos < 10) // or whatever condition
showPopup (this); // or whatever you want to do
}
return false;
}
private void showPopup (final EditText text)
{
Intent intent = new Intent (getContext(), Popup.class);
((Activity)getContext()).startActivity (intent);
}
}
SOLUTION 2
This one is actually simpler and, I think, is better - fewer side effects.
Here, the trick is to let EditText do all its click processing and then override it asynchronously. The gist is: wait for the touch to "let go" - MotionEvent.ACTION_UP - and then instead of doing your action right then, post a Runnable to the event queue and do your action there.
The whole code:
public class ClickText extends EditText implements OnTouchListener
{
public ClickText (Context context, AttributeSet attrs)
{
super (context, attrs);
setOnTouchListener (this);
}
#Override
public boolean onTouch (View v, MotionEvent event)
{
switch (event.getActionMasked())
{
case MotionEvent.ACTION_UP:
{
post (new Runnable ()
{
// Do this asynch so that EditText can finish setting the selectino.
#Override
public void run()
{
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
// If selStart is different than selEnd, user has highlighed an area of
// text; I chose to ignore the click when this happens.
if (selStart == selEnd)
if (selStart >= 0 && selStart < 10) // or whatever range you want
showPopup (this);
}
});
break;
}
}
return false;
}
private void showPopup (final EditText text)
{
Intent intent = new Intent (getContext(), Popup.class);
((Activity)getContext()).startActivity (intent);
}
}
use this below code snippet
this.editText.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//dialogue popup
}
return false;
}
});
I have an onKeyDown Event which is supposed to display an image once triggered, but I have noticed that despite pressing the corresponding key several times, the image does not appear until I click anywhere on the canvas with my mouse. Any suggestions on the actual problem and how to proceed? Pretty new to the concept so not quite sure what may be missing.
*Edited and pasted class in its entirety.
Thanks
public class BuccaneerView extends TileView {
public static final int PLAYER = 1;
public static final int GREEN_STAR = 2;
Coordinate P_Location;
public BuccaneerView(Context context, AttributeSet attrs) {
super(context, attrs);
initBucc();
}
private void initBucc() {
this.setFocusable(true);
Resources r = this.getContext().getResources();
resetTiles(4);
loadTile(PLAYER, r.getDrawable(R.drawable.aerialplayer));
loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));
/**/
P_Location = new Coordinate(5,5);
setTile(PLAYER, P_Location.x, P_Location.y);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {
if (keyCode == KeyEvent.KEYCODE_SPACE)
{
setTile(GREEN_STAR, 1, 0);
}
return super.onKeyDown(keyCode, msg);
}
public void update()
{
}
}
You seem to be treating onKeyDown as one of your on methods.
return super.onKeyDown(keyCode, msg);
Is a bad thing to do, its as if you have called this function and want to return what key has been pressed. Change it to simply be return false which will mean you are handling what they keyboard is doing.
EDIT
Is there any reason you use onKeyDown and not onKey? Here is some extra code which I use, it uses an array of booleans (pressedKeys, which is 128 in length) and you can later use it to check the array to see if a key is pressed
public boolean onKey(View v, int keyCode, KeyEvent event)
{
if (event.getAction() == android.view.KeyEvent.ACTION_DOWN)
{
if(keyCode > 0 && keyCode < 127)
pressedKeys[keyCode] = true;
}
if (event.getAction() == android.view.KeyEvent.ACTION_UP)
{
if(keyCode > 0 && keyCode < 127)
pressedKeys[keyCode] = false;
}
keyEventsBuffer.add(keyEvent);
}
return false;
}
So with this you can then say
If(pressedKeys[KeyYouWantToCheck])
{
//Do some stuff if that key is down
}
At a guess, the key event is not being delivered to whatever you have set the key listener on. This can happen if there is another view in between which is a listener and which stops the propagation of the key event (i.e. returning true from this method). There are some views which do this by default (e.g. EditText for most keys). It would be helpful if you could edit your question and include more code, or describe how your activity is setup.
By 'clicking on the canvas' you are probably changing the focus and making the key event being delivered to a different view. This could explain why you suddenly see the key listener working after clicking.
Is it possible to have an EditText widget with android:inputType="textMultiLine" set, and android:imeOptions="actionDone" at the same time?
I'd like a multi-line edit box, with the action button on the keyboard to be Done, not Enter (Carriage Return), but it doesn't seem to be working.
Use
editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
editText.setRawInputType(InputType.TYPE_CLASS_TEXT);
and in XML:
android:inputType="textMultiLine"
From the android documentation: '"textMultiLine"
Normal text keyboard that allow users to input long strings of text that include line breaks (carriage returns).' Therefore the textMultiLine attribute is not appropriate if you want to have the 'Done' button in the keyboard.
A simple way to get a multi-line (in this case 3 lines) input field with the done button is to use EditText with
android:lines="3"
android:scrollHorizontally="false"
However, for some reason this only works for me if I do these settings in the code instead of the layout file (in onCreate) by
TextView tv = (TextView)findViewById(R.id.editText);
if (tv != null) {
tv.setHorizontallyScrolling(false);
tv.setLines(3);
}
I hope this helps someone, as it took quite a while to figure out. If you find a way to make it work from the manifest, please let us know.
Working Example!
Create the below custom EditText class that supports this feature and use the class in the xml file. Working code:
package com.example;
import android.content.Context;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
public class ActionEditText extends EditText
{
public ActionEditText(Context context)
{
super(context);
}
public ActionEditText(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ActionEditText(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs)
{
InputConnection conn = super.onCreateInputConnection(outAttrs);
outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
return conn;
}
}
<com.example.ActionEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="textAutoCorrect|textCapSentences|textMultiLine" />
To do this in Kotlin (and also optionally apply other configurations like textCapSentences you can use this extension function:
// To use this, do NOT set inputType on the EditText in the layout
fun EditText.setMultiLineCapSentencesAndDoneAction() {
imeOptions = EditorInfo.IME_ACTION_DONE
setRawInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or InputType.TYPE_TEXT_FLAG_MULTI_LINE)
}
Usage:
myEditText.setMultiLineCapSentencesAndDoneAction()
Reuseable Kotlin Solution
Setting these values in code was the only thing that worked for me
edittext.inputType = EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
edittext.setHorizontallyScrolling(false)
edittext.maxLines = Integer.MAX_VALUE // Or your preferred fixed value
I require this frequently, so made this to keep the code clean:
fun EditText.multilineIme(action: Int) {
imeOptions = action
inputType = EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
setHorizontallyScrolling(false)
maxLines = Integer.MAX_VALUE
}
// Then just call
edittext.multilineIme(EditorInfo.IME_ACTION_DONE)
If you want to be add an optional custom action on 'Done', try this:
fun EditText.multilineDone(callback: (() -> Unit)? = null) {
val action = EditorInfo.IME_ACTION_DONE
multilineIme(action)
setOnEditorActionListener { _, actionId, _ ->
if (action == actionId) {
callback?.invoke()
true
}
false
}
}
}
// Then you can call
edittext.multilineDone { closeKeyboard() }
// or just
edittext.multilineDone()
Need to easily control the keyboard in callback? Read this post
Then add hideKeyboard() call in EditText.multilineDone
I think this is the way to do you thing. Having android:inputType="textMultiLine", android:imeOptions="actionDone" makes enter key functionality ambiguous. Just keep in mind that you can use android:lines="10" and maybe remove android:inputType="textMultiLine", but depends what you want to achieve sometimes you just need the android:inputType="textMultiLine" and there is no replacement for it.
EditText ed=new EditText(this);
ed.setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_ENTER){
//do your stuff here
}
return false;
}
});
This seems to work for me perfectly
int lineNum = 2;
mEditText.setHorizontallyScrolling(false);
mEditText.setLines(3);
Short answer: No, I believe it's not possible prior to API level 11 (3.0).
The same issue cropped up here (discussed in the comments to the accepted answer):
Android Soft keyboard action button
From the final comment:
Looking at a few apps on my phone, it seems common to have the multiline box last, with a visible "Done" or "Send" button below it (e.g. Email app).
A simple way to work around this situation:
keep this attributes on the EditText:
android:inputType="textMultiLine"
android:scrollHorizontally="false"
then add this code to only hide the keyboard when ENTER is pressed:
editText.setOnEditorActionListener(new OnEditorActionListener()
{
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)
{
editText.setSelection(0);
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
return true;
}
else
{
return false;
}
}
});
If it is not about the look of the on-screen keyboard, you could simply put a input listener on the keyboard and fire the "done"-status if the user inputs a newline.
if you use the input option textImeMultiline with imeoptions flagnext and actionnext you get a next button instead of the cariage return
While none of the other solutions ever worked for me, the following worked beautifully and saved me days and days of more googling, with a few twists of my own of course. Unfortunately don't remember where I got the code from exactly and so cannot give the author the credit he/she so deserves.
In your Java code :
////////////Code to Hide SoftKeyboard on Enter (DONE) Press///////////////
editText.setRawInputType(InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD|InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
editText.setImeActionLabel("DONE",EditorInfo.IME_ACTION_DONE); //Set Return Carriage as "DONE"
editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event)
{
if (event == null) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
// Capture soft enters in a singleLine EditText that is the last EditText
// This one is useful for the new list case, when there are no existing ListItems
editText.clearFocus();
InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(), 0);
}
else if (actionId == EditorInfo.IME_ACTION_NEXT) {
// Capture soft enters in other singleLine EditTexts
} else if (actionId == EditorInfo.IME_ACTION_GO) {
} else {
// Let the system handle all other null KeyEvents
return false;
}
}
else if (actionId == EditorInfo.IME_NULL) {
// Capture most soft enters in multi-line EditTexts and all hard enters;
// They supply a zero actionId and a valid keyEvent rather than
// a non-zero actionId and a null event like the previous cases.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
// We capture the event when the key is first pressed.
} else {
// We consume the event when the key is released.
return true;
}
}
else {
// We let the system handle it when the listener is triggered by something that
// wasn't an enter.
return false;
}
return true;
}
});
I'm on 4.x and tried calling setHorizontallyScrolling() (with or without setLine() or setMaxLines()), as well as many different XML configurations to get the Done button to show. None of them worked. The bottom line is that if your EditText is multi-line, Android will always want to show the carriage return instead of the "Done" button, unless you put in some hack around this.
The least complication solution I found that doesn't involve remapping the behavior of the carriage return is here: https://stackoverflow.com/a/12570003/3268329. This solution will nullify Android relentless desire to force setting of the IME_FLAG_NO_ENTER_ACTION flag for multi-line views, which causes the Done button to disappear.
I struggled as well for quite some time, but i finally found a solution!
Just create a custom EditText class as such :
public class EditTextImeMultiline extends EditText {
public void init() {
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) {
for (int i = s.length(); i > 0; i--)
if (s.subSequence(i - 1, i).toString().equals("\n"))
s.replace(i - 1, i, "");
}
});
setSingleLine();
setHorizontallyScrolling(false);
this.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
EditTextImeMultiline.this.setLines(EditTextImeMultiline.this.getLineCount());
}
});
}
public EditTextImeMultiline(Context context) {
super(context);
init();
}
public EditTextImeMultiline(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public EditTextImeMultiline(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public EditTextImeMultiline(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
}
This class removes lineBreaks (\n), wraps the text as textMultiline would do, AND allows you to replace the Enter button by a ImeAction ;).
You just need to call it in your XML instead of the classic EditText class.
To explain the logic here :
Set the EditText as a singleLine to be able to show a ImeAction button instead of Enter.
Remove the horizontal scrolling to make the text go to the next line when reaching the end of the view.
Watch the layout changes with the onGlobalLayoutListener, and set it's "line" parameter to the "lineCount" of the current text held by the editText. This is what refreshes its height.
Working solution is here, create your custom EditTextView (just extend a textview) and override onInputConnection wit a piece of code youll find in accepted answer here: Multiline EditText with Done SoftInput Action Label on 2.3
If you use DataBinding, you should create a method that will catch actions. See also https://stackoverflow.com/a/52902266/2914140.
Some file with extension methods, for instance, BindingAdapters.kt:
#BindingAdapter("inputType", "action")
fun EditText.setMultiLineCapSentencesAndDoneAction(inputType: Int, callback: OnActionListener?) {
setRawInputType(inputType)
if (callback == null) setOnEditorActionListener(null)
else setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE ||
event?.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN
) {
callback.enterPressed()
return#setOnEditorActionListener true
}
return#setOnEditorActionListener false
}
}
interface OnActionListener {
fun enterPressed()
}
Then in XML:
<data>
<variable
name="viewModel"
type="YourViewModel" />
<import type="android.text.InputType" />
</data>
<EditText
android:imeOptions="actionDone"
android:inputType=""
app:action="#{() -> viewModel.send()}"
app:inputType="#{InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE}" />
For Compose UI the code is:
TextField(
...
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done
),
...
)