I'm starting with one programatically created AutoCompleteTextView view on my layout. After user typed valid data into the field, I'm creating a new AutoCompleteTextView object right under the first one by the same way, with the same parameters. I'm storing all of the field references in an ArrayList to keep up with the last one, this way I'm making sure that there will be new fields only under the one at the bottom (basically the last element in the reference list) - so it goes on at every field.
I would like to add this feature: when a new field is created, I change the last field's IME options to EditorInfo.IME_ACTION_NEXT and nextFocusForward attribute to the freshly created field - also programmatically. What I want to achieve: right when user presses Enter on the keyboard, last field's focus jumps to the new field. I'm using this code to set new IME options:
ArrayList<AutoCompleteTextView> fields = new ArrayList<>();
//Creating freshlyCreatedField, IT'S STILL NOT IN THE fields LIST!
AutoCompleteTextView currentlySelectedField = fields.get(fields.size() - 1); // last element
currentlySelectedField.setImeOptions(EditorInfo.IME_ACTION_NEXT);
currentlySelectedField.setNextFocusForwardId(freshlyCreatedField.getId());
currentlySelectedField.setSingleLine(true); //ACTV needs it to get IME to work
//Adding freshlyCreatedField to the fields list,
//so next time it will be the currentlySelectedField
But after running this code nothing happens to the currently selected AutoCompleteTextView object. Clearing and requesting focus did not work, and making it work by the reversed way (creating +1 "invisible" (technically GONE) field everytime) would be much more painful.
One more thing: after selecting any other field and re-selecting the one with configuration changes the Enter button works as it should first time! If I could do the same programmatically, it would solve my problem... so, any ideas how to do it? (Of course, I welcome better solutions also... ;) )
After some days, I've started to play with this problem again. Yes, it is a solution, but no, it's not so pretty as I would it to be like...
Anyhow, I created this code:
ArrayList<AutoCompleteTextView> fields = new ArrayList<>();
//Creating freshlyCreatedField, IT'S STILL NOT IN THE fields LIST!
//Last element
AutoCompleteTextView currentlySelectedField = fields.get(fields.size() - 1);
currentlySelectedField.setOnEditorActionListener(
new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
boolean handled = false;
if (actionId == EditorInfo.IME_ACTION_NEXT
|| actionId == EditorInfo.IME_ACTION_DONE
|| event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
freshlyCreatedField.requestFocus();
handled = true;
}
return handled;
}
});
//Adding freshlyCreatedField to the fields list,
//so next time it will be the currentlySelectedField
I put an EditorActionListener to the field actually under edit where the keyboard listens for one of the IME_ACTION_NEXT (if it's already set) and IME_ACTION_DONE action IDs (it's the default), or for the Enter key (for the case if someone would like to use physical keyboard). Then it jumps to the field below.
It works great, there aren't terrible problems with this approach. However I'm a little bit sad, because all of these possibilities stay invisible. The Done action button switches to Next only by clicking to somewhere else as I wrote it down in my question. So I think my "solution" can work, but it's not the best answer, so feel free to write your thoughts down if you have another idea... :)
Related
I am new to android development and currently trying to integrate material design into my app.
I would like to evaluate a simple form, for this purpose I used the components com.google.android.material.textfield.TextInputLayout and com.google.android.material.textfield.TextInputEditText for user input. Besides the text input, I need a date, which I want to read with a MaterialDatePicker.
I tried to display the MaterialDatePicker with OnFocusChangeListener, this works too, but I have two problems.
the display is a little bit delayed because first a keyboard is opened which is closed immediately after calling the MaterialDatePicker.
when the display is closed with the Back button, the focus is still on TextInputLayout. So I would have to change the focus first to open a MaterialDatePicker again.
This is how I implemented the OnFocusChangeListener
#Override
public void onFocusChange(View view, boolean selected) {
if( view.getId() == R.id.myId&& selected ){
MaterialDatePicker.Builder builder = MaterialDatePicker.Builder.datePicker();
MaterialDatePicker picker = builder.build();
picker.show( this.getParentFragmentManager(), "DATE_PICKER" );
}
}
Are there alternative components of Material Design that are better suited for the presentation? I would like to keep the behavior within the form, so as soon as the date is entered by the user, a small label should be displayed above, like this:
Thank you for your help.
I recently encountered the same problem.
The first issue concerning the keyboard, is solved by calling:
mTextInputEditText.setInputType(InputType.TYPE_NULL);
By setting the InputType to TYPE_NULL the keyboard won't open by clicking on the text field.
In addition, if you no longer want the user to be able to input any text, you can add:
mTextInputEditText.setKeyListener(null);
The second issue, to show the DatePicker again while it is already in focus, you can set an extra onClickListener:
mTextInputEditText.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
openDatePicker();
}
});
The OnClickListener is called as soon as the user clicks the text field again. Sadly it will not work with the first click.
You can look at this answer https://stackoverflow.com/a/11799891/9612595 for more information. Unfortunately, making the text field unfocusable resolves into weird behavior with the hint from Material.
I hope that helps!
Adding to luk321 answer. Instead of OnClickListener you can use OnTouchListener. For ex -
editText.setOnTouchListener((view, motionEvent) -> {
if(motionEvent.getAction() == MotionEvent.ACTION_UP){
//your code
}
return false;
});
It will work on first touch. Be sure to use ACTION.UP otherwise event will occur while scrolling also.
deliverDatePicker.editText?.setOnClickListener {
viewModel.onDatePickerClick()
}
deliverDatePicker.editText?.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
viewModel.onDatePickerClick()
}
}
Overriding setOnFocusChangeListener as well as setOnClickListener solves the first unregistered click event of #luk321 answer
My first android project! I have a Login Activity. Within it there are two EditText fields, "username" and "password". I've made a listener for when the user has completed the field, namely an onEditorActionListener. My motivation behind this is that I need the username and password fields as a String Variable to send to Volley and onto an API. The password Listener is nearly identical to the code below.
final EditText loginEditText_User = (EditText) findViewById(R.id.login_user);
loginEditText_User.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_NEXT){
// TODO Retrieve Username here
Log.d("[userName]", loginEditText_User.getText().toString());
loginEditText_Pass.requestFocus();
}
return true;
}
});
I've tried simply declaring a variable String userName before the listener and setting the value within the block via userName = loginEditText_User.getText().toString();, but this doesn't work because "the variable 'userName' is accessed from within inner class, needs to be declared final". However, but when declared final, I obviously "cannot assign value to final variable".
I've seen related questions such as this and this, but they're not quite the same thing.
Thanks in advance!
You can just define you userName variable as a field of your activity, and then you will have an access to it in your scope:
private String userName;
I just answered someone else's question about the same thing here: Edit text first time to input a letter validation
If you use a TextWatcher interface instead, you can get the updated text every time the user adds or removes a letter. Now, If you don't care about the current text until they hit your login button, then you could just do something like. editText.getText() and that returns the current text. The nice thing about the TextWatcher, is you can enable/disable the login button with valid/invalid text. And, it gets you around that whole 'final' issue you mentioned.
This is a known issue every programmer has to deal with a couple of times, there are some options to work around this problem :
1)Use a set function to change the value of the variable, functions can be called anywhere within an inner class.
2)Assign a new variable, which you initialized inside the inner class, with the value you want and use the new variable
I tried the soft keyboard from scratch and I found an issue when I press the Next button. It works as expected on other keyboards, so I think maybe it’s something that they didn’t think about when developing the sample. I didn’t try on other versions yet.
Version used: Android API 15
Source code from android-sdk/samples/android-15/SoftKeyboard
The problem: If you have a form with two text fields, after pushing the next button (the enter one at the bottom right), the cursor doesn't go to the next field.
Example: It’s possible to try using the contacts app:
Open contacts
Click some contact
Click the edit button at the top in the action bar
Look at the contact's emails, if there is only one text field press the + button so there are at least 2 text fields
Click the first text field to edit email #1
Type the next button on soft keyboard sample
The cursor will disappear and it won't go to the next field
(sometimes it crashes, but usually you can just touch the text field
again and everything goes normal)
While debugging I didn’t found any problem yet, but I had some places in the source code to look around:
At the com.example.android.softkeyboard.SoftKeyboard class:
At the public boolean onKeyDown(int keyCode, KeyEvent event) method there is:
case KeyEvent.KEYCODE_ENTER:
// Let the underlying text editor always handle these.
return false;
I looked upon it, while debugging it falls here and it looks alright, because the cursor change is not to be handled by the soft keyboard
Another method that I thought about is
public void onStartInput(EditorInfo attribute, boolean restarting)
Maybe it isn’t getting the cursor where it is so it just vanishes. But I couldn’t find a way to that yet so I don’t know if it is really what’s happenning.
New to programming, now to android. So I hope I dont annoy you to much.
How would I go about setting an onkeylistener at the top level of the app that captured the keyevent no matter what.
Basically what i have is a linear layout with dynamically added edittexts.
I want to capture the Enter key event and have it get the current edittext, perform some tests then create a new edittext and add it to the layout.
I know I can (and have) implement an onkeylistener to individual child views, but not being a programmer, the logic seems weird to create an edittext that listens for input to create another edittext that listens for input to create another.... (you see where this goes)
Can anyone point me in the right direction?
I have lots more info about what Im trying to do, I just dont know what is pertinent and what is not, so let me know if you need more.
Thanks for your time in advance,
Chris
Take a look at http://developer.android.com/reference/android/app/Activity.html#dispatchKeyEvent%28android.view.KeyEvent%29
What you want is to intercept all the events before they are processed by any View in the window. Return true if the event was handled or false if you want the childs to process the event further.
Like Ben said your activity can implement OnKeyListener then for each EditText you create, set the OnKeyListener to be the activity.
editText1.setOnKeyListener(this);
And then in your implementation of onKey you can handle the key press event.
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(v == editText1) {
// do something
} else if( v == editText2 ) {
// do something
}
return true; // return true if you handled the keypress
}
Your activity can implement OnKeyListener.
I've got a few EditText fields in a ListView. When I tap on one of the EditText fields, the keyboard slides into view (as it should), but the EditText field I tapped loses focus. I've tried using various InputMethodManager methods to make the keyboard start out in view (in order to get around the problem rather than truly solve it), but that didn't work - the keyboard was not in view when the Activity appeared.
The EditText's type is number, and when the keyboard is sliding in, it is a number keyboard, but when it finishes sliding and the EditText loses focus, it changes to the alphabetical keyboard (which reinforces the idea that the EditText no longer has focus).
My questions are these:
1) How can I make the selection of my EditText field and the subsequent sliding in of the soft keyboard not make my EditText lose focus?
... failing that...
2) How can I make the keyboard start out in view so it never has to slide in (thus avoiding the behavior I find so objectionable)?
My manifest does include android:windowSoftInputMode="stateAlwaysVisible", but the keyboard does not appear until I tap on an EditText. This ignoring of the 'stateAlwaysVisible' attribute seems to only occur in the emulator - on my provisioned device, it is honored so question number 2 above does work on the device... but not in the emulator.
Thanks for any help you can provide!
You need to change in your AndroidManifest.xml
Add android:windowSoftInputMode="adjustPan" in the activity holding the listview.
This will solve your problem.
<activity android:name=".MyEditTextInListView"
android:label="#string/app_name"
android:windowSoftInputMode="adjustPan">
Regards
Here is how I did it. The onFocusChangeListener() is called several times when you touch a EditText to type text into it. The sequence is:
If focus was on a different view, then that view loses focus
The target gains focus
Soft keyboard pops up.
This causes the target to lose focus
The code detects this situation and calls target.requestFocus()
The leftmost, topmost view gains focus, due to Android nonsense
The leftmost view loses focus, due to requestFocus being called
Target finally gains focus
//////////////////////////////////////////////////////////////////
private final int minDelta = 300; // threshold in ms
private long focusTime = 0; // time of last touch
private View focusTarget = null;
View.OnFocusChangeListener onFocusChangeListener = new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean hasFocus) {
long t = System.currentTimeMillis();
long delta = t - focusTime;
if (hasFocus) { // gained focus
if (delta > minDelta) {
focusTime = t;
focusTarget = view;
}
}
else { // lost focus
if (delta <= minDelta && view == focusTarget) {
focusTarget.post(new Runnable() { // reset focus to target
public void run() {
focusTarget.requestFocus();
}
});
}
}
}
};
The code above works well for the keyboard pop-ups. However, it does not detect the speech-to-text pop-up.
In my case, this is happening because when the ListView resizes, it re-creates all of the list items (i.e. it calls getView() again for each visible list item).
Because the EditText is within the layout that I'm returning from getView(), this means that it's a different instance of EditText than the one which had the focus previously. A secondary consequence is that when the soft-keyboard appears or disappears I found that I was losing the contents of the EditText.
Because I wanted my view to remain fully accessible (i.e. I want it to be resized instead of hidden behind the keyboard window with some parts not accessible), I couldn't use Frank's answer, which otherwise seems like the best approach.
I solved this by using an OnFocusChangeListener on the EditText to record the timestamp when the focus was lost, and then in getView() when recreating the list item, if the current time is within some threshold from when the focus was lost, call requestFocus() to give it back to the EditText in question.
You can also grab the text from the previous instance of the EditText at that point and transfer it to the new instance.
private class MyAdapter<Type> extends ArrayAdapter<String>
implements OnFocusChangeListener
{
private EditText mText;
private long mTextLostFocusTimestamp;
private LayoutInflater mLayoutInflater;
public MyAdapter(Context context, int resource, int textResourceId, ArrayList<String> data, LayoutInflater li) {
super(context, resource, textResourceId, data);
mLayoutInflater = li;
mTextLostFocusTimestamp = -1;
}
private void reclaimFocus(View v, long timestamp) {
if (timestamp == -1)
return;
if ((System.currentTimeMillis() - timestamp) < 250)
v.requestFocus();
}
#Override public View getView (int position, View convertView, ViewGroup parent)
{
View v = mLayoutInflater.inflate(R.layout.mylayout, parent, false);
EditText newText = (EditText) v.findViewById(R.id.email);
if (mText != null)
newText.setText(mText.getText());
mText = newText;
mText.setOnFocusChangeListener(this);
reclaimFocus(mText, mTextLostFocusTimestamp);
return v;
}
#Override public void onFocusChange(View v, boolean hasFocus) {
if ((v == mText) && !hasFocus)
mTextLostFocusTimestamp = System.currentTimeMillis();
}
}
In AndroidManifest.xml use adjustNothing in the activity that contain the views
<activity
android:name=".ActivityName"
android:windowSoftInputMode="adjustNothing">
You should test this code on a device with hardware keyboard always visible. The behavior may also happen here.
To avoid this you can have the keyboard always visible.. but that is not very easy as you can see by this thread:
https://groups.google.com/forum/#!topic/android-developers/FyENeEdmYC0
Theoretically you may have to create your own Android keyboard (although using as base the stock Android keyboard) as described here: Android: How to make the keypad always visible?
If the editText inside the listView just make sure that you inflate the View in the getView method with this way.
if (convertView == null)
convertView = LayoutInflater.from(context).inflate(R.layout.yourItemListLayout,
parent, false);
Edit: this work for some mobiles not all I use the answer from Mr.Frank above.
This guy had the same problem and more besides. He solved it by using a ScrollView and a LinearLayout instead of a ListView.
Add android:windowSoftInputMode="adjustResize" in the activity holding the listview or EditText. This will solve your problem.
<activity android:name=".MainActivity"
android:windowSoftInputMode="adjustResize">
</activity>
For those who come here with Xamarin or Xamarin.Forms:
I had the same issue as well but only with Android 5.x - all newer Versions including 8.1 worked well.
Obviously sheltond was right by saying:
In my case, this is happening because when the ListView resizes, it re-creates all of the list items (i.e. it calls getView() again for each visible list item).
My listview was resizing as well and no, Franks solution to set windowSoftInputMode="adjustPan" was no option for me because that means that the keyboard moves the listview partly off the screen.
All I had to do after hours of focus-debugging was setting the cell caching strategy of the Xamarin Forms ListView:
From
CachingStrategy="RecycleElement"
To
CachingStrategy="RetainElement"
This will stop the cells from being recreated. However, this might result in bad performance and high memory consumption for huge lists. Be aware.
In my case, I had called
root_scrollview.fullScroll(View.FOCUS_DOWN)
on my root ScrollView when Keyboard appears.
I replaced it with
login_scrollview.post(new Runnable() {
#Override
public void run() {
root_scrollview.scrollTo(0,root_container.bottom)
}
});
where root_container is the immediate child of root_scrollview. This solved the problem for me.
Note: Directly calling
root_scrollview.scrollTo(0,root_container.bottom) was not working.
Convert to RecyclerView
I believe that loss of focus on show or hide of the keyboard is not expected behavior and either should have been (or should be) a reported Android issue.
But too late now, 10 years after OP encountered it!
In my case, the disadvantage of switching from SOFT_INPUT_ADJUST_RESIZE to SOFT_INPUT_ADJUST_PAN outweighed the advantage of not losing focus.
Why? ADJUST_RESIZE is the officially preferred approach because ADJUST_PAN blocks part of your view and may prevent scrolling.
But thanks to an earlier answer to this question I became intensely suspicious of ListView.
To prove my suspicions I spent a day converting a complex ListView-based editor to RecyclerView.
I can confirm that soft keyboard state changes no longer affect EditText focus even though I am using ADJUST_RESIZE.
Seemingly painful I know - but perhaps the final result is nicer than sub-classing or tricky workarounds?