Inflated views lose their state when fragment is popped from backstack - android

I inflate the same layout multiple times and add them to the same container view. Here is what I do:
layout_inflated.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/parentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:id="#+id/radioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="#+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
</LinearLayout>
I inflate this layout and add to another view like the following:
for(int i = 0; i < 3; i++) {
final View rowLayout = layoutInflater.inflate(R.layout.layout_inflated, null, false);
rowLayout.setTag(i);
containerView.addView(rowLayout);
}
Then, suppose I enter the numbers "111", "222" and "333" to the first, second and third rows' edittexts. Then I check the RadioButton on 3rd row, and leave 1st and 2nd rows' RadioButtons unchecked. Then I move to next fragment and this fragment is put into backstack. And then I move back to this fragment, and it is popped out of backstack. At this point, all 3 RadioButtons become checked, and all edittexts have "333" in their boxes. That is, all rows have the third row's state.
But, if I do not inflate rows from the same xml and create rows like the following, then everything works fine:
for(int i = 0; i < 3; i++) {
LinearLayout rowLayout = new LinearLayout(context);
rowLayout.setOrientation(HORIZONTAL);
RadioButton radioButton = new RadioButton(context);
EditText edittext = new EditText(context);
rowLayout.addView(radioButton);
rowLayout.addView(editText);
containerView.addView(rowLayout);
}
I suspect that when I inflate the same xml multiple times and add them to the same parent view, since all of them have same id's, something happens and Android cannot differentiate them. My real row layout is much more complex and cannot create it dynamically, I have to inflate it from xml. So, can anyone explain why this is happening and how I can avoid it?
Edit: When I debug, I see that onTextChanged() and onCheckedChanged() methods are called automatically when fragment is popped out of backstack.
Thanks.

I think your problem is the same element's id in one view. According to documentation
View IDs need not be unique throughout the tree, but it is good practice to ensure that they are at least unique within the part of the tree you are searching.
And as I know findViewById() method will return the first occurrence of a View with the provided id. It can be reason why system redraw your views with values from only last view then the activity return from backstack.
So just remove android:id from xml file. If you need them use setId(int id).

Related

Adding a custom view multiple times in a layout

I have a custom XML file. I want to repeat this in a layout (say Relative) n number of times, dynamically (obviously).
I have seen many posts, but none helped. I am not looking for a ListView or Adapters or so. It's as simple as - A RelativeLayout. Inside it, adding the custom XML one above another. Any number of times.
With a static LinearLayout (Vertical orientation), adding the view dynamically results in rendering it once, not one below another. Don't know why. Although a TextView or so do repeat one below the other in a loop inside a LinearLayout (Vertical).
Then I dynamically created the layout (Relative), and inflated the custom XML. Displayed one. When I tried for another below the first it told me to remove child's parent first (Exception). If I do that and add again, its as good as removing the first rendered view and adding it again.
So how can I get multiple views in same layout?
A rough presentation of what I've attempted:
mainLayout = (RelativeLayout)findViewById(R.id.mainlay); //Mainlayout containing some views already
params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.BELOW,R.id.sideLayout); //sideLayout is an existing LinearLayout within the main layout.
View child = getLayoutInflater().inflate(R.layout.dynamiccustomlayout,null);
RelativeLayout r1 = new RelativeLayout(this);
r1.setLayoutParams(params);
r1.addView(child);
mainLayout.addView(r1);
mainLayout.setLayoutParams(params);
mainLayout.addView( child);
/* r2 = new RelativeLayout(this);
r2.setLayoutParams(params);
r2.addView(contentLayout); [Gives exception] */
This is how it worked out for me...
Before that, the issue with android is:
If you add dynamic views inside a LinearLayout (Horizontal), they will appear horizontally with new created instances, added to the view.
However, shockingly, it's not the same in case of LinearLayout (Vertical orientation). Hence the whole mess.
Solution:
The RelativeLayout layout file was binded with the variable, somewhat like this:
customLay = (RelativeLayout) mainLay.findViewById(R.id.dynamicCustomLayout);
Then, a Dynamic RelativeLayout was created within which the former variable is added/wrapped.
customLayout = new RelativeLayout(this);
customLayout.addView(customLay);
Every layout is assigned an id:
customLayout.setId(i);
And then a loop is run (2 if conditions for i=0 and i>0)
for i>0 (indicates the 2nd dynamic layout, to be added below the first), LayoutParameter is created:
params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
And then for i>0, using the ids of dynamic views, they are added one below the other:
//Following code below used id to place these views below each other to attain list type arrangement of views//
// i==0 for first view on top//
if (i == 0) {
params.addRule(RelativeLayout.BELOW, R.id.sideLayout);
customLayout.setLayoutParams(params);
}
// i>0 for views that will follow the first top//
else {
params.addRule(RelativeLayout.BELOW, i - 1);
customLayout.setLayoutParams(params);
}
Then added to main root layout, where all these views or cards need to be displayed:
includeLayout.addView(customLayout);
Ofcourse, the code is not just this. I have written the essential points that helped me achieve the target and that may help others in future.
So the main essence was ---
using a Dynamic RelativeLayout, to
bind the static RelativeLayout, and
assigning ids to the Dynamic RelativeLayout wrappers, and
on basis of ids use RelativeLayoutParameters to place the following
ids below the previous ones.
You have to instanciate every child by itself
View child = getLayoutInflater().inflate(R.layout.dynamiccustomlayout,null);
r1.addView(child);
View child2 = getLayoutInflater().inflate(R.layout.dynamiccustomlayout,null);
r1.addView(child2);
//ok, i do a analog thing in obne of my apps. here is the code:
public class FlxForm extends LinearLayout {
public FlxForm(Context context) {
super(context);
inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.flxform, this);
this.setPadding(0, 0, 0, 0);
container = (LinearLayout) this.findViewById(R.id.flxform);
this.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));
//here is my funtion to calculate the items i want to add, its a little bit too complicated, but in the end it works like:
for(int i=0;i<10;i++){
View x = inflater.inflate(R.layout.dynamiccustomlayout,null);
container.addview(x);
}
}
}
XML for the Form
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/flxform"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:background="#android:color/transparent"
android:orientation="vertical" >
</LinearLayout>
Then you can instantiate a "Form" Objekt and add it into a ScrollView
For doing this You would have to nest your RelativeLayout inside a ScrollView and Manage all the Scrolling, items adding, memory management, etc manually.
So the simple solution for adding n Number of Custom Views is to use a RecyclerView, ListView, GridView, etc with a neat CustomAdapter and Your Custom View.
Here is a nice example of using RecyclerView with custom Adapter :
http://code.tutsplus.com/tutorials/getting-started-with-recyclerview-and-cardview-on-android--cms-23465
I hope this Helps.

Is it possible to generate a list of `EditText` pairs that automatically retain their state?

I am running into a very frustrating series of problems. I would like a definitive solution; thus, I will award bounty for this question.
Requirements
Generate a list of pairs of EditText views that automatically retain their values on orientation change (screen rotation).
The number of EditText pairs is determined at runtime.
Failing method: Use a ListView
Use a ListView that has an ArrayAdapter. The array adapter uses a layout to generate each pair of TextView views. The XML for a ListView item might appear as:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<EditText
android:id="#+id/edit_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="#+id/edit_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
The ArrayAdapter would simply inflate the layout for each view.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.listview_pair, parent, false);
The values entered into the EditText are not retained on orientation change. Since the EditText views are instantiated through the ArrayAdapter, the Android runtime has no way of automatically pre-populating the EditText views with their last state before rotation. This means we must save/restore user-input manually -- failing requirement #1.
Failing method: Use a TableLayout and TableRows
Instead of using a ListView, we can inflate one TableRow layout for each EditText pair and attach it to the TableLayout. Since we only know the number of pairs at runtime (per requirement #2) we must instantiate the layout programmatically. Something like:
for (int i = 0; i < numPairs; ++i)
{
TableRow row = (TableRow) View.inflate(this, R.layout.tablerow_pair, null);
table.addView(row);
}
This also fails to retain state on orientation change. Each pair of EditText views has the same Ids as all other EditText pairs in the ListView. This happens because we instantiate the same layout for each item in the list; thus, they all have the same Ids. On rotation, Android will give every pair of EditText values the same values since they share the same Id.
Recapitulation
So, is there a way to create a list of EditText pairs that retain their state automatically upon orientation change? It seems like there must be a way, yet researching this question is difficult because it's a fairly specialized use case. I would be glad to reward some bounty for somebody who can give me a nice explanation of this situation.
You can use setId() with your EditText widgets, whether you create those widgets via their constructors or via layout inflation (per your second strategy). Use generateViewId() to get distinct view IDs.

Android - dynamically update layout contents (for a "submenu" effect)

An activity in our Android application features a spinner, the selected value of which affects which other views are to be displayed in the activity (these views are inputs for sub-parameters of the spinner parameter, and so are spinner value specific).
The contents of the activity (below the top-most spinner) should change dynamically upon spinner selection and is visualized with this very quick mockup:
Initially, we had the sub-parameters in their own linear layouts in the activity xml, and upon spinner selection change, hid all the irrelevant sub-parameter layouts, but this seems a fairly rotten approach, and also severely undermines the extensibility of the activity (in terms of adding new top spinner box options and sub-parameters).
We've also considered generating the layout completely in code with declarations of the types of inputs needed (with some encapsulated layout generator based on these declarations) for each parameter, but this seemed a bit of an over-complication, and we'd really prefer to define the sub-parameter layouts in xml.
How should we approach this?
Would this be an appropriate scenario for using fragments? (would using fragments involve hiding and showing them just as awfully as using the sub-parameter linear layouts?
Thanks!
For anyone out there seeking a solution:
We ended up having an empty 'container' view (a linear layout) within our activity's xml, which will store the sub-menus...
<LinearLayout
android:id="#+id/algorithm_layout_container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</LinearLayout>
and seperate xml layout files for each of the sub-menus (eg; the empty negative sub-menu...)
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
tools:context="PACKAGE.ACTIVITY"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
installing an onItemSelectedListener (well actually, having our activity implement it) and upon the selected spinner item changing, adjust the displayed sub-menu by clearing the container, and adding the sub-menus corresponding layout (inflated to a view) to the container...
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
// get the current spinner value
String choice = parent.getSelectedItem().toString();
// get and clear our sub-menu container
LinearLayout container = (LinearLayout) findViewById(R.id.algorithm_layout_container);
container.removeAllViews();
// determine which sub-menu layout to set by the spinner option
// (we're exploiting the strings.xml string identifier for our own identification)
int layout;
if (choice.equals(getString(R.string.halftone_algorithm_choice))) {
layout = R.layout.algorithm_halftone;;
}
else if (choice.equals(getString(R.string.negative_algorithm_choice))) {
layout = R.layout.algorithm_negative;
}
else if (choice.equals(getString(R.string.gaussian_algorithm_choice))) {
layout = R.layout.algorithm_gaussian;
}
else if (choice.equals(getString(R.string.dithering_algorithm_choice))) {
layout = R.layout.algorithm_dithering;
} else {
// only reached via a dev bug: you've got an unexpected spinner value selected
// we handle this with an alert, then switch to another (default) spinner value
}
// inflate the determined layout to a view, and add it to our container
container.addView(LayoutInflater.from(this).inflate(layout, null, false));
}

Change TextView on multiple parts of XML layout

I am adding multiple XML Views programmatically. I'm using a layout inflater to add them and there are no problems with that.
But I'm not able to modify the TextView in each of them.
For example, consider I am adding a LinearLayout three times in my final View. I have a TextView in that linear layout. I extract it using findViewById and if I setText("hello"); it is being reflected in the first layout, but not the second and third.
Will the inflater create new ids dynamically when adding multiple XML elements?
To answer part of your question, no: Ids are not dynamically generated by LayoutInflater.
When you say you are adding the views, where are you adding them? You mention a final View.
You don't include your code, but I assume you are calling Activity.findViewById. If you call this.findViewById from your Activity, you are traversing the entire View hierarchy and finding the first view with such an Id.
What you need to do, is iterate through all of the LinearLayouts that contain your TextViews and call findViewById on each of them.
for (LinearLayout layout : <fill in>) {
((TextView) layout.findViewById(R.id.yourid)).setText("hello");
}
As for the <fill in>. Hard to tell how to fill it in without your code. It seems like you are adding these into a parent view right? Let's assume we have a parent View parent = some view;.
You just have to get all of its children and iterate. There are a few ways you can do it depending on which subclass of View the parent is. Let's keep it simple and assuming the parent is just another LinearLayout.
In that case the for loop changes to something like:
for (int i = 0; i < parent.getChildCount(); i++) {
final LinearLayout layout = (LinearLayout) parent.getChildAt(i);
((TextView) layout.findViewById(R.id.yourid)).setText("hello");
}

How to go to next layout(not activity) when pressed button?

I want to create the instruction, so when user press the next button I want to set to the
next layout page. I tried setContentView(R.layout.aboutus); but it looks like to set the new
layout to particular page.
Layout:
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:background="#dddddd">
<LinearLayout android:gravity="center" android:background = "#drawable/aboutus"
android:orientation="vertical" android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
code :
public void onItemClick(AdapterView<?> parent, View view, int position,
long lg) {
switch (position) {
case 4:
setContentView(R.layout.aboutus);
}
}
So, I want to know how to go to next layout without new activity and can be back to previous page. Thank you.
You should use a ViewFlipper.
Get a reference to the ViewFlipper:
ViewFlipper myViewFlipper = (ViewFlipper) findViewById(R.id.my_viewFlipper);
You then inflate each page as a view:
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.your_layout_id, null);
And then add it to the ViewFlipper:
myViewFlipper.addView( view );
Then when the user hits the button for next page you call this method:
myViewFlipper.showNext();
Or if you want the previous page:
myViewFlipper.showPrevious();
Use a ViewSwitcher or ViewFlipper.
You can try to place the same page in your layout and set all it's component to have Visibility.GONE set, and when you press the button, to set all your visible widgets to GONE and others to Visible. Or you can try a dynamically inflate procedure ( to inflate your views ) since you can use separate layout to inflate views.
Check this for more information about inflate
http://developer.android.com/reference/android/view/LayoutInflater.html
May be you could do this:
If you have two different layouts and one wanna each one at one time.
First get the id's of each layout by findviewbyid method.
then use
firstlayout.setVisibility(View.VISIBLE).
secondlayout.setVisibility(View.GONE);
for showing first layout.
When you press button, in its click listener make
firstlayout.setVisibility(View.GONE).
secondlayout.setVisibility(View.VISIBLE);
it works but the layouts should be seperate.
Hope it helps.

Categories

Resources