Reset all Views in fragment - android

So I am currently in FragmentA.java, a class which consists of different EditTexts and Checkboxes for users to press and input.
My question is, I have this reset button, how can I reset the entire fragment view? (e.g. an EditText will be set to empty string, or with the value 0, as it is when created).
P.S. of course I can set the editText/Checkboxes one by one programically, however there are quite a lot of them and other views, so I would like to know if there is a good way to reset all of them.

Let's break it down into steps:
1. Getting the references
How this is done depends on what you already have. If the fields are created in-code, it's easy: Just store the references in a List<CommonBaseType>.
If they are loaded from an XML Layout, there are multiple options. If you just want all views of certain type(s) to be reset, you can iterate through the view hierarchy by getting a reference to their holding ViewGroup (the layout) and iterate over the children with getChildCount() and getChildAt(int). Then, check the type of the child. If it's a ViewGroup, check it's children. If it's an EditText or CheckBox, add them to the list.
If you need more control and don't want all views to be reset, you can tag the ones you want with something. You can tag a view in XML by using the android:tag-attribute and find them after inflation using the View.findViewWithTag(Object)-method.
2. Resetting
Now that you have the references, you can reset them by simply iterating over the collection you made in step 1 and handle them depending on their type. Some pseudo code with that:
List<View> form_elements = findViewsToReset();
for (View element : form_elements){
if (element instanceof EditText){
((EditText) element).setText("");
} else if (element instanceof CheckBox){
((CheckBox) element).setChecked(false);
}
// and so forth...
}
Something like this will reset all fields in your form to a default-value, depending on their type.
3. Resetting back to their original values
If you want to reset the views to their original values, you should "index" those when the initial values are set (which might be directly after inflation, if you set the values via XML).
To do this, simply run through your list from step 1 and make a mapping from their ID to their value at that point:
List<View> form_elements = findViewsToReset();
Map<Integer, Object> default_values = new HashMap<>(form_elements.size());
for (View element : form_elements){
if (element.getId() == View.NO_ID){
// We have nothing to identify this view by...
continue;
}
// Store the default values away:
if (element instanceof EditText){
default_values.put(
element.getId(),
((EditText) element).getText()
);
} else if (element instanceof CheckBox){
default_values.put(
element.getId(),
((CheckBox) element).isChecked()
);
}
// and so forth...
}
Later when you want to reset the form-elements, you can just iterate the list again and get the default values from the map. Cast them depending on the type of field (EditText -> String, CheckBox -> Boolean, etz) and set the values.
Bonus: Nasty RadioGroup
Resetting a RadioGroup is simply archived by calling clearCheck() on it, which has the nasty side-effect of triggering the associated OnCheckedChangeListener (which you might not want, depending on what you're doing in the listener).
The simplest way around this is to un-register the listener before calling clearCheck() and re-registering it afterwards. This can be archived by overriding RadioGroup.clearCheck():
/**
* When {#link #clearCheck()} is called, the registered (if any) {#link android.widget.RadioGroup.OnCheckedChangeListener} will <b>not</b> be called.
* #author Lukas Knuth
* #version 1.0
*/
public class CustomRadioGroup extends RadioGroup {
private OnCheckedChangeListener checked_change_listener;
public CustomRadioGroup(Context context) {
super(context);
}
public CustomRadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
// We need to store this ourselves, since there is no getter-method for the listener -.-
this.checked_change_listener = listener;
super.setOnCheckedChangeListener(listener);
}
#Override
public void clearCheck() {
// 1. unregister the listener:
super.setOnCheckedChangeListener(null);
// 2. Clear
super.clearCheck();
// 3. restore the listener like it was before:
super.setOnCheckedChangeListener(this.checked_change_listener);
}
}

Related

android - RecyclerView items: how to save properties of Views in every row?

My problem is to understand how RecyclerView works.. I have RecyclerView with a little bit complicated item in every row, but the main thing is that Item has a child ImageView and LinearLayout. I want to press ImageView and set Visibility of LinearLayout to GONE or VISIBLE and rotate my ImageView. I tried to do this in my onBindViewHolder:
holder.mIVExpandBtn.setOnClickListener(new OnClickListener() {
boolean isOpen = false;
#Override
public void onClick(View v) {
if (isOpen) {
CounterListAdapter.this.notifyItemChanged(position);
holder.mLLDetails.setVisibility(GONE);
holder.mDivider.setVisibility(VISIBLE);
holder.setArrowUp(false);
isOpen = false;
counterItem.setDetailsOpened(false);
} else {
holder.mLLDetails.setVisibility(VISIBLE);
holder.mDivider.setVisibility(GONE);
holder.setArrowUp(true);
isOpen = true;
counterItem.setDetailsOpened(true);
}
}
});
And I have some problems here.
I have a boolean variable inside OnClickListener, I know its wrong, so it changes only one time when I expand my LinearLayout. If I make this boolean global variable, if I expand one row of RecyclerView isOpen = true for any other item and it doesn't expand itself when I click on ImageView.. Where should I place this boolean?
And the second question - how can I save the state of my RecyclerView rows on screen rotation? For example I expanded one of my rows, LinearLayout.setVisibility(VISIBLE), change screen orientation and its closed.
For your first problem, you should put your boolean variable where you also define your views, i.e., inside your ViewHolder, ir order that onClick you call the boolean this way
if(holder.isOpen)
In this way you keep the reference of each boolean to each row.
For your second problem, the solution is pretty simple. In your manifest, in the activity where you have your RecyclerView, define the following:
android:configChanges="keyboardHidden|orientation|screenSize"
This prevents your activity from being recreated on configuration change in case you rotate the screen, so the activity will keep it's state and your RecyclerView will therefor not be recreated along with your adapter.
Notice that this means that, if your activity is not recreated, onPause, onStop, etc, will not run. This is only for screen rotation, your activity will still run the method onConfigurationChanged() which is where you should define any changes you need in case the screen rotates.
You better put the OnClickListener in the Holder class, something like this:
private class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
#Override
public void onClick(View view) {
....
}
About how to save state I think all things that define the rows should be saved in the array you pass to the adapter contructor, you can add fields in the array item object that save VISIBILITY state of the row views.
On screen rotation then two options:
1 - as #Ricardo said avoiding Activity recreation
2 - onSaveInstanceState / onRestoreInstanceStates save/restore the array that define the rows .. my prefered method for that is to use JSON and put it in a String that can be saved/restored in the Bundle.

Keep a view state/aspect after rotation

In my application I populate a GridView's adapter with random data from a file. The data is shown to the user as a TextView per item. If the user touch an item, the item changes the background colour.
The problem is that if the user touches an item and then rotates the device, the item returns to its original aspect (with the normal background colour)
I've tried different approaches:
Implementing my own adapter
Extending BaseAdapter
Using ArrayAdapter
Using selectors for the TextView
Extending the TextView item with custom styles (from here and here)
Disabling the View within the GridView's onItemClick(AdapterView<?> parent, View view, int position, long id)
What I want to do is keep the Views' colour/style/aspect when I rotate the device
Note:
Why I load data radomly from a file?
The file contains different words. Every time the player start the activity (it is a game) different words in random order are shown inside the GridView. The user hast to point to the right word. If the user make a mistake, the word changes the colour (indeed, I prefer to disable the View). The process is repeated till the user makes the right choice.
You can save the selected states of your list using onSaveInstanceState.
As you click on an item in your list you can assign a state to a boolean array.
Implement the onSaveInstanceState method in your Fragment/Activity.
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBooleanArray(BundleArgs.STATES, mAdapter.getStates());
}
And then in onCreateView you pass those values to your adapter.
if (savedInstanceState != null) {
states = savedInstanceState.getBooleanArray(BundleArgs.STATES);
//Declare adapter and pass states to it
myAdapter = new Adapter(context, values, states);
}
That's a mistake I've seen several times repeated on SO.
The data and the view that represents the data are totally different entities and they should be treated separately.
You need to keep the state of your data in another data element and preserve that data element during rotation. For example (it's just an example, there' several ways of doing it):
// possible states
private static final int NORMAL = 0;
private static final int RIGHT = 1;
private static final int WRONG = 2;
Map<String, Integer> states; // here you keep the states
then on every click, on the code that checks the answer and change color:
// process the click/state change
states.put(word, newState);
then on rotation:
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable("states", states);
}
and on create
// onCreate
if (savedInstanceState != null) {
states = (Map<String, Integer>) savedInstanceState.getSerializable("states");
} else {
states = new HashMap<String, Integer>();
}
and then back on your custom adapter you have to check the state and modify the view accordingly.
// inside getView
int state = 0
if(states.containsKey(word)){
state = states.get(word).intValue();
}
switch(state){
// deal with the cases and set the color
}

Typed UI and listeners in android

I would like to improve the way i created the following UI. Currently i am creating each tablerow programmatically according to each object's type attribute.
class objectDTO {
private type; //enum
public boolean isMultiple(){...}
public boolean isSingle(){...}
}
I am trying to find a more dynamic solution like having a class for each type that might not requires programmatically adding layouts and components as i do in the fragment class,
if(objectDTO.isMultiple()) {
//Create TableRow + Multiple radiobuttons
}
else if(objectDTO.isSingle() {
//Create TableRow + Add One Checkbox
{
else {
//Create default invalid object Interface or skip
}
Havind a listadapter and applying the different ui there will just move the design problem to other class.
Early thanks for your contribution
Well, the simple solution for you would be to have a class hierarchy- a base objectDTO class and a child for each type. When you load the object list, have a factory method create the proper type of object. Each type would override a createView method which would create the view for that type. Then your table creation function becomes:
for(objectDTO object : allObjects){
View view = object.createView();
tableView.addView(view, lp);
}
But if you're creating a view for an object type, there's always going to need to be someone that dynamically creates view objects (createView in this case), and there's always going to need to be some function that knows what class to make an object (the factory in this case). Its just a matter of where you want that complexity to be.

Reset background color in Android ListView

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"

Adapter's getView called multiple times for same positions, how to know what view will appear on the screen?

I know the reason why the getView method of an Adapter is called more than once, but is there a way for knowing which of the returned view will be actually displayed on the activity?
Until now I put all the returned view linked to the same position in a list and, every time I need to modify a shown view I modify all the views corresponding to that position (one of those will be the right one...).
Surely is not the best way...
Here a piece of code of my adapter:
class MyAdapter extends BaseAdapter {
Vector<ImageView> vectorView[] = new Vector<ImageView>[25];
public MyAdapter(Activity context) {
...
}
public doSomeStuffOnAView(int position) {
// needs to know which view corresponds to the given position
// in order to avoid the following for cycle
for (ImageView iv: vector[position]) {
// do something
}
}
public View getView(int position, ...) {
ImageView childView = ...;
if (vector[position]==null) {
vector[position]=new Vector<ImageView>();
}
vector[position].add(childView);
return childView;
}
}
The method getView(...) might be called more than once for each position, but just one returned view per position will be shown on the activity. I need to know which of these. I thought it was the one returned the last time getView has been called for a position, but it is now always true.
one way but it is not standard.. you can directly check with integer variable i.e. hardcoded type. You have list of views in your xml file. jst cross check & compare with integer variable.

Categories

Resources