private fun turnOnAllItems() {
items.forEachIndexed { index, item ->
val viewHolder = recyclerView.findViewHolderForAdapterPosition(index)
as SwitchableItemViewHolder
viewHolder.switchButton.isChecked = false
}
}
What this does, is it also changes list items object values isEnabled to false. Looks weird to me, as I actually change viewHolder attribute. Why is this happening? How to avoid this?
I strongly believe that you are doing it the wrong way. RecyclerView is meant to display already modified data, meaning that you have a set of it.
Let's say, 10 tables in restaurant, and at some point table #4 becomes available for new customer and you want to indicate that.
A good approach would be to modify your list of tables somewhere outside RCV, even fragment or activity will do, and then just graphically update (all or just one) item by means of RCV.
Here's a little article I made to illustrate how to properly use RecyclerView, hope it will help you
I have a list of questions with answer choices(let us assume 5 questions). Currently, all 5 questions are loaded at once in the RecyclerView, so a student can answer all the questions in the view. I would like to change this such that the first question loads and only after the student has answered and clicks the submit button, will the second load and so on. In other words, submit the answer to question 1, the view clears and shows question 2 and so on. An example of an app that I have seen achieve that is Duolingo. You only see the next question after you have submitted the current one displayed. How do I achieve this?
If you need to show only one question at a time, you don't need to use a RecyclerView at all. Just use text views and reuse them.
If you want to display old questions when a new question is unlocked, you may handle it in the getItemCount() method of your RecyclerView's adapter.
First sort the questions in order and use:
#Override
public int getItemCount() {
return currentQuestionIndex + 1;
}
After that, when the next button is clicked, increment the current question's index and call notifyDataSetChanged() in your RecyclerView.
If you want to display only one question and use RecyclerView anyway, return 1 from the getItemCount method. Then in the onBindViewHolder method instead of using the position parameter to get item from the list, use list.get(currentQuestionIndex);
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
Question question = questionList.get(currentQuestionIndex);
//other code
}
I have 10 images in an array which are answers to 10 questions which are also images in another array.
I have 10 image buttons coming up on my relative layout loaded with the 10 answer images. The answer array is answers[] and the questions array is questions[]. They correspond to each other, meaning that answers[1] is the answer to questions[1], and answers[4] is the answer to questions[4], and so on. I have a simple "for" statement which is for(int i=0; i<=9; i++).
I want the 10 questions to come up one at a time, and let the user answer. I need the program to pause and let the user click the answer to the question. With that "for" statement, the first question which is questions[0] will pop up in imageview. Using if statements, I want to say "if the imagebutton with images[0] is clicked then I want an image to display that will say correct and do some other stuff, else I want an image to say incorrect and do some other stuff" and then I want it to pause until the user just clicks anywhere on the screen.
Can anyone help me with the pausing in the "for" statement to let the user answer, and the "if" statement where if the correct answer is picked I can display an image that says correct etc, and then pause again until the user just clicks anywhere on the screen?? Thanks!
By the way, I need those question images to pop up in the same imageview each time obviously, just changing the image.
You can't really control the program flow the way you want to with a for loop. The user interface in Android is event-driven, so the only way to move from question to question is by reacting to the event that is generated when the user clicks a button. You do that by finding the button and attaching an event handler for the click event, called onClick, which is a part of the View.OnClickListener interface. You can define a variable on your activity class to store which question you're on and use that to step your way through as the user answers questions. Your activity might look something like this:
public class QuestionActivity extends Activity implements View.OnClickListener {
public int currentQuestion = 0;
Image[] answers;
Image[] questions;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Load questions and answers
setContentView(R.layout.mylayout);
final ImageButton button = (ImageButton)findViewById(R.id.mybutton);
button.setOnClickListener(this);
}
public void onClick(View view)
{
if (/* the answer is correct */) {
// happiness: show images, do what needs doing
} else {
// sadness: show other images, etc
}
currentQuestion++;
}
}
It's taken me quite a while to get my head around the Android Spinner. After several failed implementation attempts, and after reading many questions partially similar to my own but without satisfactory answers, and some without any answers at all, e.g. here and here, I finally get that a "spinner" in Android isn't meant to be the same thing as a "drop-down list" from desktop apps, or a select in HTML. However, what my app (and I'm guessing the apps of all the other posters whose questions are similar) needs is something that works like a drop-down box, not like a spinner.
My two problems are with what I first considered to be idiosynchrasies the OnItemSelectedListener (I've seen these as separate questions on this site but not as one):
An initial selection of the first list item is triggered automatically without the user's interaction.
When the item that was already selected is selected again by the user, it is ignored.
Now I realise that, when you think about it, it makes sense for this to happen on a spinner - it has to start with a default value selected, and you spin it only to change that value, not to "re-select" a value - the documentation actually says: "This callback is invoked only when the newly selected position is different from the previously selected position". And I've seen answers suggesting that you set up a flag to ignore the first automatic selection - I guess I could live with that if there's no other way.
But since what I really want is a drop-down list which behaves as a drop-down list should (and as users can and should expect), what I need is something like a Spinner that behaves like a drop-down, like a combo-box. I don't care about any automatic pre-selection (that should happen without triggering my listener), and I want to know about every selection, even if it's the same one as previously (after all, the user selected the same item again).
So... is there something in Android that can do that, or some workaround to make a Spinner behave like a drop-down list? If there is a question like this one on this site that I haven't found, and which has a satisfactory answer, please let me know (in which case I sincerely apologise for repeating the question).
+1 to David's answer. However, here's an implementation suggestion that does not involve copy-pasting code from the source (which, by the way, looks exactly the same as David posted in 2.3 as well):
#Override
void setSelectionInt(int position, boolean animate) {
mOldSelectedPosition = INVALID_POSITION;
super.setSelectionInt(position, animate);
}
This way you'll trick the parent method into thinking it's a new position every time.
Alternatively, you could try setting the position to invalid when the spinner is clicked and setting it back in onNothingSelected. This is not as nice, because the user will not see what item is selected while the dialog is up.
Ok, I think I've come up with a solution for my own situation with the help of both David's and Felix' answer (I believe David's helped Felix', which in turn helped mine). I thought I'd post it here together with a code sample in case someone else finds this approach useful as well. It also solves both of my problems (both the unwanted automatic selection and the desired re-selection trigger).
What I've done is added a "please select" dummy item as the first item in my list (initially just to get around the automatic selection problem so that I could ignore when it was selected without user interaction), and then, when another item is selected and I've handled the selection, I simply reset the spinner to the dummy item (which gets ignored). Come to think of it, I should've thought of this long ago before deciding to post my question on this site, but things are always more obvious in hindsight... and I found that writing my question actually helped me to think about what I wanted to achieve.
Obviously, if having a dummy item doesn't fit your situation, this might not be the ideal solution for you, but since what I wanted was to trigger an action when the user selected a value (and having the value remain selected is not required in my specific case), this works just fine. I'll try to add a simplified code example (may not compile as is, I've ripped out a few bits from my working code and renamed things before pasting, but hopefully you'll get the idea) below.
First, the list activity (in my case) containing the spinner, let's call it MyListActivity:
public class MyListActivity extends ListActivity {
private Spinner mySpinner;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: other code as required...
mySpinner = (Spinner) findViewById(R.id.mySpinner);
mySpinner.setAdapter(new MySpinnerAdapter(this));
mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> aParentView,
View aView, int aPosition, long anId) {
if (aPosition == 0) {
Log.d(getClass().getName(), "Ignoring selection of dummy list item...");
} else {
Log.d(getClass().getName(), "Handling selection of actual list item...");
// TODO: insert code to handle selection
resetSelection();
}
}
#Override
public void onNothingSelected(AdapterView<?> anAdapterView) {
// do nothing
}
});
}
/**
* Reset the filter spinner selection to 0 - which is ignored in
* onItemSelected() - so that a subsequent selection of another item is
* triggered, regardless of whether it's the same item that was selected
* previously.
*/
protected void resetSelection() {
Log.d(getClass().getName(), "Resetting selection to 0 (i.e. 'please select' item).");
mySpinner.setSelection(0);
}
}
And the spinner adapter code could look something like this (could in fact be an inner class in the above list activity if you prefer):
public class MySpinnerAdapter extends BaseAdapter implements SpinnerAdapter {
private List<MyListItem> items; // replace MyListItem with your model object type
private Context context;
public MySpinnerAdapter(Context aContext) {
context = aContext;
items = new ArrayList<MyListItem>();
items.add(null); // add first dummy item - selection of this will be ignored
// TODO: add other items;
}
#Override
public int getCount() {
return items.size();
}
#Override
public Object getItem(int aPosition) {
return items.get(aPosition);
}
#Override
public long getItemId(int aPosition) {
return aPosition;
}
#Override
public View getView(int aPosition, View aView, ViewGroup aParent) {
TextView text = new TextView(context);
if (aPosition == 0) {
text.setText("-- Please select --"); // text for first dummy item
} else {
text.setText(items.get(aPosition).toString());
// or use whatever model attribute you'd like displayed instead of toString()
}
return text;
}
}
I guess (haven't tried this) the same effect could be achieved using setSelected(false) instead of setSelection(0), but re-setting to "please select" suits my purposes fine. And, "look, Ma, no flag!" (Although I guess ignoring 0 selections is not that dissimilar.)
Hopefully, this can help someone else out there with a similar use case. :-) For other use cases, Felix' answer may be more suitable (thanks Felix!).
Look. I don't know if this will help you, but since you seem tired of looking for an answer without much success, this idea may help you, who knows...
The Spinner class is derived from AbsSpinner. Inside this, there is this method:
void setSelectionInt(int position, boolean animate) {
if (position != mOldSelectedPosition) {
mBlockLayoutRequests = true;
int delta = position - mSelectedPosition;
setNextSelectedPositionInt(position);
layout(delta, animate);
mBlockLayoutRequests = false;
}
}
This is AFAIK taken from 1.5 source. Perhaps you could check that source, see how Spinner/AbsSpinner works, and maybe extend that class just enough to catch the proper method and not check if position != mOldSelectedPosition.
I mean... that's a huge "maybe" with a lot of "ifs" (android versioning comes to mind etc.), but since you seem frustrated (and I've been there with Android many times), maybe this can give you some "light". And I assume that there are no other obvious answers by looking at your previous research.
I wish you good luck!
Here is an alternative solution to differentiate between any (intended or unintended) programmatic and user-initiated changes:
Create your listener for the spinner as both an OnTouchListener and OnItemSelectedListener
public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {
boolean userSelect = false;
#Override
public boolean onTouch(View v, MotionEvent event) {
userSelect = true;
return false;
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (userSelect) {
// Your selection handling code here
userSelect = false;
}
}
}
Add the listener to the spinner registering for both event types
SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
This wouldn't handle the case in which the re-selection of the same item by the user doesn't trigger the onItemSelected method (which I have not observed), but I guess that could be handled by adding some code to the onTouch method.
Anyway, the problems Amos pointed out were driving me crazy before thinking of this solution, so I thought I'd share as widely as possible. There are many threads that discuss this, but I've only seen one other solution so far that is similar to this: https://stackoverflow.com/a/25070696/4556980.
Modifying the Spinner is useful if you want to have multiple selections simultaneously in the same activity.
If you only desire the user to have a hierarchical selection, for example:
What do you want to eat?
Fruit
Apples
Bananas
Oranges
Fast Food
Burgers
Fries
Hot dogs,
then the ExpandableListView might be better for you. It allows the user to navigate a hierarchy of different groups and choose a child element. This would be similar to having several Spinners for the user to choose from - if you do not desire a simultaneous selection, that is.
I worked through several of the issues mentioned in this thread before I realized that the PopupMenu widget is what I really wanted. That was easy to implement without the hacks and workarounds needed to change the functionality of a Spinner. PopupMenu was relatively new when this thread was started in 2011, but I hope this helps someone searching for similar functionality now.
I want to create a page with X questions (the questions are stored in a database and I read them), headline and buttons at the end.
When the user clicks on one question than the question should transform into a dialog where the user can modify the question, however the other questions above and beneath it should still display the same way.
The way ListActivity is used in the sample notepad application in the android documentation it seems like the class can only display multiple items of the same type.
Is there a straightforward way to go about this problem?
I should tell you that I don't like your solution as an user.
I would prefer to chose from a list and having an edit activity after a click.
That's the default approach I've seen in every android app and it will be also easier for you.
If you still want to do what you explained I would try do this:
Create a ListView
Create a class QuestionOrDialog
Create an Adapter that extends from ArrayAdapter
Override getView doing something like:
QuestionOrDialog aQuestionOrDialog = getItem(position);
if ( aQuestionOrDialog.showDialog() ) {
return mInflater.inflate(R.layout.dialog, parent, false);
} else {
view = mInflater.inflate(R.layout.question, parent, false);
TextView question = (TextView) view.findViewById(R.id.question);
question.setText(aQuestionOrDialog.getQuestion());
}
On the OnClick you will have to do a getItem() and set that it was clicked.
Tell the listView that it's item have changed.
Hope it works.