Google Glass: open menu on touch in regular Card - android

How do I have the menu pop up on a regular Card. I have tried to use this code:
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
openOptionsMenu();
return true;
}
return false;
}
But it doesn't seem to work. Also, is it possible to put Live Cards in an Activity like I did here. I believe I am using Static Cards, but eventually I will need access to the camera and video functions through these Cards.
Activity
public class NewChecklistActivity extends Activity {
private GestureDetector mGestureDetector;
private List<Card> mCards;
private CardScrollView mCardScrollView;
StepCardScrollAdapter adapter;
Intent checklistIntent;
String allStepsJSONString;
ArrayList<String[]> allStepsArray;
ArrayList<String[]> checklistsArray;
ArrayList<String[]> stepsArray;
ArrayList<String[]> checklistSteps;
#SuppressWarnings("unchecked")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checklistsArray = (ArrayList<String[]>) this.getIntent().getSerializableExtra("checklists");
stepsArray = (ArrayList<String[]>) this.getIntent().getSerializableExtra("steps");
createCards();
mCardScrollView = new CardScrollView(this);
adapter = new StepCardScrollAdapter();
mCardScrollView.setAdapter(adapter);
mCardScrollView.activate();
setContentView(mCardScrollView);
mCardScrollView.setOnItemClickListener(adapter);
checklistIntent = new Intent(this, ChecklistActivity.class);
}
private void createCards() {
mCards = new ArrayList<Card>();
Card card;
String[] checklist;
for (int i = 0; i < checklistsArray.size(); i++) {
checklist = checklistsArray.get(i);
card = new Card(this);
card.setText(checklist[1]);
mCards.add(card);
}
}
private class StepCardScrollAdapter extends CardScrollAdapter implements OnItemClickListener {
#Override
public int findIdPosition(Object id) {
return -1;
}
#Override
public int findItemPosition(Object item) {
return mCards.indexOf(item);
}
#Override
public int getCount() {
return mCards.size();
}
#Override
public Object getItem(int position) {
return mCards.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
return mCards.get(position).toView();
}
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
checklistIntent.putExtra("steps", getChecklistSteps(adapter.findItemPosition(position)));
startActivity(checklistIntent);
}
}

It looks like you're using a CardScrollView, which is going to intercept all of the touchpad gestures and that's why your onKeyDown method isn't getting called.
For this specific case, you can attach an OnItemClickListener to the card scroller and use that listener to find out when one of the cards is tapped. Then, call openOptionsMenu from there.
If you need to have different menu items for different cards, you'll need to store the index of the card that gets passed into the listener and then use that information inside onPrepareOptionsMenu to load different items. The flow would look like this:
You create a private field in your activity class that will store the index of the last tapped card.
In your onItemClick method, you store the index you're given in that field, and then you call openOptionsMenu.
This causes onPrepareOptionsMenu to be called. Override that method and do whatever modifications you need to the menu based on the index stored in that field (inflate a menu, add/remove items, etc.). You have to do this inside onPrepareOptionsMenu instead of onCreateOptionsMenu because you want it to happen every time the user taps, not just the first time the menu is initialized.
Handle onOptionsItemSelected as you normally would.

Related

How to temporarily stop clicking effect before changing adapter data?

background
When choosing an item from a listView, I change its data and call notifyDataSetChanged.
The problem
Since it's the same listView, when I click the item, the effect stays for the view that will be used after the notifyDataSetChanged.
This is especially noticeable on Android Lollipop, where the ripple can continue after the listView gets refreshed with new data.
The code
Here's a sample code showing the problem:
public class MainActivity extends ActionBarActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView=(ListView)findViewById(R.id.listView);
listView.setAdapter(new BaseAdapter()
{
int pressCount=0;
#Override
public int getCount()
{
return 100;
}
#Override
public Object getItem(int position)
{
return null;
}
#Override
public long getItemId(int position)
{
return 0;
}
#Override
public View getView(int position,View convertView,ViewGroup parent)
{
View rootView=convertView;
if(rootView==null)
{
rootView=LayoutInflater.from(MainActivity.this).inflate(android.R.layout.simple_list_item_1,parent,false);
rootView.setBackgroundResource(getResIdFromAttribute(MainActivity.this,android.R.attr.selectableItemBackground));
rootView.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
pressCount++;
notifyDataSetChanged();
}
});
}
TextView tv=(TextView)rootView;
tv.setText("text:"+(pressCount+position));
return rootView;
}
});
}
public static int getResIdFromAttribute(final Activity activity,final int attr)
{
if(attr==0)
return 0;
final TypedValue typedvalueattr=new TypedValue();
activity.getTheme().resolveAttribute(attr,typedvalueattr,true);
return typedvalueattr.resourceId;
}
}
The question
How can I temporarily stop the selection effect till the next time anything is clicked on the listView (but also resume allowing it for the next time the user clicks an item) ?
OK, I've found the answer. It seems it's a known issue, and the solution is quite simple (shown here) :
ViewCompat.jumpDrawablesToCurrentState(view);
Weird thing is, it works for me only when I call it via Handler.post(...) .
Wonder why (as the view is already during animation), and if there's a better solution.

ParseQueryAdapter get position of object

I am trying to figure out how to find the position of a custom ParseObject in a ParseQueryAdapter<> so that I can set the position of a spinner or check rows of a check list in my Android project.
I am currently using a custom adapter extending ParseQueryAdapter<Vendor> where Vendor extends ParseObject. I also have an Item that extends ParseObject that is associated with a Vendor. In this specific example, if I want to edit an Item, I want the previously chosen Vendor displayed in the spinner. I want to set the selection of a spinner that is backed by my custom ParseQueryAdapter<Vendor>. Before I integrated Parse, I was using an ArrayAdapter<CharSequence> which could do:
String vendorName = Vendor.getName();
int position = adapter.getPosition(vendorName);
spinner.setSelection(position);
I was thinking that I could get the position of a Vendor object like when I was just using the String name in the array, but ParseQueryAdapter doesn't have a getPosition method. I was thinking of making a custom method to do so, but am at a loss of how to find the position of the Vendor. I am also thinking that I might need .isEqualTo(Vendor vendor) method in my Vendor class.
You need to facade the ParseQuery Adapter, do something like this :
public class CustomListAdapter extends BaseAdapter {
private final YourParseAdapter adapter;
public CustomListAdapter(final Context context) {
this.adapter = new YourParseAdapter(context);
adapter.registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
notifyDataSetChanged();
}
});
}
public void reloadObjects() {
adapter.loadObjects();
}
// Customize the layout by overriding getItemView
#Override
public View getView(int i, View v, ViewGroup viewGroup) {
View result = adapter.getView(i, v, viewGroup);
// HERE YOU HAVE THE VIEW GENERATED BY PARSE AND THE ITEM POSITION
return result;
}
#Override
public int getCount() {
return adapter.getCount();
}
#Override
public Object getItem(int i) {
return adapter.getItem(i);
}
#Override
public long getItemId(int i) {
return adapter.getItemId(i);
}
}
I figured out what I needed to do. Instead of using a ParseQueryAdapter<Vendor>, I got my List<Vendor> from a separate query then I used an ArrayAdapter<Vendor> so that I could use the adapter.getPosition(obj) method. Since the ArrayAdapter uses .indexOf(Obj) which in turn uses .equals(Obj), I added to my Vendor object:
#Override
public boolean equals(Object o) {
return this.getObjectId().equals(((Vendor) o).getObjectId());
}
I just needed to have a way for my custom object to be compared.

Using Android, how can I select rows from a ListView which contains Button controls

I have the following, very simple test program for using a ListView. I create a ListView and set it as the content view. I set a ListAdapter which supplies the rows. There are 30 rows, and each row consists of a LinearLayout ViewGroup. Into that ViewGroup, I place a TextView and a Button. When I run the program, I find that I cannot select rows of the list. I can, however, scroll the list and click the button.
If I remove the button from the LinearLayout (so that it contains only the TextView), then I am able to select rows of the list. I would
like to be able to have buttons on my individual row views, and still be able to select rows of the list. On another forum, someone said that this was possible, but I am at a loss as to how to accomplish it.
Can anyone give me a clue?
Thanks.
public class ListViewTest extends Activity implements ListAdapter
{
int m_count;
DataSetObserver m_observer;
public ListViewTest()
{
m_count = 30;
m_observer = null;
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
ListView lv = new ListView(this);
lv.setAdapter(this);
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
setContentView(lv);
}
#Override
public boolean areAllItemsEnabled() {
return true;
}
#Override
public boolean isEnabled(int position) {
return true;
}
#Override
public int getCount()
{
return m_count;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LinearLayout vg = new LinearLayout(this);
TextView tv = new TextView(this);
tv.setText("ListItem");
Button bv = new Button(this);
bv.setText("Button");
vg.addView(tv);
vg.addView(bv);
return(vg);
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public boolean hasStableIds() {
return false;
}
#Override
public boolean isEmpty() {
return false;
}
#Override
public void registerDataSetObserver(DataSetObserver observer)
{
m_observer = observer;
}
#Override
public void unregisterDataSetObserver(DataSetObserver observer) {
}
}
As the other answers point out, whether or not you can select ListView rows as full items on their own depends on whether or not those rows contain focusable items. However, the solution is usually not setting focusable=false on your buttons or the like. That will prevent your app from being navigable with a d-pad, trackball, arrow keys, or what have you.
You want your list items to be able to control their own focus properties. You want setItemsCanFocus. This will disable the special focus/selection handling that ListView normally uses to treat list items as a single unit.
Now you can set a listener on the layout you use as the top-level element in your rows, set a stateful background drawable on it to display focus/press state, as well as focusLeft/Right properties to control focus shifting within the item itself.

Android: Programmatically animate between images in Gallery widget

Note: As of Jellybean the gallery widget is deprecated. A ViewPager should be used instead.
I'd like to programmatically move between images in the Gallery widget, with animation.
I can change the currently displaying image using the setSelection(int position) method, however that does not animate. Then there's setSelection(int position, bool animate) but the extra boolean on the end there doesn't appear to do anything.
In the source of Gallery it appears that it can handle DPAD key-presses, so a work-around I thought of was to fake the key-presses. Eg.
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT))
However I can't get this working for some reason. Anyone tried this?
I notice three of the widget's methods I'd love to use moveNext(), movePrevious() and scrollToChild() are all private and unusable.
Does anyone know how I might be able to do this?
Just call the key press handler for the gallery directly:
public boolean onKeyDown(int keyCode, KeyEvent event)
i.e
Gallery gallery = ((Gallery) findViewById(R.id.gallery));
gallery.onKeyDown(KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(0, 0));
One important thing - this solution works only if child that is on left/right was already created, which means that it has to be 'visible'. If you have your image on fullscreen - consider setting spacing to -1 value.
You can Animate using dispatchKeyEvent or calling onFling directly.
Here is sample code for dispatchKeyEvent:
KeyEvent evtKey = new KeyEvent(0, KeyEvent.KEYCODE_DPAD_RIGHT);
dispatchKeyEvent(evtKey);
Use gallery.setSelected(int); Here is a simple example.
public class Splash extends Activity {
ArrayList objects = new ArrayList();
Gallery g;
int i = 0;
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.photos);
g = (Gallery) findViewById(R.id.gallery);
objects.add(getResources().getDrawable(R.drawable.icon));
objects.add(getResources().getDrawable(R.drawable.icon));
objects.add(getResources().getDrawable(R.drawable.icon));
objects.add(getResources().getDrawable(R.drawable.icon));
objects.add(getResources().getDrawable(R.drawable.icon));
objects.add(getResources().getDrawable(R.drawable.icon));
g.setAdapter(new CustomAdapter(this, objects));
g.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView arg0, View arg1,
int arg2, long arg3) {
Log.i("", "selected " + arg2);
}
#Override
public void onNothingSelected(AdapterView arg0) {}
});
}
#Override
public void onBackPressed() {
g.setSelection(i++);
}
private class CustomAdapter extends BaseAdapter {
private Context mCtx;
private List objects;
public int getCount() {
return this.objects.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public CustomAdapter(Context context, ArrayList objects) {
super();
mCtx = context;
this.objects = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView row = (ImageView) convertView;
if (row == null) {
row = new ImageView(mCtx);
row.setBackgroundDrawable(objects.get(position));
}
return row;
}
}
}
In the end I wrote my own version of the Gallery widget with the help of the code at this site.
I then wrote my own method which uses mFlingRunnable.startUsingDistance(distance);
Now I can programmatically animate the gallery between images.
Try this
mGallery.onFling(null,null, velocity,0);
http://groups.google.com/group/android-developers/browse_thread/thread/9140fd6af3061cdf#

How to keep onItemSelected from firing off on a newly instantiated Spinner?

I've thought of some less than elegant ways to solve this, but I know I must be missing something.
My onItemSelected fires off immediately without any interaction with the user, and this is undesired behavior. I wish for the UI to wait until the user selects something before it does anything.
I even tried setting up the listener in the onResume(), hoping that would help, but it doesn't.
How can I stop this from firing off before the user can touch the control?
public class CMSHome extends Activity {
private Spinner spinner;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Heres my spinner ///////////////////////////////////////////
spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.pm_list, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
};
public void onResume() {
super.onResume();
spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
}
public class MyOnItemSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> parent,
View view, int pos, long id) {
Intent i = new Intent(CMSHome.this, ListProjects.class);
i.putExtra("bEmpID", parent.getItemAtPosition(pos).toString());
startActivity(i);
Toast.makeText(parent.getContext(), "The pm is " +
parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
}
public void onNothingSelected(AdapterView parent) {
// Do nothing.
}
}
}
The use of Runnables is completely incorrect.
Use setSelection(position, false); in the initial selection before setOnItemSelectedListener(listener)
This way you set your selection with no animation which causes the on item selected listener to be called. But the listener is null so nothing is run. Then your listener is assigned.
So follow this exact sequence:
Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
Referring to the answer of Dan Dyer, try to register the OnSelectListener in a post(Runnable) method:
spinner.post(new Runnable() {
public void run() {
spinner.setOnItemSelectedListener(listener);
}
});
By doing that for me the wished behavior finally occurred.
In this case it also means that the listener only fires on a changed item.
I would have expected your solution to work -- I though the selection event would not fire if you set the adapter before setting up the listener.
That being said, a simple boolean flag would allow you to detect the rogue first selection event and ignore it.
I created a small utility method for changing Spinner selection without notifying the user:
private void setSpinnerSelectionWithoutCallingListener(final Spinner spinner, final int selection) {
final OnItemSelectedListener l = spinner.getOnItemSelectedListener();
spinner.setOnItemSelectedListener(null);
spinner.post(new Runnable() {
#Override
public void run() {
spinner.setSelection(selection);
spinner.post(new Runnable() {
#Override
public void run() {
spinner.setOnItemSelectedListener(l);
}
});
}
});
}
It disables the listener, changes the selection, and re-enables the listener after that.
The trick is that calls are asynchronous to the UI thread, so you have to do it in consecutive handler posts.
Unfortunately it seems that the two most commonly suggested solutions to this issue, namely counting callback occurrences and posting a Runnable to set the callback at a later time can both fail when for example accessibility options are enabled. Here's a helper class that works around these issues. Further explenation is in the comment block.
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
/**
* Spinner Helper class that works around some common issues
* with the stock Android Spinner
*
* A Spinner will normally call it's OnItemSelectedListener
* when you use setSelection(...) in your initialization code.
* This is usually unwanted behavior, and a common work-around
* is to use spinner.post(...) with a Runnable to assign the
* OnItemSelectedListener after layout.
*
* If you do not call setSelection(...) manually, the callback
* may be called with the first item in the adapter you have
* set. The common work-around for that is to count callbacks.
*
* While these workarounds usually *seem* to work, the callback
* may still be called repeatedly for other reasons while the
* selection hasn't actually changed. This will happen for
* example, if the user has accessibility options enabled -
* which is more common than you might think as several apps
* use this for different purposes, like detecting which
* notifications are active.
*
* Ideally, your OnItemSelectedListener callback should be
* coded defensively so that no problem would occur even
* if the callback was called repeatedly with the same values
* without any user interaction, so no workarounds are needed.
*
* This class does that for you. It keeps track of the values
* you have set with the setSelection(...) methods, and
* proxies the OnItemSelectedListener callback so your callback
* only gets called if the selected item's position differs
* from the one you have set by code, or the first item if you
* did not set it.
*
* This also means that if the user actually clicks the item
* that was previously selected by code (or the first item
* if you didn't set a selection by code), the callback will
* not fire.
*
* To implement, replace current occurrences of:
*
* Spinner spinner =
* (Spinner)findViewById(R.id.xxx);
*
* with:
*
* SpinnerHelper spinner =
* new SpinnerHelper(findViewById(R.id.xxx))
*
* SpinnerHelper proxies the (my) most used calls to Spinner
* but not all of them. Should a method not be available, use:
*
* spinner.getSpinner().someMethod(...)
*
* Or just add the proxy method yourself :)
*
* (Quickly) Tested on devices from 2.3.6 through 4.2.2
*
* #author Jorrit "Chainfire" Jongma
* #license WTFPL (do whatever you want with this, nobody cares)
*/
public class SpinnerHelper implements OnItemSelectedListener {
private final Spinner spinner;
private int lastPosition = -1;
private OnItemSelectedListener proxiedItemSelectedListener = null;
public SpinnerHelper(Object spinner) {
this.spinner = (spinner != null) ? (Spinner)spinner : null;
}
public Spinner getSpinner() {
return spinner;
}
public void setSelection(int position) {
lastPosition = Math.max(-1, position);
spinner.setSelection(position);
}
public void setSelection(int position, boolean animate) {
lastPosition = Math.max(-1, position);
spinner.setSelection(position, animate);
}
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
proxiedItemSelectedListener = listener;
spinner.setOnItemSelectedListener(listener == null ? null : this);
}
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position != lastPosition) {
lastPosition = position;
if (proxiedItemSelectedListener != null) {
proxiedItemSelectedListener.onItemSelected(
parent, view, position, id
);
}
}
}
public void onNothingSelected(AdapterView<?> parent) {
if (-1 != lastPosition) {
lastPosition = -1;
if (proxiedItemSelectedListener != null) {
proxiedItemSelectedListener.onNothingSelected(
parent
);
}
}
}
public void setAdapter(SpinnerAdapter adapter) {
if (adapter.getCount() > 0) {
lastPosition = 0;
}
spinner.setAdapter(adapter);
}
public SpinnerAdapter getAdapter() { return spinner.getAdapter(); }
public int getCount() { return spinner.getCount(); }
public Object getItemAtPosition(int position) { return spinner.getItemAtPosition(position); }
public long getItemIdAtPosition(int position) { return spinner.getItemIdAtPosition(position); }
public Object getSelectedItem() { return spinner.getSelectedItem(); }
public long getSelectedItemId() { return spinner.getSelectedItemId(); }
public int getSelectedItemPosition() { return spinner.getSelectedItemPosition(); }
public void setEnabled(boolean enabled) { spinner.setEnabled(enabled); }
public boolean isEnabled() { return spinner.isEnabled(); }
}
I have had LOTS of issues with the spinner firing of when I didn't want to, and all the answers here are unreliable. They work - but only sometimes. You will eventually run into scenarios where they will fail and introduce bugs into your code.
What worked for me was to store the last selected index in a variable and evaluate it in the listener. If it is the same as the new selected index do nothing and return, else continue with the listener. Do this:
//Declare a int member variable and initialize to 0 (at the top of your class)
private int mLastSpinnerPosition = 0;
//then evaluate it in your listener
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if(mLastSpinnerPosition == i){
return; //do nothing
}
mLastSpinnerPosition = i;
//do the rest of your code now
}
Trust me when I say this, this is by far the most reliable solution. A hack, but it works!
Just to flesh out hints at using the onTouchListener to distinguish between automatic calls to the setOnItemSelectedListener (which are part of Activity initialization, etc.) vs. calls to it triggered by actual user interaction, I did the following after trying some other suggestions here and found that it worked well with the fewest lines of code.
Just set an Boolean field for your Activity/Fragment like:
private Boolean spinnerTouched = false;
Then just before you set your spinner's setOnItemSelectedListener, set an onTouchListener:
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("Real touch felt.");
spinnerTouched = true;
return false;
}
});
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
...
if (spinnerTouched){
//Do the stuff you only want triggered by real user interaction.
}
spinnerTouched = false;
I was in similar situation, and I have a simple solution working for me.
It seems like methods setSelection(int position) and setSelected(int position, boolean animate) have different internal implementation.
When you use the second method setSelected(int position, boolean animate) with false animate flag, you get the selection without firing onItemSelected listener.
spinner.setSelection(Adapter.NO_SELECTION, false);
This will happen if you are making selection in code as;
mSpinner.setSelection(0);
Instead of above statement use
mSpinner.setSelection(0,false);//just simply do not animate it.
Edit: This method doesn't work for Mi Android Version Mi UI.
After pulling my hair out for a long time now I've created my own Spinner class. I've added a method to it which disconnects and connects the listener appropriately.
public class SaneSpinner extends Spinner {
public SaneSpinner(Context context) {
super(context);
}
public SaneSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SaneSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// set the ceaseFireOnItemClickEvent argument to true to avoid firing an event
public void setSelection(int position, boolean animate, boolean ceaseFireOnItemClickEvent) {
OnItemSelectedListener l = getOnItemSelectedListener();
if (ceaseFireOnItemClickEvent) {
setOnItemSelectedListener(null);
}
super.setSelection(position, animate);
if (ceaseFireOnItemClickEvent) {
setOnItemSelectedListener(l);
}
}
}
Use it in your XML like this:
<my.package.name.SaneSpinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/mySaneSpinner"
android:entries="#array/supportedCurrenciesFullName"
android:layout_weight="2" />
All you have to do is retrieve the instance of SaneSpinner after inflation and call set selection like this:
mMySaneSpinner.setSelection(1, true, true);
With this, no event is fired and user interaction is not interrupted. This reduced my code complexity a lot. This should be included in stock Android since it really is a PITA.
No unwanted events from the layout phase if you defer adding the listener till the layout is finished:
spinner.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Ensure you call it only once works for JELLY_BEAN and later
spinner.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// add the listener
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
// check if pos has changed
// then do your work
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
}
});
I got a very simple answer , 100% sure it works:
boolean Touched=false; // this a a global variable
public void changetouchvalue()
{
Touched=true;
}
// this code is written just before onItemSelectedListener
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("Real touch felt.");
changetouchvalue();
return false;
}
});
//inside your spinner.SetonItemSelectedListener , you have a function named OnItemSelected iside that function write the following code
if(Touched)
{
// the code u want to do in touch event
}
I've found much more elegant solution to this. It involves counting how many times the ArrayAdapter (in your case "adapter")has been invoked. Let's say you have 1 spinner and you call:
int iCountAdapterCalls = 0;
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.pm_list, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
Declare an int counter after the onCreate and then inside onItemSelected() method put an "if" condition to check how many times the atapter has been called. In your case you have it called just once so:
if(iCountAdapterCalls < 1)
{
iCountAdapterCalls++;
//This section executes in onCreate, during the initialization
}
else
{
//This section corresponds to user clicks, after the initialization
}
Since nothing worked for me, and I have more than 1 spinner in my view (and IMHO holding a bool map is an overkill) I use the tag to count the clicks :
spinner.setTag(0);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Integer selections = (Integer) parent.getTag();
if (selections > 0) {
// real selection
}
parent.setTag(++selections); // (or even just '1')
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
My small contribution is a variation on some of the above that has suited me a few times.
Declare an integer variable as a default value (or last used value saved in preferences).
Use spinner.setSelection(myDefault) to set that value before the listener is registered.
In the onItemSelected check whether the new spinner value equals the value you assigned before running any further code.
This has the added advantage of not running code if the user selects the same value again.
After having had the same problem, I came to this solutions using tags.
The idea behind it is simple: Whenever the spinner is changed programatically, make sure the tag reflects the selected position. In the listener then you check if the selected position equals the tag. If it does, the spinner selection was changed programatically.
Below is my new "spinner proxy" class:
package com.samplepackage;
import com.samplepackage.R;
import android.widget.Spinner;
public class SpinnerFixed {
private Spinner mSpinner;
public SpinnerFixed(View spinner) {
mSpinner = (Spinner)spinner;
mSpinner.setTag(R.id.spinner_pos, -2);
}
public boolean isUiTriggered() {
int tag = ((Integer)mSpinner.getTag(R.id.spinner_pos)).intValue();
int pos = mSpinner.getSelectedItemPosition();
mSpinner.setTag(R.id.spinner_pos, pos);
return (tag != -2 && tag != pos);
}
public void setSelection(int position) {
mSpinner.setTag(R.id.spinner_pos, position);
mSpinner.setSelection(position);
}
public void setSelection(int position, boolean animate) {
mSpinner.setTag(R.id.spinner_pos, position);
mSpinner.setSelection(position, animate);
}
// If you need to proxy more methods, use "Generate Delegate Methods"
// from the context menu in Eclipse.
}
You will also need an XML file with the tag setup in your Values directory.
I named my file spinner_tag.xml, but that's up to you.
It looks like this:
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item name="spinner_pos" type="id" />
</resources>
Now replace
Spinner myspinner;
...
myspinner = (Spinner)findViewById(R.id.myspinner);
in your code with
SpinnerFixed myspinner;
...
myspinner = new SpinnerFixed(findViewById(R.id.myspinner));
And make your handler somewhat look like this:
myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (myspinner.isUiTriggered()) {
// Code you want to execute only on UI selects of the spinner
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
The function isUiTriggered() will return true if and only if the spinner has been changed by the user. Note that this function has a side effect - it will set the tag, so a second call in the same listener call will always return false.
This wrapper will also handle the problem with the listener being called during layout creation.
Have fun,
Jens.
Lots of answers already, here's mine.
I extend AppCompatSpinner and add a method pgmSetSelection(int pos) that allows programmatic selection setting without triggering a selection callback. I've coded this with RxJava so that the selection events are delivered via an Observable.
package com.controlj.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
import io.reactivex.Observable;
/**
* Created by clyde on 22/11/17.
*/
public class FilteredSpinner extends android.support.v7.widget.AppCompatSpinner {
private int lastSelection = INVALID_POSITION;
public void pgmSetSelection(int i) {
lastSelection = i;
setSelection(i);
}
/**
* Observe item selections within this spinner. Events will not be delivered if they were triggered
* by a call to setSelection(). Selection of nothing will return an event equal to INVALID_POSITION
*
* #return an Observable delivering selection events
*/
public Observable<Integer> observeSelections() {
return Observable.create(emitter -> {
setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if(i != lastSelection) {
lastSelection = i;
emitter.onNext(i);
}
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
onItemSelected(adapterView, null, INVALID_POSITION, 0);
}
});
});
}
public FilteredSpinner(Context context) {
super(context);
}
public FilteredSpinner(Context context, int mode) {
super(context, mode);
}
public FilteredSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
super(context, attrs, defStyleAttr, mode);
}
}
An example of its usage, called in onCreateView() in a Fragment for example:
mySpinner = view.findViewById(R.id.history);
mySpinner.observeSelections()
.subscribe(this::setSelection);
where setSelection() is a method in the enclosing view that looks like this, and which is called both from user selection events via the Observable and also elsewhere programmatically, so the logic for handling selections is common to both selection methods.
private void setSelection(int position) {
if(adapter.isEmpty())
position = INVALID_POSITION;
else if(position >= adapter.getCount())
position = adapter.getCount() - 1;
MyData result = null;
mySpinner.pgmSetSelection(position);
if(position != INVALID_POSITION) {
result = adapter.getItem(position);
}
display(result); // show the selected item somewhere
}
I would try to call
spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
after you call setAdapter(). Also try out calling before the adapter.
You always have the solution to go with subclassing, where you can wrap a boolean flag to your overriden setAdapter method to skip the event.
The solution with a boolean flag or a counter didn't help me, 'cause during orientation change onItemSelected() calls "overflew" the flag or the counter.
I subclassed android.widget.Spinner and made tiny additions. The relevant parts are below. This solution worked for me.
private void setHandleOnItemSelected()
{
final StackTraceElement [] elements = Thread.currentThread().getStackTrace();
for (int index = 1; index < elements.length; index++)
{
handleOnItemSelected = elements[index].toString().indexOf("PerformClick") != -1; //$NON-NLS-1$
if (handleOnItemSelected)
{
break;
}
}
}
#Override
public void setSelection(int position, boolean animate)
{
super.setSelection(position, animate);
setHandleOnItemSelected();
}
#Override
public void setSelection(int position)
{
super.setSelection(position);
setHandleOnItemSelected();
}
public boolean shouldHandleOnItemSelected()
{
return handleOnItemSelected;
}
This is not an elegant solution either. In fact it's rather Rube-Goldberg but it seems to work. I make sure the spinner has been used at least once by extending the array adapter and overriding its getDropDownView. In the new getDropDownView method I have a boolean flag that is set to show the dropdown menu has been used at least once. I ignore calls to the listener until the flag is set.
MainActivity.onCreate():
ActionBar ab = getActionBar();
ab.setDisplayShowTitleEnabled(false);
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
ab.setListNavigationCallbacks(null, null);
ArrayList<String> abList = new ArrayList<String>();
abList.add("line 1");
...
ArAd abAdapt = new ArAd (this
, android.R.layout.simple_list_item_1
, android.R.id.text1, abList);
ab.setListNavigationCallbacks(abAdapt, MainActivity.this);
overriden array adapter:
private static boolean viewed = false;
private class ArAd extends ArrayAdapter<String> {
private ArAd(Activity a
, int layoutId, int resId, ArrayList<String> list) {
super(a, layoutId, resId, list);
viewed = false;
}
#Override
public View getDropDownView(int position, View convertView,
ViewGroup parent) {
viewed = true;
return super.getDropDownView(position, convertView, parent);
}
}
modified listener:
#Override
public boolean onNavigationItemSelected(
int itemPosition, long itemId) {
if (viewed) {
...
}
return false;
}
if you need to recreate activity on the fly eg: changing themes , a simple flag/counter wont work
use onUserInteraction() function to detect user activity,
reference : https://stackoverflow.com/a/25070696/4772917
I have done with simplest way:
private AdapterView.OnItemSelectedListener listener;
private Spinner spinner;
onCreate();
spinner = (Spinner) findViewById(R.id.spinner);
listener = new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
Log.i("H - Spinner selected position", position);
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
};
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
spinner.setOnItemSelectedListener(listener);
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
Done
if () {
spinner.setSelection(0);// No reaction to create spinner !!!
} else {
spinner.setSelection(intPosition);
}
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position > 0) {
// real selection
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
That's my final and easy to use solution :
public class ManualSelectedSpinner extends Spinner {
//get a reference for the internal listener
private OnItemSelectedListener mListener;
public ManualSelectedSpinner(Context context) {
super(context);
}
public ManualSelectedSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ManualSelectedSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void setOnItemSelectedListener(#Nullable OnItemSelectedListener listener) {
mListener = listener;
super.setOnItemSelectedListener(listener);
}
public void setSelectionWithoutInformListener(int position){
super.setOnItemSelectedListener(null);
super.setSelection(position);
super.setOnItemSelectedListener(mListener);
}
public void setSelectionWithoutInformListener(int position, boolean animate){
super.setOnItemSelectedListener(null);
super.setSelection(position, animate);
super.setOnItemSelectedListener(mListener);
}
}
Use the default setSelection(...) for default behaviour or use setSelectionWithoutInformListener(...) for selecting an item in the spinner without triggering OnItemSelectedListener callback.
I need to use mSpinner in ViewHolder, so the flag mOldPosition is set in the anonymous inner class.
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
int mOldPosition = mSpinner.getSelectedItemPosition();
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
if (mOldPosition != position) {
mOldPosition = position;
//Do something
}
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
//Do something
}
});
I would store the initial index during creation of the onClickListener object.
int thisInitialIndex = 0;//change as needed
myspinner.setSelection(thisInitialIndex);
myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
int initIndex = thisInitialIndex;
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (id != initIndex) { //if selectedIndex is the same as initial value
// your real onselecteditemchange event
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
My solution uses onTouchListener but doesn't restricts from its use. It creates a wrapper for onTouchListener if necessary where setup onItemSelectedListener.
public class Spinner extends android.widget.Spinner {
/* ...constructors... */
private OnTouchListener onTouchListener;
private OnItemSelectedListener onItemSelectedListener;
#Override
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
onItemSelectedListener = listener;
super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
}
#Override
public void setOnTouchListener(OnTouchListener listener) {
onTouchListener = listener;
super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
}
private OnTouchListener wrapTouchListener(final OnTouchListener onTouchListener, final OnItemSelectedListener onItemSelectedListener) {
return onItemSelectedListener != null ? new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Spinner.super.setOnItemSelectedListener(onItemSelectedListener);
return onTouchListener != null && onTouchListener.onTouch(view, motionEvent);
}
} : onTouchListener;
}
}
I might be answering too late over the post, however I managed to achieve this using Android Data binding library Android Databinding . I created a custom binding to make sure listener is not called until selected item is changed so even if user is selecting same position over and over again event is not fired.
Layout xml file
<layout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/activity_vertical_margin"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Spinner
android:id="#+id/spinner"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:spinnerMode="dropdown"
android:layout_below="#id/member_img"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:background="#drawable/member_btn"
android:padding="#dimen/activity_horizontal_margin"
android:layout_marginStart="#dimen/activity_horizontal_margin"
android:textColor="#color/colorAccent"
app:position="#{0}"
/>
</RelativeLayout>
</layout>
app:position is where you are passing position to be selected.
Custom binding
#BindingAdapter(value={ "position"}, requireAll=false)
public static void setSpinnerAdapter(Spinner spinner, int selected)
{
final int [] selectedposition= new int[1];
selectedposition[0]=selected;
// custom adapter or you can set default adapter
CustomSpinnerAdapter customSpinnerAdapter = new CustomSpinnerAdapter(spinner.getContext(), <arraylist you want to add to spinner>);
spinner.setAdapter(customSpinnerAdapter);
spinner.setSelection(selected,false);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String item = parent.getItemAtPosition(position).toString();
if( position!=selectedposition[0]) {
selectedposition[0]=position;
// do your stuff here
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
You can read more about custom data binding here Android Custom Setter
NOTE
Don't forget to enable databinding in your Gradle file
android {
....
dataBinding {
enabled = true
}
}
Include your layout files in <layout> tags
mYear.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View arg1, int item, long arg3) {
if (mYearSpinnerAdapter.isEnabled(item)) {
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});

Categories

Resources