I declared an empty LinearLayout to which in my onCreate method I call a function that has a loop that repeats five times to inflate another layout and add it to my LinearLayout.
private void setupList() {
LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout itemList = (LinearLayout) findViewById( R.id.itemList );
itemList.removeAllViews();
for ( Category category :: categories ) {
View rowView = layoutInflater.inflate(R.layout.category_row, null);
initializeRow( rowView, category.percentComplete );
itemList.addView( rowView );
}
}
Then in my initializeRow method I initialize a TextView and ProgressBar that are in the view that I just inflated.
private void initializeRow( final View view, final int percentComplete ) {
TextView topicView = ((TextView) view.findViewById(R.id.topic));
topicView.setText( category.title );
ProgressBar progressBar = (ProgressBar) view.findViewById( R.id.progressBar );
progressBar.setMax( 100 );
progressBar.setProgress( percentComplete );
TextView textView = (TextView) view.findViewById( R.id.progressText );
textView.setText( percentComplete "% Complete" );
}
The first time this activity gets created the progress bar and text view are displayed with the proper values. However if I rotate my device, the TextViews get displayed with the proper values, but all the ProgressBars show the progress that corresponds to the last ProgressBar. Why does this work when onCreate gets called initially but not when the onCreate method gets called after the device has been rotated?
I realize that all the ProgressBars have the same id. But in my code I am getting the reference to the specific ProgressBar by using the findViewById of the view that I inflated. I was able to get this working by giving each ProgressBar a unique id by calling
progressBar.setId( progressBarIds[ position ] );
in my initializeRow method. I am curious if this behavior is a result of some bug in ProgressBar or if there is some rule about layoutInflaters or ProgressBars that I do not understand.
The difference between onCreate and the rotation event is that in onCreate you secuentially inflate the view that has only 1 progressbar. When you call view.findViewById( R.id.progressBar ); there's only 1 possible progressbar that can be found. Then you set its value to percentComplete.
When you rotate the phone, Android destroys and creates the activity. It also then tries to restore the values of the controls/views of the activity. It saves the view's values and ids in a bundle and then tries to restore them from there. I'm suspecting that since all the progressbars have the same id, when reading the value from the bundle there's only value assigned to that id. I.e. for every control it checks its id, and tries to find the corresponding value in the bundle. Since all of them have the same id, all of them get the same value.
Consider that this is the code to save to a bundle: bundle.putInt("valueKey", integer), probably all the progressbars end up having the same key.
But regardless of how the value is stored, the id is the very mean to tell each progressbar appart from the others. How is the system to know which one is which?
Since the number of progressbars is dependant on the number of categories, you'll probably going to have to handle the activity lifecycle on your own. Probably going to implement your own onSaveInstanceState() and onRestoreInstanceState() functions so that you can store each of the category percentages in a way that you can tell them appart when you need to restore them.
Related
I am dynamically adding views to Android and am seeing a really weird result which makes little to no sense. Here is the code that shows the bug:
private static int count = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
count++;
// Allow 'up' action for actionBar
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("TEST");
setContentView(R.layout.test);
LinearLayout testLinear = (LinearLayout)findViewById(R.id.testLinear);
LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.job_step_text, null, false);
TextView title = (TextView) view.findViewById(R.id.jobStepTextTextView);
final EditText editText = (EditText)view.findViewById(R.id.jobStepEditText);
title.setText("Field 1");
editText.setText("Value 1");
testLinear.addView(view);
if(count == 1) {
View view2 = inflater.inflate(R.layout.job_step_text, null, false);
TextView title2 = (TextView) view2.findViewById(R.id.jobStepTextTextView);
final EditText editText2 = (EditText)view2.findViewById(R.id.jobStepEditText);
title2.setText("Field 2");
editText2.setText("Value 2");
testLinear.addView(view2);
}
}
First time round the Activity is as follows:
Field 1: [ Value 1 ]
Field 2: [ Value 2 ]
Then when the device rotates the following is shown:
Field 1: [ Value 2 ]
Can anyone help? It seems that after the first time round the line:
final EditText editText = (EditText)view.findViewById(R.id.jobStepEditText);
is fetching an old reference and not calling setText()? When the screen rotates the line of code which sets the text field to 'Value 2' isn't even called and yet that's the result in the Field 1 text box.
Can anyone help?
By the way - a way to fix this is to give each EditText a unique ID before adding it to the view... but that makes no sense... Surely the inflated View has no knowledge of any other views (past or present) until testLinear.addView(view); is added?
Thanks in advance.
The onSaveInstanceState in the Activity (your super class) captures information about the attached content view and reloads it for you during onCreate() So when the Activity gets recreated after the device rotates, values are put into views found by ID as soon as you call setContentView(). This can produce surprising results if you are changing your layout on-the-fly.
One approach I have used is to call findViewById to see if the child view is already present, before adding it "by hand".
Another approach would be to inflate the layout first, make your adjustments to it, then call setContentView.
[I don't quite understand Android's reason for doing this -- maybe to allow super-simple applications to skip the onSaveInstanceState/reload from bundle process??)
I read this question where it says not to worry about it but I guess I need some reassurance.
My custom CursorAdapter's bindView:
#Override
public void bindView(View view, Context context, Cursor c) {
// get handles for views in xml
ImageView imageView = (ImageView)view.findViewById(R.id.add_lvrow_image);
TextView titleView = (TextView)view.findViewById(R.id.add_lvrow_title);
// get data from cursor and "massage" if necessary
String imageUri = c.getString(c.getColumnIndex(CollectionsTable.COL_IMAGEURI));
String title = c.getString(c.getColumnIndex(CollectionsTable.COL_TITLE));
// terrible time getting run-time sizes of imageView, hardcode for now
int XML_WIDTH = 100;
int XML_HEIGHT = 100;
Log.d(TAG, SCOPE + "bindView called: " +count);
count++;
// use static util class
ImageUtils.loadBitmap(context, imageUri, imageView, XML_WIDTH, XML_HEIGHT);
I'm following the series of Android tutorials for loading large bitmaps but have moved decodSmapledBitmapFromUri, calculateInSmapleSize, loadBitmap, BitmapWorkerTask, AsyncDrawable, cancelPotentialWork, and getBitmapWorkerTask to a utility folder.
...so I'm calling loadBitmap and it's chain 77 times for a listview that currently has 12 rows in it (six show on the screen at load time with just a hint of the 7th showing).
So I shouldn't worry, this is okay (this number of calls to bindView & the firing off of all those subsequent methods)?
Thanks for your words.
If in your listview xml android:layout_height is not "match_parent" change it to android:layout_height="match_parent"
The newView method is called for each newly created view while the bindView is called once each view data is required to bind to the corresponding view. One of the reasons that cause bindView get called several times is listview recycling which recycles the views that goes out of viewport. As an example when you scroll over a listview every new view which comes to view port would cause a call to bindView. I suggest you to create a cache mechanism if your loadBitmap is resource intensive so that you shouldn't have a new call to loadBitmap for each bindView call.
I'm new to android, so maybe I'm doing something horribly wrong. I want to have a particular Activity that shows details about an instance of a "Creature" class for a game. Name, damage taken, that sort of thing.
I'm having a problem getting the creature data to be properly shown in the GUI objects. Both at initial creation (where it should copy the creature's name into the name field) and when a damage mark is added (where it doesn't update to show the proper image).
Here's my mini-example of what I have:
public class CreatureDetailActivity2 extends Activity
{
Creature creature;
public void addMark(View v)
{
// connected to the button via android:onClick="addMark" in the XML
creature.getTrack().addDamage(DamageType.Normal, 1);
refreshDisplay();
new AlertDialog.Builder(this).setTitle(creature.getName())
.setMessage(creature.getTrack().toString()).show();
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_creature_detail);
creature = new Creature("Example");
refreshDisplay();
}
public void refreshDisplay()
{
final View creatureDetailView = this.getLayoutInflater().inflate(
R.layout.activity_creature_detail, null);
final EditText nameField = (EditText) (creatureDetailView
.findViewById(R.id.textbox_creature_name));
nameField.setText(creature.getName());
final ImageView damageBox0 = (ImageView) (creatureDetailView.findViewById(R.id.damageBox0));
damageBox0.setImageResource(R.drawable.__n);
// in the full program this does the same for 0 through 9, but this is a sample
// also, in the full program, this is a dynamic lookup for the correct pic
// but again, this is just a sample version.
}
}
Now the problem is that the app will load up and start, but then none of the widgets will update properly. You can click the button, and it'll show the AlertDialog, and the text of the AlertDialog will change, but the textfield in the activity won't be changed, and the ImageView doesn't change at any point from what it starts as to the one it's supposed to change to.
So I'm very stumped. I can post more about the project's setup if I'm leaving out something important, but I'm not even sure what the problem going on is so I'm not sure what else to include in my question.
final View creatureDetailView = this.getLayoutInflater().inflate(
R.layout.activity_creature_detail, null);
Inflates your Activity's layout into basically nothing, just returning the View it inflated. setContentView is what actually inflates your layout into the Activity's View hierarchy.
Once you inflate your layout you don't need to do it again. Just use findViewById without the reference to a dangling unattached View.
Change your refreshDisplay method to this:
public void refreshDisplay()
{
final EditText nameField = (EditText) findViewById(R.id.textbox_creature_name);
nameField.setText(creature.getName());
final ImageView damageBox0 = (ImageView) findViewById(R.id.damageBox0);
damageBox0.setImageResource(R.drawable.__n);
// in the full program this does the same for 0 through 9, but this is a sample
// also, in the full program, this is a dynamic lookup for the correct pic
// but again, this is just a sample version.
}
Nothing changes because You do it completely wrong.
If You wish to update any view element of current activity You do it like this
View v = findViewById(R.id.element);
v.setText("text");
this is just simple example.
You would need to cast a returned element to correct type like to be able to access all available methods.
What You do wrong is trying to inflate a layout again.
My app contains 25 edittexts. I am getting this 25 edittexts with the help of adapter class by giving count=25 and fitting in gridView by gridView.setAdapter(new TextAdapter(this)); in the activity class. So, the edittexts are dynamically generated. But the thing is I am unable to set the initial values in the edittexts. This is because the edittext objects are unavailable to set the values.
Suppose if I don't set any initial values in the edittexts and continue with my app. The same problem repeats while setting the values back in the edittexts which are entered in previous mode after changing the orientation. Because change in orientation creates new activity. Even I tried android:configChanges="orientation|keyboardHidden", but no use while I am setting the values back in the **onConfigurationChanged()**. Because I am setting the setContentView(); in the onConfigurationChanged() as I need the respective view, but still the edittext objects are unavailable to set their values.
Is there any solution to set back the values? If not, I am thinking(Might be completely wrong way, but as a newbie please go easy) to move the onCreate() method content to Application class. So the initial part goes to Application class including the creation of edittexts. and getting that edittext objects in the onCreate() method to set the values. Is it possible? Please suggest. Code snippet would be appreciated.
You will need to modify TextAdapter. Store the initial values in a String array, with the position of the String array element aligned to the position of the EditText in your GridView.
Pseudo-code (untested):
public class TextAdapter extends BaseAdapter {
String [] initial_value = {"Initial Value 1", "Initial Value 2", "Initial Value 3", ..., };
public View getView(int pos, View view, ViewGroup viewGroup) {
if (view == null) {
LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.edit_text_container, null);
}
(EditText) edtTemp = (EditText) view.findViewById(R.id.edit_text_id);
edtTemp.setText(initial_value[pos]);
}
}
I have a ListView that is filled with items of an ArrayAdapter.
Now I want to strike them out when click and also check in the beginning if they are already struck through(calling an external api etc).
But it only works in the onItemClickListener the method above it doesn't.
to understand it better, here is some code:
public void machListe() {
listViewArrayAdapter = new ArrayAdapter(getApplicationContext(),R.layout.task_item, ti);
taskListe.setAdapter(listViewArrayAdapter);
TextView ab=(TextView) taskListe.getChildAt(0);
So if I Debugg now I see that ab is null.
taskListe.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View arg1,int arg2, long arg3) {
TextView ab=(TextView) taskListe.getChildAt(0);
If I debug here, ab is NOT null.
You are trying to saft the state of the tasks inside your Views.
You should try to separate Model and View at this point.
Create a Model Object that contains a single task then extend ArrayAdapter and overwrite getView. In the getView you retrieve the correct task from the task list. Create the textview for the task and if the task is marked as done in your model you cancel out the text.
If you try to change the views in a list after they are created you would have to do this every time the list stops scrolling because the list will only hold childviews for the items that are shown on the screen, the other views are created during scrolling to save memory.
The only correct place to change a listview Item is in the getView method of the corresponding Adapter.
Have a look at the ListsView Tutorial on vogella.de for more information.
Setting the adapter will trigger a requestLayout but the actual layout is not done yet.
So taskListe won't have child-views yet when you call getChildAt(0) immediately after calling setAdapter
Here is the code and 2 is the index value
View view=adapter.getView(2, null, null);
TextView textView = (TextView) view.findViewById(R.id.textView1);
String value = textView.getText().toString();