Others has the problem as doesn't working, I have the problem it is working ( and it shouldn't )
I have a data model, which is saved, and need to loaded back to GUI, Activity. It has a few spinner value.
The data is place to a common accesible class, a reference holder.
The activity's onCreate it will check if is on edit mode or not with:
editMode = getIntent().getBooleanExtra(EDIT_MODE_KEY, false);
It will load the UI elements from xml, and start selecting, filling values.
At editing mode, and at creation mode it should select values what has the data model.
At runtime ( after onResume() ) has some workflow: is something is selected at spinner1, than should refresh the spinner2 adapter content and so on.
It doesn't worked the plain .setSelection(positiontoSelect); so I have added a delayed post, now is working.
My problem is: I would like remove for temp the selection listener, call the selection and add back the listener.
Here is the code, which should be modified:
if (editedTimezonePosition > -1) {
final int positiontoSelect = editedTimezonePosition;
new Handler().postDelayed(new Runnable() {
public void run() {
OnItemSelectedListener listener = spSelectTimezone.getOnItemSelectedListener();
spSelectTimezone.setOnItemSelectedListener(null);
spSelectTimezone.setSelection(positiontoSelect);
spSelectTimezone.setOnItemSelectedListener(listener);
}
}, 250);
}
setting to null the listener has no effect: I am getting callback to my listener method.
If you have any idea how to fix it, please share it!
You could put a counter variable in your onItemSelected method. If it is 0 (meaning the first time the method has been called), do nothing but increment the variable. If it is greater than 0, execute the rest of your code.
private int mSpinnerSelectionCount=0;
public void onItemSelected(AdapterView<?> parent, View view,
int pos, long id) {
if(mSpinnerSelectionCount == 0){
mSpinnerSelectionCount++;
} else {
// Your normal selection code here
}
}
Related
Currently, when I
Launch a new Activity by clicking on AutoCompleteTextView's drop down
Close the launched Activity
AutoCompleteTextView's drop down is hidden.
I would like to preserve AutoCompleteTextView's drop-down state which includes
Drop down should not be hidden when gets back from launched Activity
Drop down's scroll position should be preserved.
I'm not exactly sure the reason why AutoCompleteTextView's dropdown will be hidden when I back from launched Activity. Hence, I had tried 2 things
Change windowSoftInputMode of launched Activity from stateAlwaysHidden to stateUnchanged.
In onActivityResult, when the launched Activity is closed, perform mSearchSrcTextView.showDropDown(); explicitly.
However, I am still facing the issue. The previous scroll position of AutoCompleteTextView's dropdown is not preserved. It is reset back to top of the list.
Here's the screen-shot to better illustrate the problem I am facing.
(Current AutoCompleteTextView's dropdown is scrolled to the end. I click on the last item and launch a new Activity)
(New Activity is launched. Now, I click on the BACK soft key twice, to close the keyboard and then close the Activity)
(Due to the explicit call of mSearchSrcTextView.showDropDown(); in onActivityResult, the drop down is shown again. However, its previous scrolled position is not being preserved. Start of list is being shown instead of end of list)
I was wondering, is there any way to preserved the AutoCompleteTextView's DropDown state, when closing a previous launched Activity?
For AutoCompleteTextView, it has a method called dismissDropDown(). I believe when back from newly launched activity, this function is being triggered. So we workaround this problem by extending AutoCompleteTextView & override it's dismissDropDown().
We add a boolean flag temporaryIgnoreDismissDropDown, to indicate whether to temporarily ignore dismissDropDown.
public class MyAutoCompleteTextView extends AutoCompleteTextView {
private boolean temporaryIgnoreDismissDropDown = false;
.....
#Override
public void dismissDropDown() {
if (this.temporaryIgnoreDismissDropDown) {
this.temporaryIgnoreDismissDropDown = false;
return;
}
super.dismissDropDown();
}
public void setTemporaryIgnoreDismissDropDown(boolean flag) {
this.temporaryIgnoreDismissDropDown = flag;
}
}
Before launching new Activity, we set dismissDropDown to true. After coming back from launched activity, dismissDropDown is called. The override method checks if temporaryIgnoreDismissDropDown is true, just set it to false & do nothing. So the real dismissDropDown is skipped.
// myAutoCompleteTextView is instance of MyAutoCompleteTextView
myAutoCompleteTextView.setTemporaryIgnoreDismissDropDown(true);
// launch new Activity
startActivity(....);
Hope this help, good luck!
After an hour of coding, much trying and a lot of googling around, I've put together a solution that does just what you want. It uses reflection to access the ListView within the Dropdown menu and to access the dropdown state when you leave the activity.
The code for this is kinda long, so I'll walk you through all the parts. Firstly, I have some variables we will need:
boolean wasDropdownOpen;
int oldDropdownY;
Handler handler;
The handler will be neccessary for later, as we have to do a little trick in the onResume() method. Initialize it as usual in your onCreate() method:
handler = new Handler(getMainLooper());
Now, let's get to the tricky part.
You need to call the following method before you start any activity. It can't be done in onPause() since the Dropdown menu is already closed when this method is called. In my test code I've overridden the startActivity() and startActivityForResult() method, and called it there, but you can do this however you like.
private void processBeforeStart() {
ListPopupWindow window = getWindow(textView);
if(window == null) return;
wasDropdownOpen = window.isShowing();
ListView lv = getListView(window);
if(lv == null) return;
View view = lv.getChildAt(0);
oldDropdownY = -view.getTop() + lv.getFirstVisiblePosition() * view.getHeight();
}
This will save your dropdown ListView's state for later. Now, we will load it. This is the onResume() method we will need for this:
#Override
protected void onResume() {
super.onResume();
if (wasDropdownOpen)
textView.showDropDown();
handler.postDelayed(new Runnable() {
#Override
public void run() {
ListView lv = getListView(getWindow(textView));
if (lv != null)
scrollToY(lv, oldDropdownY);
}
}, 150);
}
First of all, let me explain this method. We saved the state if the dropdown was open, so we reopen the menu if it was. Simple. The next part is the scrolling. We need to do this in a Handler because the UI is not yet fully loaded when onResume() is called and therefore the ListView is still inaccessible.
The scrollToY() method you see there is a modified version of the code from this post, as Android's ListView does not have an inbuilt method to set the scroll position as precisely as we want it here.
The implementation of this method is as follows:
private void scrollToY(ListView lv, int position) {
int itemHeight = lv.getChildAt(0).getHeight();
int item = (int) Math.floor(position / itemHeight);
int scroll = (item * itemHeight) - position;
lv.setSelectionFromTop(item, scroll);// Important
}
Now, you've probably seen the getWindow() and getListView() methods I've used above. These are the reflection methods, which we have to use because Android does not expose a public API to access the ListView within the ListPopupWindow of the AutoCompleteTextView. Additionally, the DropDownListView, a subclass of ListView that is actually used within this object, is not visible to the oudside as well, so we have to use Reflection once again.
Here is the implementation of my two helper methods:
private ListView getListView(ListPopupWindow window) {
for (Field field : window.getClass().getDeclaredFields()) {
if (field.getType().getName().equals("android.widget.DropDownListView")) {
field.setAccessible(true);
try {
return (ListView) field.get(window);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return null;
}
private ListPopupWindow getWindow(AutoCompleteTextView tv) {
Class realClass = tv.getClass().getName().contains("support") ? tv.getClass().getSuperclass() : tv.getClass();
for (Field field : realClass.getDeclaredFields()) {
if (field.getType().getName().equals(ListPopupWindow.class.getName())) {
field.setAccessible(true);
try {
return (ListPopupWindow) field.get(tv);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return null;
}
I've tested this on Android O (API level 26) and it works just as you described you want it to work.
I hope that the effort I put into this answer gets me a chance on the Bounty ;-)
It sounds like you've already figured out how to show the drop-down on demand (via showDropDown()), so I'll only address how to restore the scroll position of the dropdown.
You can access the first visible position of the dropdown like this:
autocomplete.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int firstVisiblePosition = parent.getFirstVisiblePosition();
// save this value somehow
}
});
Save the value of this int however you'd like (in memory, via onSaveInstanceState(), pass it through to the started activity so that it can pass it back via onActivityResult(), etc). Then, wherever you re-show the dropdown, do this:
autocomplete.showDropDown();
autocomplete.setListSelection(firstVisiblePosition);
The shortcoming of this technique is that it makes the item at firstVisiblePosition completely visible, so if it was halfway scrolled out of view, the list position won't be restored perfectly. Unfortunately, I don't believe there's any way to save/restore this partial-view offset.
i have created two spinners both of different class.i want to set content of one spinner based on selected value of other spinner which is having in other class.can any one help me.thanks in advance.
There is a solution,you might feel it lengthy to implement but it would perfectly work for you,i think!
Lets say you have spinner_one in your ActivityOne.class and spinner_two in your ActivityTwo.class. And you want to populate spinner_2 based on what is selected in spinner_1.
Then,
spinner_1.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
// save your selected item into a SharedPreference Variable
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
Now,for spinner_2:
String spinner1_value=get value from SharedPreference;
if(spinner1_value.equals("something_1"))
{
//populate spinner_2 accordingly
}
else if(spinner1_value.equals("something_2"))
{
//populate spinner_2 accordingly
}
else
{
//populate spinner_2 accordingly
}
And don't forget to put this code to populate spinner_2 in onResume() of your ActivityTwo.class,so that it will reflect the changes user made in spinner_1 value.
This will allow you to change spinner_2 content according to spinner_1 value as and when user changes it.
The short answer is that you can do this programatically - either by assigning a new ArrayAdapter to the Spinner, or by removing/adding items from the existing ArrayAdapter.
The longer answer is that you have to be very careful in relying on OnItemSelectedListener.onItemSelected(), since it will not get called if the old selection and a new selection happen to be in the same position, and Spinner.getSelectedItemPosition(), since it can actually return positions greater than the number of items in the ArrayAdapter if you're removing items on the fly.
I have a system of three cascaded Spinners, which in turn can drive the contents of other Buttons and Texts. I was able to get 95% solutions until I recognized the above and changed my view of what was authoritative and what was not - by making the Spinner subservient to the logic that determines content, rather than the other way around. So rather than calling setSelection() and relying on the onItemSelected() callback, do all your cascading logic outside the handler, and only then call setSelection() and return back up the chain.
Adapted from my working code:
class Spinster extends Activity {
...
private void setSpinnerOne( int pos ) {
// 1. Do our own chores, etc
doSomeStuff();
mSomeText.setText( "Blah!" );
mSomeButton.setEnabled( some_condition );
// 2. Populate dependent Spinner based on pos
populateSpinnerTwoAdapter( pos );
// 3. Cascade by calling SpinnerTwo logic (not the Spinner itself)
lSpinnerTwoPos = someNiceFunction();
setSpinnerTwo( lSpinnerTwoPos );
// 4. Now set SpinnerOne
mSpinnerTwo.setSelection( pos );
}
private void setSpinnerTwo( int pos ) {
// Follows the same pattern as setSpinnerOne(), but cascades to SpinnerThree
}
private void setSpinnerThree( int pos ) {
// Follows the same pattern as setSpinnerOne(), but need not cascade
}
...
private OnItemSelectedListener item_select = new OnItemSelectedListener() {
public void onItemSelected( AdapterView parent, View v, int position, long id )
{
...
int lId = parent.getId();
if ( lId == R.id.spinner_one ) {
setSpinnerOne( position );
} else if ( lId == R.id.spinner_two ) {
setSpinnerTwo( position );
} else if ( lId == R.id.spinner_three ) {
setSpinnerThree( position );
}
}
}
}
I've left out a small detail here, that of setting and clearing a guard variable in the setSpinner*() methods so that they don't bother doing all their work again when onSelectedItemListener() calls them back. I think the basic logic is easier to show by leaving it out. (And they don't strictly need to be there - onItemSelected() will not be called the second time round.)
I could post a real, working example if this is deemed to abstract.
I create a layout in code in onCreate. Inside this layout, i have spinner with registred listener. When user changes item in a spinner, new data is read and layout must be changed according to data. I do this with the same function as in onCreate (i create scrollview and other views and call setContentView(scollView) on the last line).
The layout changes correctly but everything blocks, spinner and buttons can't be clicked anymore. Logcat displays no error(only lots of GC freed x objects).
I tried calling scrollview.removeAllViews() before trying to redraw layout, but that doesn't help either.
What am i missing here?
Presumably you are creating a whole new set of button and spinner objects. You need to register all the Listeners again to these new objects.
I found a problem in my onItemSelected function, where i forgot to change boolean "firstTime", so this function was called over and over.
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (firstTime) {
//do nothing (just change the flag) because we don't want this called during UI building
firstTime=false;
}
else {
posit=pos;
String koda=Roaming.m.get(parent.getItemAtPosition(pos).toString());
Roaming.operaterji.clear();
Roaming.parsePrices(json, koda);
getAll();
firstTime=true; //FORGET TO SET FLAG BACK TO TRUE, SO THIS WAS CALLED IN A LOOP
makeGui();
}
}
I'm in a trouble managing a spinner, so may I ask for your help ?
I have a spinner with its adapter.
I initialize the spinner with a list of values when starting my activity.
Then I force the selected value to be the one used in the object that I manage.
Once the screen is initialized :
When the user selects a value in the spinner, according to the selected value, I may continue (or not) to another activity for let the user choose a complementary and necessary value.
If the user "cancels" this second activity, I want to rollback the spinner to its previous selected value, and cancel some actions made in the meantime.
If the user goes to the end of the second activity, everything is fine and I want juste to refresh the spinner display with the datas selected in the second activity (I overload the getView method in the adapter to do this).
Overall, I can easily do all of this, however, when I force the selected value in the spinner at the begining of my activity, or whene returning back from the second activity by "Cancel", the change value event is catched and the second activity is triggered (the user did not click anything at all).
How would you allow the second activity to be lauched only if the change of the selected value in the spinner is due to a manual action from the user, and prevent that same second activity to be launched when the value of spinner is changed "in the code "?
I tried many solutions, as setting a boolean into the adapter that tells if the next event will be raised because of an "in the code" action.
Or also putting a boolean in the adapter that tells if the adapter has initialised itself, and I force that boolean to true on the forst change catched event.
But nothing that really works fine.
Thank you for your help.
Oliver
I've always solved that issue with boolean flags, it isnt pretty at all, but it works if you think it through.
The idea is more or less, create a global usable boolean and init with false, in the onSelectedItemListener() use that boolean to choose wether or not to trigger the action, the important thing to remember is to set it to true after the computer has selected it the first time automatically, and reset it to false in the onResume() method.
This isnt perfect but it should work.
Edit:
bool spinnerUsable1;
bool spinnerUsable2;
int positionSpinner;
public void onCreate(Bundle savedInstanceState){
spinnerUsable1 = false;
spinnerUsable2 = true;
if(savedInstanceState != null){
positionSpinner = savedInstanceState.getInt("posSpinner");
if(positionSpinner != 0) spinnerUsable2 = false;
}
//Declare your spinner, set the on item selected lister
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
boolean spinnerUsable = (spinnerUsable1 && spinnerUsable2);
if (!spinnerUsable1) {
spinnerUsable1 = true;
} else if (!spinnerUsable2) {
spinnerUsable2 = true;
}
if (spinnerUsable) {
//Action;
}
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// Nothing
}
});
}
Something like this should work.
It appears that android's Spinner class (and possibly ListView in general, although I don't know for sure) calls your OnItemSelectedListener's onItemSelected() method after you call setAdapter(), even if the user hasn't explicitly selected anything yet.
I can see how this would be useful in many situations, but there are times when I only want onItemSelected() to be called when an item is actually specifically selected.
Is there a way to control this behaviour and have Spinner NOT call onItemSelected() after setting the adapter?
I haven't used this solution for very long yet so I'm not totally confident that it works as expected, but I've had luck so far with this workaround:
spinner.setOnItemSelectedListener( new OnItemSelectedListener() {
protected Adapter initializedAdapter = null;
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// Always ignore the initial selection performed after setAdapter
if( initializedAdapter !=parent.getAdapter() ) {
initializedAdapter = parent.getAdapter();
return;
}
...
}
}
Is there a better way?
Add listener to spinner like below:
spinner.post(new Runnable(){
public void run()
{
spinner.setOnItemSelectedListener( new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
...
}
}
}
});
I've used the setTag and getTag methods, and created a resource id called "spinnerstate".
Then whenever I programmatically set the adapter, I set the "spinnerstate" tag to "init", and in the fired event, set it to "ready" and ignore the event. (note my code is Mono for Android se it will look different):
Set Adapter:
profileSpn.SetTag (Resource.Id.spinnerstate, "init");
profileSpn.Adapter = new ArrayAdapter (this, Android.Resource.Layout.SimpleSpinnerItem, items.ToArray ());
Item Selected event:
string state = (string)((Spinner)sender).GetTag (Resource.Id.spinnerstate);
if (state == "init") {
((Spinner)sender).SetTag (Resource.Id.spinnerstate, "ready");
return;
}
I agree that this is not desired behaviour in almost 100% of cases, and I don't think it's good design on the part of Google, but there you go.
I did similar things before, I used count value. Using parent adapter object is incomplete because it can be a problem when view is refreshed or getView() called again.
Therefore, I recommend that using array of counter.
At first, define array of count in adapter globally.
private int isInitializedView[];
And then initialize it on getView().
isInitializedView[position] = 0;
In the selection listener, do something that you want if it already initialized.
holder.mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
isInitializedView[position]++;
if(isInitializedView[position] > 1) {
// do someting that you want
}
}
#Override
public void onNothingSelected(AdapterView<?> parentView) {}
});
(Note that isInitializedView[position]++; can be come after if() routine, and only trigger event when this value is >0 . It's your choice.)
I had three spinner in my activity and all spinner adapter data has been filled at runtime(from web-service data after call from onCreate method). So it automatically call onItemSelected(AdapterView<?> parent, View view, int position, long id) method of spinner.
I solved this issue by using onUserInteraction() method of activity
check this method that user is interacting with spinner or not. if yes then perform the action else not
Declare isUserIntract boolean variable globally
in onItemSelected method use following procedure
If(isUserIntract)
{
//perform Action
}
else{
//not perform action
}
use below code in activity
#Override
public void onUserInteraction() {
super.onUserInteraction();
isUserIntract = true;
}