I have this ListView:
I'm using a custom adapter.
As you see, each row is made of a checkbox, a big TextView, and a little TextView.
All items have defined the little TextView, even the "Item 2" but it's a void string.
The problem comes when I tap the EditText placed in the header of the list:
The keyboard appears, and the rows are recycled, so the getView method of my adapter is called. In that method I have an if clause where I check if the length of the "optional" text (the little TextView) is greater than 0. In that case I make some room (the space that you can see in the screenshot) and I display it.
The problem is that "Item 2" has the "optional" text initialized but it's void (0-sized). I don't understand why the if clause is executed. But more strangely... the else is also executed! In the else I just show a void string in the little TextView.
Why is this happening? The app is really simple. This is my getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListItem list_item = items.get(position);
TextView item_title = (TextView) convertView.findViewById(R.id.item_title);
TextView item_optional = (TextView) convertView.findViewById(R.id.item_optional);
item_title.setText(list_item.getTitle());
// If the task has an optional text, make some room and display it
if (list_item.hasOptional()) {
// This portion of code will be executed when you tap the EditText and the keyboard appears, putting the item up in the row
LayoutParams layout_params = (LayoutParams) item_title.getLayoutParams();
layout_params.topMargin = 10;
layout_params.height = -2; // -2: wrap_content
item_title.setLayoutParams(layout_params);
item_optional.setText(list_item.getOptional());
item_optional.setVisibility(0);
} else {
// This portion of code will ALSO be executed when you tap the EditText... why? this should not happen!
item_optional.setText("");
}
return convertView;
}
The source code can be seen here (github).
When you modify a recycled view you have no idea what the state of the view is, with respect to how it might have been customized by previous calls to getView. The view you are recycling is not a fresh-out-the-box inflation of R.layout.list_row. Think of it as a "second hand" or "used" view.
So I can see under if (list_item.hasOptional().. you make some modification to the item_title.getLayoutParams(). As a view created here may later be recycled for a list item that will fail the check if (list_item.hasOptional() under the else code block you must reset the values you modify to the default specified in the layout.
Related
The Problem
My way to add the view makes every fifth item to add the view when i only want one position to have this "Mängd" row.
Why Can i only edit listitems when they are visible on the screen.
The child will be static at 5 items even though i got like 20 item....
Is there any way to only say that item 1 will have this and not
position - firstvisibleposition
i think this is the problem with the listview
My code is not understandable at the time because of other things so i hope you get my problem anyways.
This is my main question
It seems like the thing i add to position 0 also happens to 6 and 12 Why is ListView this wierd ?
It's on swedish, but this is what i got with list view.
Every listview item has a empty Linearlayout that i add a view to when i press the down arrow button. the problem is that every fifth item gets this or i can only click on the first 5.
I dont get why they make ListView so complicated. i want to be able to get a child that is in the list without seening it!
CODE getView
public View getView (int position, View convertView, ViewGroup parent){
if (convertView == null)
{
convertView = LayoutInflater.from(getContext()).inflate(R.layout.view_meal_item_editable, null);
}
convertView.setOnTouchListener(new ItemSwipeListener(position,
getResources().getDisplayMetrics().density));
convertView.setClickable(true);
// Lookup view for data population
TextView food_item_name = (TextView) convertView.findViewById(R.id.food_item_name);
food_item_name.setHint("hello");
}
Where i add the view
View view = searchResultList.getAdapter().getView(position, searchResultList.getChildAt(position - searchResultList.getFirstVisiblePosition()), searchResultList);
LinearLayout extendedView = (LinearLayout)view.findViewById(R.id.extended_food_information);
View convertExtendedView = LayoutInflater.from(this).inflate(R.layout.change_amount_on_food_view, null);
extendedView.addView(convertExtendedView);
It's recommended to use a header view if you do this stuff only for the first element.
Otherwise it will be better if you add your extra view in getView() method, something like:
if(position==0){
// add extra view
} else {
// remove extra view if exist
}
Or you can remove the IF condition: if (convertView == null), so you will inflate a new layout each time, it will solve your problem but this is not good for list performance
In my listview I have a custom Adapter, which I build using a TreeMap, I defined the getView method which is as follows. I am trying to strike out the text in a certian textview of the listview on click, only those textviews will be striken off which contain a certain pattern of characters (y#y). However on clicking one row in the listview I am getting strike out effect on some other row.
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = inflater.inflate(R.layout.chklistlayout, parent, false);
}
TextView textView = ((TextView) convertView.findViewById(R.id.textView1));
TextView imageview = ((TextView) convertView.findViewById(R.id.textView2));
textView.setText(values[position]);
imageview.setText(mValues[position]);
String s = mValues[position];
if (s.contains("y#y")) {
System.out.println("In if of getview");
System.out.println(s);
imageview.setPaintFlags(imageview.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
}
return convertView;
}
}
I tried using a holder pattern too, using a static holder class, but the problem seems to persist. Any pointers?
this answer is half mine and half from Muhammad Babar that we both commented on the question making together a quite nice answer:
use else block to handle this, imageview.setPaintFlags() not to strike
that happens
Because of the convertView. When you scroll the list the same view
that was used before is give back to getView method. So the same
imageView that you painted with StrikeThrough before is still painted
with it. So you have to revert that action.
more over, because of this recycling, usually dealing with Adapters you must ALWAYS undo/revert/repaint/change text/change image to all the elements to make sure it will be on screen the way you want.
ps.: now you should apply a Holder pattern to get better performance.
For my application i retrieve a number from the database. When the activity starts up it has to show the number in a different color then the other numbers in the list.
After retrieving the data from the database this is my code:
int row = 5;
TextView child = (TextView)ListView.GetChildAt(row);
child.SetTextColor(Color.Red);
This code was placed in the OnCreate function. I kept getting a null value back for the child textview. I then found out that the reason for the null value is that in the OnCreate function the listview still needs to be rendered. I then moved the code to the OnStart() function but this didn't work either.
Can anyone tell me how I should retrieve the child row from the listview during the creation/start of the activity?
even if you will be able to do it this way you will experience problems with this view getting recycled .. (you will see other views getting colored with red when you scroll up and down).
You need to override your adapter and set the view's color in the position you want
under getView() -
TextView myText = (TextView) view.findViewById(R.Id ....
if (pos==5)
myText.setTextColor(Color.Red);
else
myText.setTextColor(Color.Black); //original color..
EDIT:
you don't need to have a custom xml. if you find android's xml you can find its id. I believe its android.R.id.text1 . so your adapter should look something like
myAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1) {
#Override
public View getView(int position, View v, ViewGroup parent) {
if (v == null) {
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(android.R.layout.simple_list_item_1, null);
}
View view = super.getView(position, v, parent);
if (position==5)
view.setTextColor(Color.Red);
else
view.setTextColor(Color.Black); //original color..
}
myList.setAdapter(myAdapter);
not sure I got it all right but something like that..
hope it helps.
because I need to display something more than just a list in a Fragment.
So I choose Fragment rather than ListFragment, and my layout is something looks like
<linearlayout...>
<TextView...>...<TextView/>
<Button...>...<Button/>
<ListView android:id = "#"+id/mylist" ...></ListView>
</linearylayout>
And I implemnt "MyAdapter" extend BaseAdapter, which has getView like following
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if(position == 0)
{
return categroyView("Team leader");
}
else if (position == 2)
{
return categroyView("Team memebers");
}
else
{
LayoutInflater inflater = context.getLayoutInflater();
View v = inflater.inflate(R.layout.row_group, null, false);
return v;
}
}
protected View categroyView(String text)
{
TextView txtView = new TextView(context);
txtView.setText(text);
return txtView;
}
It turns out that I can receive onItemClick when its position is 0 or 2 (which as you can see I dynamically generate textView.
Meanwhile I can't receive onItemClick when its position is not 0 or 2 (which I return inflate view from XML)
I've seen some discussion about if customized row layout has some clickable item (like button), this situation will happen, but even my row layout has only one textView, it still failed to receive onItemClick.
p.s.
Also, I select Fragment rather than Activity for other other design issue.
I know I can alternatively add v.setOnClickListene in getView to help this issue, but then still the item won't highlight if I pressed on it.
What is in the position two view? If that thing might be able to take focus sit will do it instead of the list item if you don't want the inards to be clickabke then disable that it's click and it will the be passed to the item
Also are these long lists? You will run into trouble if you inflate a lot
I have a ListView in a custom ArrayAdapter that displays an icon ImageView and a TextView in each row. When I make the list long enough to let you scroll through it, the order starts out right, but when I start to scroll down, some of the earlier entries start re-appearing. If I scroll back up, the old order changes. Doing this repeatedly eventually causes the entire list order to be seemingly random. So scrolling the list is either causing the child order to change, or the drawing is not refreshing correctly.
What could cause something like this to happen? I need the order the items are displayed to the user to be the same order they are added to the ArrayList, or at LEAST to remain in one static order. If I need to provide more detailed information, please let me know. Any help is appreciated. Thanks.
I was having similar issues, but when clicking an item in the custom list, the items on the screen would reverse in sequence. If I clicked again, they'd reverse back to where they were originally.
After reading this, I checked my code where I overload the getView method. I was getting the view from the convertedView, and if it was null, that's when I'd build my stuff. However, after placing a breakpoint, I found that it was calling this method on every click and on subsequent clicks, the convertedView was not null therefore the items weren't being set.
Here is an example of what it was:
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.listitemrow, null);
RssItem rssItem = (RssItem) super.getItem(position);
if (rssItem != null)
{
TextView title = (TextView) view.findViewById(R.id.rowtitle);
if (title != null)
{
title.setText(rssItem.getTitle());
}
}
}
return view;
}
The subtle change is moving the close brace for the null check on the view to just after inflating:
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.listitemrow, null);
}
RssItem rssItem = (RssItem) super.getItem(position);
if (rssItem != null)
{
TextView title = (TextView) view.findViewById(R.id.rowtitle);
if (title != null)
{
title.setText(rssItem.getTitle());
}
}
return view;
}
I hope this helps others who experience this same problem.
To further clarify the answer of farcats below in more general way, here is my explanation:
The vi.inflate operation (needed here for parsing of the layout of a row from XML and creating the appropriate View object) is wrapped by an if (view == null) statement for efficiency, so the inflation of the same object will not happen again and again every time it pops into view.
HOWEVER, the other parts of the getView method are used to set other parameters and therefore should NOT be included within the if (view == null) statement.
Similarily, in other common implementation of this method, some textView, ImageView or ImageButton elements need to be populated by values from the list[position], using findViewById and after that .setText or .setImageBitmap operations.
These operations must come after both creating a view from scratch by inflation and getting an existing view if not null.
Another good example where this solution is applied for BaseAdapter appears in BaseAdapter causing ListView to go out of order when scrolled
The ListView reuses view objects when you scroll. Are you overriding the getView method? You need to make sure you set each property for every view, don't assume that it will remember what you had before. If you post that method, someone can probably point you at the part that is incorrect.
I have a ListView, AdapterView and a View (search_options) that contains EditText and 3 Spinners. ListView items are multiple copies of (search_options) layout, where user can add more options in ListView then click search to send sql query built according to users options.
I found that convertView mixing indecies so I added a global list (myViews) in activity and passed it to ArrayAdapter. Then in ArrayAdapter (getView) I add every newly added view to it (myViews).
Also on getView instead of checking if convertView is null, I check if the global list (myViews) has a view on the selected (position).. It totally solved problems after losing 3 days reading the internet!!
1- on Activity add this:
Map<Integer, View> myViews = new HashMap<>();
and then pass it to ArrayAdapter using adapter constructor.
mSOAdapter = new SearchOptionsAdapter(getActivity(), resultStrs, myViews);
2- on getView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder viewHolder;
if (!myViews.containsKey(position)) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(R.layout.search_options, parent, false);
/// ...... YOUR CODE
myViews.put(position, view);
FontUtils.setCustomFontsIn(view, getContext().getAssets());
}else {
view = myViews.get(position);
}
return view;
}
Finally no more mixing items...