I have a very peculiar problem. I understand that while in the foreground the member variables of an Activity remain untouched (don't need saving to persist). Now, I have a PagerAdapter that represents a quiz with a question and three answers. I've made various objects to represent the question and answers.
A Question always has a list of exactly 3 Answer's. This is how I do it: (note: some code was left out for readability)
public class QuizPagerAdapter
{
List<Question> questionList;
Context context;
public QuizPagerAdapter (List<Question> list, Context ctx) {
questionList = list;
context = ctx;
}
#Override
public Object instantiateItem (View pager, int position) {
// Create various views (ScrollView, LinearLayout, etc)
final Question q = questionList.get(position);
RadioGroup group = new RadioGroup(context);
for (int i = 0; i < 3; i++) {
Answer a = q.getAnswer(i);
RadioButton rb = new RadioButton(context);
rb.setId(i);
rb.setText(a.getText());
rb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
RadioButton btn = (RadioButton) v;
q.unselectAll(); // unselects all Answer's
q.getAnswer(btn.getId()).setSelected(true);
}
});
if (a.isSelected()) rb.setChecked(true);
}
return view;
}
}
When I use this in my Activity, I get a nice list of Questions with three Answers. Now, when I select one Answer, the RadioButton is checked. OK, now, I scroll to the next Question and check that one, then go back to the previous Question and there the answer I checked in the first place is now not checked.
Now, this doesn't always happen, it happens sometimes, but that sometimes is enough that it can cause a problem. The Activity is never placed in the background.
I understand that Android will destroy Views in the PagerAdapter to conserve memory, thus destroying the RadioButton, this is why I have this snippet:
if (a.isSelected()) rb.setChecked(true);
But it is still happening. I've tried logging everything, debugging but I cant seem to get to the source of the problem. Maybe someone here has come across a similar problem.
Any help is appreciated,
Thank you in advance!
In the OnClick event, make sure that your reference to Question is the correct object. You might want to reference the question by the list index, because if you have two Questions on the screen at the same time, the reference of q could be the second question, but you have clicked the first questions answer.
** Added **
The issue is that q is a reference to a question, and each time through the instantiateItem function, this reference is set to a new instance of a question from the list. So when you execute the OnClick event, the value q might be pointing to the incorrect instance of question than what you expect. You would be better off getting the question out of the original list by the views index in the activity.
Something like the following:
public void onClick(View v) {
RadioButton btn = (RadioButton) v;
Question question = questionList.get(position);
question.unselectAll(); // unselects all Answer's
question.getAnswer(btn.getId()).setSelected(true);
}
Related
sorry if bad english
I'm kinda new in android development.
I'm working on an app and i need to fill a custom ListView. each of the ListView items got a Delete Button, which removes them from the list, the problem is that i need to keep that removed item.
A little background:
i am working on a "Monster Generator" app that just gives you a monster (name, desc, etc) from a "bag" (consider just a List) and places it on another list inside a java object i made, the object can keep track of the monster generated and the monsters left in the bag. i already make a functional prototype, using only TextViews but i tried to make it as ListView, and there is the problem.
in my Custom Adapter in the getView method i have
Button btnDelete = (Button) view.findViewById(R.id.btn_delete);
btnDelete.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
items.remove(position); // this item i need to keep somehow in the activity
notifyDataSetChanged();
// I need to keep the item removed, ready to use in the activity
//and i don't know how
}
});
ArrayList<Monster> listToKeep = new ArrayList<>();
Button btnDelete = (Button) view.findViewById(R.id.btn_delete);
btnDelete.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
listToKeep.add(items.get(position));
items.remove(position);
notifyDataSetChanged();
}
});
well, i kinda resolve it:
First of all, i think the situation of my problem may be to specific, anyway i'll post my solution.
Second, thanks to you for posting an answer.
apparently i have a really bad understanding of objects, at least in java (i am ashamed).
i just needed to change my CustomAdapterClass contructor to accept my "game logic"
public CustomAdapter(GameActivity activity, List<Monster> items, GameLogic gamelogic){...}
my "GameLogic" (this just for clarify my situation) has two ArrayList, one of MonstersInBag list(monsters that are not in game), and another of MonstersInGame list.
The MonstersInGame list, is the list i am using to fill the listview.
So i add the removed item of the listview and add it to the GameLogic - MonstersInBag, and updated a textView in my activity with a public method.
buttonDelete.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
Game.getMonsterGameBag().add(items.remove(position));
notifyDataSetChanged();
activity.UpdateMonsterNumber();
}
});
I hope it help anyone with a specific situation like this.
I have custom CursorAdapter where I have set ut a deletebutton in the overrided bindView function. Below is the code for that and the deletePhrase(id, context) is just a function that uses the provides delete function. I tried lots of different stuff make this button seperatly from the onClick for the listitem, cause when I added the button orinally it made the onclick for the item itself not function so I made the textView clickable instead, my point being that I might have got lost somewhere along the way and there's a much better way to do this.
Button delButton = (Button) view.findViewById(R.id.list_item_removeButton);
int id = cursor.getInt(cursor.getColumnIndex("_id"));
delButton.setTag(id);
delButton.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
int rowId = (Integer) v.getTag();
long ret = deletePhrase(rowId, v.getContext());
if (ret > 0) {
notifyDataSetChanged();
}
}
});
But now here's my issue I want to reset the Loader which I have implemented in the fragment that displays the list. Now in that fragment I have a button to add a phrase which at the end calls this:
getLoaderManager().restartLoader(0, null, this);
So when I add a phrase it will update the UI accordingly but I can't seem to get the same functionality for when I delete since I do this from the CursorAdapter class from which I can't seem to get access to the loader. Im flexible to any solutions that might work. Maybe there's someway I can move my deletePhrase function to the fragment and call it from there but then I'd have to get the correct instance of the fragment into the CursorAdapter class? Sorry I don't really know im kinda new to android and programming in general.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 2 years ago.
Improve this question
in my current project i have dealt with spinner class which has been customized from almost every aspect possible. Thus i have gained some detailed aspects as i deal with it . So i will start with the part it shows why it is flawed.
1_There is no default listener that will be fired on pop up window/layout/dialog created-showed(layout inflated) event. There are some workarounds such as ontouch listener on spinner, then check if on touch finish happened in spinner area, then you know popup will be shown but still not reliable since you can fill popup with async task..
2_On item selected event does not fire when same index is selected again. This is really annoying since i may be updating the adapter depending on other conditions which will change current selection and list order etc... Of course there is a workaround way by creating own spinner class and adding it in xml like com.myproject.customspinner etc.....(Spinner : onItemSelected not called when selected item remains the same)
3_There is no working functional OnClickListener and OnItemLongTouchListener event for spinner.
4_Changing Spinner DropDown list divider element's attributes such as color requires more labor than changing all dropdrown and spinner' background views itself which is very absurd.
5_Spinner the name itself is very absurd =))).
So what can i use instead of Spinner? Which is best way to go?
You can create a custom spinner using ListPopupWindow to a TextView means when a TextView is clicked a ListPopupWindow open like spinner dropdown list and you can choose a element. If you need I will help you in that.
ListPopupWindow numberList;
TextView spDays;
ArrayList<Map<String, String>>() listTrans;
in oncreate() spDays.setonclicklistner(this);spDays.setText("Select");
setNumberListSpinnerView();
in onclick(){
when spDays clicked :- numberList.show();
}
void setNumberListSpinnerView() {
numberList= new ListPopupWindow(this);
numberList.setAnchorView(spDays);
numberList.setOnItemClickListener((new AdapterView.OnItemClickListener() {
#Override
getListItem();
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Map map = listTrans.get(position);
spDays.setText(map.get("circle_name").toString());
circle_name = map.get("circle_name") + "";
circle_id = map.get("circle_id").toString();
circleList.dismiss();
Log.d("Circle id:", circle_id + "");
getRetails();
}
}));
}
void getListItem(){
String[] numbers = {"1","2","3","4","5","6"};
listTrans = new ArrayList<Map<String, String>>();
LinkedHashMap<String, String> tran = new LinkedHashMap<String, String>();
for (String number : numbers) {
tran.put("numbers", number);
listTrans.add(tran);
}
SimpleAdapter adapter = new SimpleAdapter(AddRetailSurvey.this, listTrans,
android.R.layout.simple_spinner_dropdown_item,
new String[]{"numbers"},
new int[]{android.R.id.text1});
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
numberList.setAdapter(adapter);
}
Check this code and modify it according to your requirement. If you found any problem I am here to help you. :)
Putting a simplified kotlin version of the accepted answer here, which may help. At first make a ListPopupWindow member in your Activity or other class-
private val listPopupView by lazy { ListPopupWindow(this) }
Then initialize it in the onCreate() method-
val dataList = arrayOf("item1", "item2", "item3", "item4")
listPopupView.setAdapter(ArrayAdapter(this, android.R.layout.simple_list_item_1, dataList))
listPopupView.setOnItemClickListener { _, _, position, _ ->
selectionTextView.text = dataList[position]
listPopupView.dismiss()
// do other things on selection
}
listPopupView.anchorView = selectionTextView
selectionTextView.setOnClickListener { listPopupView.show() }
And you are done!
This shows you how to replace Spinner with your own implementation. It's pretty simple, the important thing is to use a PopupWindow containing a list view to imitate Spinner's layout behavior.
https://www.androidcode.ninja/show-listview-as-drop-down-android/
This fixes the issues with weird event handlers in Spinner's implementation. It's also much easier to customize.
The only problem with this approach is that like Spinner, it still uses PopupWindow, which causes weird bugs in the system UI when you're in immersive/fullscreen mode. But it's easier to handle those bugs when you don't also have to deal with Spinner's specific issues.
In my QuestionsActivity, I am showing a question and bunch of answers. The question is displayed on TextView and answers are displayed on ListView that is composed of TextViews. There is an ActionButton named "Check Answer" and when it is clicked, it shows the correct answer by changing the background color of the TextView in the ListView.
The background changing code looks like this:
if (allAnswers.get(i).isCorrect())
{
mAnswerList.getChildAt(i).setBackgroundColor
(getResources().getColor(R.color.correct_answer_background));
return;
}
and now there are two Buttons at the footer section of this QuestionsActivity called PreviousQuestionButton and NextQuestionButton and they are basically navigation buttons between questions.
The problem is, when I go to the next question after clicking on "Check Answer" button, the answer background color doesn't go away and remains in the next question answer options. I tried invalidate(), refreshDrawableState() method of ListView but no luck!
This is the method which displays the answers for a given question:
private void showAnswers(int questionLocation)
{
int questionId = mAllQuestions.get(questionLocation).getQuestionId();
List<Answer> answers = mAnswerRepository.getAllByQuestionId(questionId);
mAnswerAdapter.clear();
for (int i = 0; i < answers.size(); i++)
{
mAnswerAdapter.add(mOptionLetters[i] + ". "
+ answers.get(i).getAnswerText());
}
mAnswerAdapter.notifyDataSetChanged();
}
My Question
What I want is that when I click on next or previous buttons, the background color of the correct answer in ListView should disappear so that next and previous question button can show non-selected answer options list to the user. Is there any method which resets ListView to a state which does not have any background applied?
For selected answer option, I am using mAnswerList.clearChoices() in order to unselect but it does not apply for correct answer background color.
Well, to reset the color you can very well hard-reset the adapter by creating a new one. So don't clear and add as that may keep the views in the state they were before. I am not too sure about this since I am not clearing or adding from an adapter, but always creating a new one to fulfill my new needs.
Anyway, another reason why things may not go in the direction you want is that the views may get recycled, since we're talking about a ListView. So if you want to highlight a list item, you should keep in the data model the information about highlight by initializing it to false and if the user selects one set the highlight state to true. I suppose the Answer class has as a minimum the following:
public class Answer {
private String data;
private boolean correct;
public String getData() {
return data;
}
public boolean isCorrect() {
return correct;
}
#Override
public String toString() {
return data;
}
}
So your adapter could look close to this - getView method is the most important to notice (don't forget to set to default background if the answer is incorrect or the adapter should not highlight correct answer):
public class MyAdapter extends ArrayAdapter<Answer> {
private boolean showCorrectAnswer;
private List<Answer> modelAnswers;
public MyAdapter(Context context, List<Answer> answers) {
super(context, android.R.layout.simple_list_item_1, answers);
this.modelAnswers = answers;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if(showCorrectAnswer && modelAnswers.get(position).isCorrect()) {
view.setBackgroundColor(getContext().getResources().getColor(R.color.correct_answer_background));
} else {
view.setBackgroundColor(getContext().getResources().getColor(R.color.default_background));
}
}
public void setShowCorrectAnswer(boolean showCorrectAnswer) {
this.showCorrectAnswer = showCorrectAnswer;
notifyDataSetChanged();
}
}
What you need to do is to keep a reference to this custom adapter and if you need to highlight the correct answer or not simply call setShowCorrectAnswer(true / false);. It will trigger a redraw and in the getView() it will decide what to do based on adapter state and correct answer.
Hope it make sense ... I wrote all this while drinking a beer :)
Basically, I agree with the answer from payeli - you should change the background of selected answer in the "next/previous question" button.
But then there is this question:
"Is there any method which resets ListView to a state which does not have any background applied?"
Answer to this (as far as I know) is: not directly. But there are two workarounds.
If you call notfiyDataSetChange, not all views are completely redrawn. If possible, just the appropriate values are changed. It's mainly for performance reasons. (Imagine having 1000 contacts with images and names dumped and redrawn)
So how can you deal with this? (Other then reseting the background in the onClick method) Since you said these items are answers for a question, I assume you are not concerned about performance because there won't be too many of them.
Then you can simply:
A) Create new instance of Adapter instead of changing data in the old one. When you switch adapters, all views in list are removed, so, no recycling can happen in the new adapter.
B) Create custom Adapter, override getView method and make sure every time view is requested, you return a new view, so no recycling can happen.
Again: this really isn't "performance friendly" and should not be used with big lists with a lot of items!
If you want to know more, feel free to ask in comments or read the reference of Adapter class, especially the parameter convertView of getView method. (http://developer.android.com/reference/android/widget/Adapter.html)
As per my understanding U need to change color of text view in one of the list view row i.e row containing correct answer.
If this is your problem then On Click of button simply clear list view and recreate list view. Not when list view is recreated then check out position of row which contains correct answer. After getting row position simply change color of text view.
For Ex: In your Adapter class check as fallows in your getView();
for ( int i = 0; i < position.length ; i++ )
{
if(position == your_required_position){
{
textview.setColor(Color.BLUE);
}else{
textview.setColor(Color.BLACK);
}
}
In the onClickListener of "Check Answer" button, you need to reset the color:
void onClick (View v){
..............
mAnswerList.getChildAt(currentQuestion).setBackgroundColor
(getResources().getColor(R.color.default_answer_background));
}
you can save the default background somewhere, and when you press next/previous question button you should apply that color. Example:
TypedArray array = getTheme().obtainStyledAttributes(new int[] {
android.R.attr.colorBackground,
android.R.attr.textColorPrimary,
});
int backgroundColor = array.getColor(0, 0xFF00FF);
int textColor = array.getColor(1, 0xFF00FF);
array.recycle();
I found this code online to get background and text color (you can just keep the background part), when the onClick activates just set the background of your view to "backgroundColor"
I have two fragments and first fragment I have two pages and each page has at least two radio buttons with a question and those radio buttons are to choose your answer.
At the moment I have to use the if else function and using the setText to set the question and the answers for the radio button and when the right answer is clicked nothing will show until you go to the last page and click on show result.
Right now I have to keep on using if else and using setOnClickListener for all of them.
Also, by using the setOnClickListener it'll send a +1 result to my result page's variable.
Is there an easier way to do this?
This is what my code looks like
if(fragmentNumber == 0)
{
ask_question.setText("Question1?");
rb1.setText("Answer1");
rb2.setText("Answer2");
rb1.setOnClickListener(new android.view.View.OnClickListener() {
#Override
public void onClick(View v) {
ResultsFragment.Q1 = 1;
}
});
}
else if(fragmentNumber == 1)
{
ask_question.setText("Question2?");
rb1.setText("Answer2-1");
rb2.setText("Answer2-2");
rb2.setOnClickListener(new android.view.View.OnClickListener() {
#Override
public void onClick(View v) {
ResultsFragment.Q2 = 1;
}
});
}
Other than looking for an easier way to do this, I ran into another problem too.
Let me use examples.
E.g. if Q1 I got it right and Q2 I got it wrong when I go to show result I will get 1/2 which is good but if I go back to Q1 and click on the wrong answer on purpose then go back to show result, it'll still show 1/2 instead of 0/2 which means if the 1 is set into the variable then it'll stay there. I know if I use the setOnclickListener then I can set the other radioButtons == 0 but that means inside each if statement there will be LOTS setonClickListner this is kind of another reason I'm seeking another easier result.
Thanks in advance.
Looking for an easier way? Ok. In my opinion you should have a fragment(custom QAFragment) with only one page for a question and the answers.
public class QuestionFragment extends Fragment {
public static QuestionFragment newInstance(String questionText, String[] answerArray) {
QuestionFragment f = new QuestionFragment();
Bundle args = new Bundle();
args.putString("questionText", questionText);
args.putStringArray("answerArray", answerArray);
f.setArguments(args);
return f;
}
public String getQuestionText() {
return getArguments().getString("questionText");
}
public String[] getAnswerArray() {
return getArguments().getStringArray("answerArray");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View content = null;
// TODO: inflate your content here, put your radios and question text
final String questionText = getQuestionText();
String[] answerArray = getAnswerArray();
// TODO: set your question text with questionText value
// TODO: set your asnwers with answerArray values
RadioGroup radioGroup = null;
// TODO: keep your radios with this radioGroup
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup radioGroup, int radioButtonID) {
String selectedAnswer = null;
// TODO: if/else or switch statement on radioButtonId to get the selected answer
// Post the result to main activity to do extra operations or open new page.
((MainActivity)getActivity()).postQAResult(questionText, selectedAnswer);
}
});
return content;
}
}
Add postQAResult method to your main activity to listen callbacks from your fragment.
public void postQAResult(String question, String asnwer) {
// TODO: handle answer for the question.
// For example if you need to store answer you can use SharedPreferences to save.
// Or you can ask different questions for given answer.
}
Now you can add this qa fragment from your activity when you need a new question/answer page. for example;
String questionText = "Any problem?";
String[] answerArray = new String[]{"Yes","No"};
QuestionFragment questionFragment = QuestionFragment.newInstance(questionText, answerArray);
getFragmentManager().beginTransaction().add(android.R.id.content, questionFragment, "UNIQUE_TAG_FOR_YOUR_PAGE").commit();
You may need to show the previous question to the user. Then do not remove your qa fragments and put them to backstack while adding them.