In my Android application I have a ListView with 5 elements. I created a custom adapter, in order to change the background of some elements of the listView. For example, the second item of the list view is not ready yet, so I want to setBackground(Color.Gray), so he can look like it's not done it. In order to do that, I Overrided the getView() method from ArrayAdapter in my custom adapter how it follows:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
if(!itensAvailable[position]) v.setBackgroundColor(Color.Gray);
return v;
}
The weird thing is, no matter if I use the boolean itensAvailable[position] or !itensAvailable[position] the first element of the list always has it's background changed! All the others elements of the list are behaving like expected, except the first one. More weird than that, if I do
if(position == 2) v.setBackgroundColor(Color.Gray);
it changes the background from the item in the position 2, and the first item as well! If I do
if(position == 2) {
v.setBackgroundColor(Color.Gray);
System.out.println(v.getText());
}
Even more weird! Only the text from the position 2 gets printed, not the text from the first item.
What is going on? Android bug? By the way, im testing it on a XOOM 3.2 Honeycomb device.
And obviously, if I comment this if code, the first item doens't has it's background changed.
It is very strange !
What happens if you write :
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
if(!itensAvailable[position]) v.setBackgroundColor(Color.Gray);
else v.setBackgroundColor(Color.Transparent);
return v;
}
I cannot explain the weird behaviour. Can you try the following code and see if it solves your problem -
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = to the view to be displayed;
}
if(!itensAvailable[position]) convertView.setBackgroundColor(Color.Gray);
return convertView;
}
I Have the exact same issue. Strange indeed!
What worked for me is to remove the line
View v = super.getView(position, convertView, parent);
and use instead -
LayoutInflater inflater = LayoutInflater.from(getContext());
View v = inflater.inflate(R.layout.__my__layout__, parent, false);
.... more view manipulations ....
hope it helps!
Related
I'm developing an app with a form users will have to fill.
The form has some textview, edittexts, a listview and two buttons.
The listview consist of a textview and two radiobuttons.
I populate the listview with a question and two radiobuttons in every row.
If I reuse the convertview given in overriden getView method, when I check a radiobutton, it will check the first radiobutton visible while scrolling down as per every screen scrolled.
#Override
public View getView(int position, View convertView, ViewGroup parent){
if(convertView == null){
convertView = ((Activity)context).getLayoutInflater().inflate(R.layout.riskrowlayout, parent, false);
}
((TextView)convertView.findViewById(R.id.tvPreg)).setText(data.get(position));
return convertView;
}
Otherwise, if I inflate the layout everytime getView gets called, it will automatically uncheck the radiobutton I checked while scrolling.
#Override
public View getView(int position, View convertView, ViewGroup parent){
convertView = ((Activity)context).getLayoutInflater().inflate(R.layout.riskrowlayout, parent, false);
((TextView)convertView.findViewById(R.id.tvPreg)).setText(data.get(position));
return convertView;
}
What I'm doing wrong? How can I solve this?
If you need more info ask and I'll give you.
Thank you very much in advantage!
Problem from last comment solved, just pasted this code from another post:
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
You need to keep your check data into an array. When you scroll down and came back checked row it doesnt know where is checked. If you dont have model array simply create a checked list and set all false inside and inside getview method look at array.
#Override
public View getView(int position, View convertView, ViewGroup parent){
if(convertView == null){
convertView = ((Activity)context).getLayoutInflater().inflate(R.layout.riskrowlayout, parent, false);
}
TextView textView = (TextView) convertView.findViewById(R.id.tvPreg);
textView.setText(data.get(position));
radioButton.setChecked(checked.get(position));
return convertView;
}
I defined a Adapter which extends BaseAdapter when I use ListView to display something.I overrided View getView (int position, View convertView, ViewGroup parent) method to reuse View component, in the method, I also wrote if(convertView == null) {System.out.println("test");} block. There are 50 rows data in ListView and the screen only can display about 20 rows data. when I ran the application, LogCat printed less than 50 rows of "test" though I slided the screen to make sure all data are loaded.But why ? I think it should print 50 rows data. Here is the key code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = inflater.inflate(resource, null);
System.out.println(++count + "convertView == null:" + convertView);
}
}
someone help me please, I am a newbie.... thanks
Android does not inflate a View for every item in your adapter. It reuses inflated views previously used for other items.
The pattern for binding views in a adapter is something like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if(convertView == null) {
view = inflater.inflate(resource, null);
} else {
view = convertView;
}
// bind data to view here
return view;
}
In fact you normally would use a ViewHolder class. But first fix your basic adapter before reading about that.
Because convertView will be null only if there isn't a previously returned view that can be recycled (i.e. it's no longer on screen).
The views you return from an adapter's getView() can be recycled in later calls to getView(). The framework passes such recyclable views in via the convertView arg.
So your convertView == null branch only gets run enough times to fill the listview screen once and after that, when scrolling, these old views get recycled.
You missing return view.
ListView recycles view. How ListView's recycling mechanism works
Use a ViewHolder pattern
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null) {
convertView = inflater.inflate(resource, null);
holder = new ViewHolder();
holder.tv = (TextView)convertView.findViewById(R.id.textView1);
//initialize views
convertView.setTag(holder);
} else {
holder =(ViewHolder) convertView.getTag();
}
// update your view here
holder.tv.setText("hi");
return convertView;
}
static class ViewHolder {
// YourViews Declaration
TextView tv; // an example
}
I have a row in a listview that I need to change its layout dynamically. In my array adapter I have the following (simplified) code
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = inflater.inflate(R.layout.default);
}
...
if(condition) {
View view = inflater.inflate(R.layout.new);
...
return view; // doesn't work
}
return convertView;
}
My actual listview is actually pretty complex and I am also using the getViewType for the two different layouts. But the above code should be enough to make my point that the new layout won't display correctly when I want to switch layout dynamically.
My question is, how can I refresh the convertView, or trigger a reload of the adapter/listview to the point that the convertViews will be cleared? I've tried calling notifyDataSetChanged, or calling the listview invalidateViews() but they all didn't worked. My last resort seems to restart the whole activity but I don't think this is an elegant solution.
I'm new to android programming and doing the first steps with Adapters (for a ListView).
Overriding the Adapter.getView I often see things like this:
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) parent.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
itemView = inflater.inflate(R.layout.table_row, null);
} else {
itemView = convertView;
}
// play with itemView
return itemView;
}
My question is what speaks against this:
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = super(position, convertView, parent);
// play with itemView
return itemView;
}
This seems to work for me but I'm sure there's a big point I'm missing :D
Thanks for reading and sorry for my bad english ...
You can use
View itemView = super(position, convertView, parent);
if only you are deriving from "ready to use" adapters (not BaseAdapter), like SimpleAdapter, or ArrayAdapter, as they already have their implementation for the getView().
Have a look at them: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r2.1/android/widget/ArrayAdapter.java#361 for the ArrayAdapter, and
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r2.1/android/widget/SimpleAdapter.java#113 for SimpleAdapter.
If you derive from BaseAdapter, you will have to manualy implement the whole method, as you've described in the first example, because it does not have it out of the box: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r2.1/android/widget/BaseAdapter.java#BaseAdapter
The getView(..)-method of the Adapter can be multiple ways. The only question is, which one is the most efficient?
An interesting article to read and make you understand the ListView more detailed: http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/
If you mean that this piece of code:
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) parent.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
itemView = inflater.inflate(R.layout.table_row, null);
} else {
itemView = convertView;
}
seems unnecessary for you: this piece of code allows Android to create a relatively small number of cells (equals to the number of cells that are visible on your screen +-), and then "recycle" these cells - use them over and over again while the user scrolls the list, instead of creating a cell for each object in your array.
This will help you with:
Saving memory - because you don't create view for each element in your array
Saving CPU usage - creating a view object out of xml file ("inflating") is relatively expensive task and doing so for each item in your array might choke your UI thread
I have several spinners that I have created a custom ArrayAdapter for so I can change the drop down menu look. I want to manipulate the view depending on what spinner the dropdown belongs to. I thought I would be able to do something like parent.getTag() but it is returning null.
The custom array adapter looks like:
class BackgroundColorAdapter extends ArrayAdapter<String> {
BackgroundColorAdapter() {
super(SettingsActivity.this, R.layout.settings_spinner_item, R.id.item_text, textColors);
}
public View getDropDownView (int position, View convertView, ViewGroup parent){
View row=super.getView(position, convertView, parent);
if(parent.getTag().equals("background"){
//Do custom stuff here
}
return(row);
}
}
and I'm setting the tag:
settingsSpinner.setTag("bg_color_spinner");
settingsSpinner.setAdapter(new BackgroundColorAdapter());
I think I'm confused how the view hierarchy works but it seems logical that the parent of the spinner drop down would be the spinner. Anyone know how I can find out what spinner the drop down belongs to in getDropDownView?
edit: made the settingsSpinner a single spinner instead of an array of spinners to make it less confusing
Eventually got this to work, here is the code for example that changes the text font for each item in the drop down.
class TextSizeAdapter extends ArrayAdapter<String> {
TextSizeAdapter() {
super(SettingsActivity.this, R.layout.settings_spinner_item, R.id.item_text, textSizes);
}
public View getDropDownView (int position, View convertView, ViewGroup parent){
View row=super.getView(position, convertView, parent);
TextView text = (TextView)row.findViewById(R.id.item_text);
text.setTextSize(TypedValue.COMPLEX_UNIT_PX,appState.FONTSIZES[position]);
RadioButton radio = (RadioButton)row.findViewById(R.id.item_radio);
if(settingsSpinners[2].getSelectedItemPosition() == position){
radio.setChecked(true);
}else{
radio.setChecked(false);
}
return(row);
}
}
I'm unfamiliar with getDropDownView(), and don't know why you use it. Documentation for getDropDownView() states the following about the parent:
parent the parent that this view will eventually be attached to
This doesn't sound like the 'parent' you are looking for...
Since the 'parent' in the getView() call is indeed a Spinner, you could use that to store an instance variable of the parent like below:
public Spinner mParent = null;
public View getView (int position, View convertView, ViewGroup parent)
{
this.mParent = parent;
return super.getView(position, convertView, parent);
}
public View getDropDownView(int position, View convertView, ViewGroup parent)
{ // Your code here -> but use 'mParent'
}
I haven't tried, but maybe it's a workaround to get what you need. Please let me know if you found the solution.