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.
Related
I have used this tutorial in order to create a custom listview, but for some reason it is behaving differently than what I expect.
Here is the code I changed and am currently using for the adapter:
public View getView(int position, View convertView, ViewGroup parent) {
// same code as in example with some differences in the .xml
viewHolder.itemLabel.setText(info.split(";")[0]);
viewHolder.itemDescription.setText(info.split(";")[1]);
viewHolder.itemLabel.setOnClickListener(this);
viewHolder.itemLabel.setTag(position);
if(viewHolder.itemLabel.getText().equals("Fat")){
System.out.println(info);
System.out.println(viewHolder.itemLabel.getText());
viewHolder.itemLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.info_icon, 0);
}
return convertView;
}
Now, what is happening is that for some reason the icon is being set for more than one item on the list even though there is only one with "Fat" on the label.
The print is actually showing that the code on the if is being run three times.
I think that this must have something to do with the lifecycle of the app, but i'm not sure about it.
I'm sorry if the question is to vague, but I couldn't find a better way to ask this.
You need to add the else part inside getView().
public View getView(int position, View convertView, ViewGroup parent) {
// same code as in example with some differences in the .xml
if(viewHolder.itemLabel.getText().equals("Fat")){
// Stuff
}else{
// Else stuff
}
return convertView;
}
ConvertView parameter is a recycled instance of View that you previously returned from getView(). You can read about it just search for it .
Making some quick points . You are not using viewholder pattern correctly in code. Use it properly look for ViewHolder pattern in List view. Apart from that you should Move to RecyclerView ListView is in legacy now.
Please check code It may be helpful.
public View getView(int position, View convertView, ViewGroup parent) {
// same code as in example with some differences in the .xml
viewHolder.itemLabel.setText(info.split(";")[0]);
viewHolder.itemDescription.setText(info.split(";")[1]);
viewHolder.itemLabel.setOnClickListener(this);
viewHolder.itemLabel.setTag(position);
if(viewHolder.itemLabel.getText().equals("Fat")){
System.out.println(info);
System.out.println(viewHolder.itemLabel.getText());
viewHolder.itemLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.info_icon, 0);
}else{
viewHolder.itemLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
return convertView;
}
As you should know, ListView recycles the view. But i want to work with elements that can be clicked and expanded. Like i already did:
But it was completely messed up, even using:
View checklayout = convertView;
if(checklayout == null){
checklayout = inflater.inflate(R.layout.home_cell, null);
}
When some opened expandable views goes out of the screen, the recycled one, which shouldn't be expandable, receives the vanished's layout. Only view that has "1 AVALIAÇÃO LANÇADA" should open, and show it's content. I add this content by using if(qtdAvaliacoes > 0) that is a property of my Object that comes from ArrayList<>.
I "solved" this disabling the recycler, with:
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
Once my listView will only receives 5~10 rows. But i know that isn't a good practice. While i'm writting this question, i found a solution, calling my object before inflate any view, then checking the property:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View checklayout = convertView;
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final Disciplina disciplina = lista.get(position);
if(checklayout == null || disciplina.getQtdAvaliacoes() == 0){
checklayout = inflater.inflate(R.layout.home_cell, null);
}
final View layout = checklayout;
But I don't think this is the best way to do this. I read something about Tags, but was little confused. I think if i could bind these onClick methods to the row position it would be better.
Any ideas ? Or is my solution good at you, developer's, point of view.
Thanks.
The easiest way is to not do subinflates within a list item. Do it via view visibilities instead, making the inflated part GONE if you don't want it to display yet. You'll just have to explicitly set the visibility of that view in every call to getView
I have listview with hundred of items. Every item had a couple of LinearLayouts but ONE of them is Visibility.GONE! Every item has textviews and an image. On Image Click i want to set the LinearLayout with visibility.Gone to View.VISIBLE. It works fine until you scroll down the listview, then every 4th item has the same layout set to VISIBLE but i only need the Clicked one! Here is the getView method:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ImageView imgForClick;
View vi = convertView;
if (convertView == null)
vi = inflater.inflate(R.layout.custom_row, null);
final LinearLayout hiddenLayout = (LinearLayout)vi.findViewById(R.id.hiddenLayout);
imgForClick = (ImageView)vi.findViewById(R.id.imageView3);
imgForClick.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
hiddenLayout.setVisibility(View.VISIBLE);
}
});
return vi;
}
That's because you are recycling the views, so the layout gets changed on a click and then that layout is used in your other rows to save memory.
You need to remember the state of each of the rows to know whether or not the layout should be visible or not
Have something like this:
public View getView(final int position, View convertView, ViewGroup parent) {
...
if (shouldBeVisible.get(position)) {
hiddenLayout.setVisibility(View.VISIBLE);
} else {
hiddenLayout.setVisibility(View.GONE);
}
That way the layout will always be set one way or another.
shouldBeVisible is a List of something that lets you know which rows should have that layout visible or not.
EDIT--
An alternative is to remove view recycling, however this will dramatically hurt performance and should NOT be done, but I'm just explaining to list all your options.
You would remove the line
if (convertView == null)
Making Android always inflate a new view, instead of using the recycled one when possible.
I don't know if it is possible, but actually I wouldn't see why not.
Can we do a grid view not just with ImageView but with a custom view.
I am trying to make a grid view of a view composed of an ImageView and a TextView.
I know that everything happens in my Adapter getView's function but I can't figure out how to do it.
public View getView(int position, View convertView, ViewGroup parent) {
View cases = findViewById(R.id.fileUnitLayout);
if (convertView == null) {
convertView = new View(mContext);
} else {
cases = convertView;
}
return cases;
}
My view has an id of R.id.fileUnitLayout. Let's say my inner TextView has an id of A and my inner ImageView has an id of B. How can I fill them ?
Thank you,
You should not need to override getView to accomplish this, necessarily. GridView is an AdapterView so you can provide an adapter that will display what you want via setAdapter
You could, for example, use SimpleAdapter to provide an xml file that is used for each grid view item.
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...