I have a .xml file that is a row for a ListView and it has a layout included in it like this:
<relativeLayout>
<Textview username blah blah/>
<include
android:id="#+id/like_button_layout"
layout="#layout/like" />
<relativeLayout>
Now when I want to use ViewHolder pattern in my adapter, it shows duplicate values only for this "like" layout (which has an image and a textview inside it)
I can't figure out why. Here's the java code:
private class ViewHolder {
public TextView userName;
public View likeButtonLayout;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
final Context context = parent.getContext();
if (convertView == null) {
convertView =
LayoutInflater.from(context).inflate(R.layout.panel_talk_topic, parent, false);
holder = new ViewHolder();
holder.userName = convertView.findViewById(R.id.user_name);
holder.likeButtonLayout = convertView.findViewById(R.id.like_button_layout);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
ImageView likeButtonImage =
holder.likeButtonLayout.findViewById(R.id.like_button_imageView);
TextView likeCountText = holder.likeButtonLayout.findViewById(R.id.like_count_textView);
.
.
.
The usernames are all correct, but I think there needs to be a special way of inflating views inside views when using view holder pattern that I'm not aware of. Ideas?
This happens with recycler view, maybe it happens with list view also.. the thing is in recycler view, views are recycled and the one view holder is used again and again. That is why, the state of one view holder is persists.
What I generally do to solve it, is just reset the state in the first line of onBindViewHolder()
Related
I'm working with a custom ListView with custom Adapter which only has EditText and TextView in each row, the problem is in scroll, when there is something in the first row, for example, it doesn't matter how long is the text in the field, if it goes out of the screen while scrolling, then if I get back to it, it looses the text it had
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(R.layout.campos, viewGroup, false);
TextView tv= itemView.findViewById(R.id.lblNumeroCampo);
EditText txtCampo = itemView.findViewById(R.id.txtCampo);
txtCampo.setText(elementos.get(i));
tvNumero.setText("" + (i +1));
return itemView;
}
I just want not to lose text that is in any field of the list, thanks to those who want to help me.
I think you don't need to call the findViewById every time the getview action fired.
You can also use a holder class.
Your UI classes should be called once when the view class is null.
The view class can save the holder class with setTag method then reuse them by getTag.
Here is my suggestion.
class ViewHolder
{
TextView tvNumero;
EditText txtCampo;
}
public View getView(int position, View view, ViewGroup viewGroup) {
ViewHolder holder;
if(view == null)
{
view = inflater.inflate(resource, viewGroup, false);
holder = new ViewHolder();
holder.tvNumero= view.findViewById(R.id.lblNumeroCampo);
holder.txtCampo = view.findViewById(R.id.txtCampo);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
holder.txtCampo.setText(elementos.get(i));
holder.tvNumero.setText("" + (i +1));
return view;
}
Have a good coding.
This happens because android calls getView() everytime the view goes out of visible space and appears again. Since you are creating a new view in your getView() method, there is no recycling happening. Everytime the getView() method is called, a new view is returned with no text in it.
As mentioned by #tommybee, you can use a view holder class that checks to see if the view has already been initialized and return the initialized view if called.
Alternatively, I would suggest you to use RecyclerView instead of ListView, which makes using ViewHolder pattern mandatory. Reason is explained clearly here
I know recycle view is new but I want to know what is difference in 2 codes in list view. I have already tried to search a lot but not get specific answer. I know First one is more faster then the second because of memory consumption but why second code is slow then the first one what is the internal process can any one enplane it.
This is the first Code
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final HashMap<String ,String > item = lst.get(position);
ViewHolderItem viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.shadow_request_row, parent, false);
viewHolder = new ViewHolderItem();
viewHolder.title = (TextView)convertView.findViewById(R.id.item_name);
viewHolder.msg = (TextView)convertView.findViewById(R.id.message);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolderItem) convertView.getTag();
}
viewHolder.title.setText(item.get(Const.USERNAME));
viewHolder.msg.setText(item.get(Const.GET_MESSAGE));
return convertView;
}
This is the Second Code
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final HashMap<String ,String > item = lst.get(position);
ViewHolderItem viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.shadow_request_row, parent, false);
viewHolder = new ViewHolderItem();
viewHolder.title = (TextView)convertView.findViewById(R.id.item_name);
viewHolder.msg = (TextView)convertView.findViewById(R.id.message);
}
viewHolder.title.setText(item.get(Const.USERNAME));
viewHolder.msg.setText(item.get(Const.GET_MESSAGE));
return convertView;
}
The second example is missing the part about saving the viewHolder as the tag of the created view if the view is just being inflated and reusing the viewHolder if the view already exists.
List View items are re created whenever it wants. (This happens when you scroll up and down). Whenever a ListView needs to re create an item it calls the getView() of the adapter with the required position. Inside the getView() method you have the logic to generate the item View required for that position.
The method findViewById() that you use to find a View inside the XML, is CPU extensive. You might see a considerable lag if your XML contains a long sequence of children and the getView() contains lots of findViewById() calls.
This is where a ViewHolder comes handy. A ViewHolder is a class that can hold the View items. You can use the already created ViewHolder objects instead of calling findViewById() every time.
To make use of this you have to save a ViewHolder object associated with a particular position. You do it like this.
ViewHolder viewHolder;
if(convertView==null){
//the view is created for the first time
//you have to make the View HOlder object here
viewHOlder=new ViewHOlder(convertView);
//ViewHOlder constructor can find the required view elements and store it in variables
//now you have to save this View Holder object for future reference
//you save it as a tag
convertView.setTag(viewHolder);
}
Now you have a defined View Holder for the specific item position. Here is how to re use it.
When the ListView adapter wnats to re use it the convertView given to getItem() is not null. So the re use occurs in the else statement of the above if.
else{
//you already have a pre created View holder. Retrieve it.
viewHOlder=(ViewHolder)convertView.getTag();
//now you can get access to your View elements easily
}
In you second example, you create the ViewHOlder but you never re use it. So it makes not improvement.
I use listView with BaseAdapter
I initial put 50 items in adapter
Then when the list is scrolled up and reach row 2, I load 50 more
I execute adpater.notifyDatasetChanged() when I add more items.
Then I do listView.setSelection(50)
However, my issue is android is re-creating view for all 100 rows.
But I only want the new 50 rows to be drawn from 0 - 50
If I will have 1000 rows, UI will be very slow.
Is it possible to draw partial rows?
You should implement a view holder pattern in your adapter. Here's an example of the Adapter.getView method from one of my apps that implements this pattern:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
final LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.list_item_feeling, parent, false);
viewHolder = new ViewHolder();
viewHolder.imageViewFeeling = (ImageView) convertView.findViewById(R.id.imageview_feeling);
viewHolder.textViewFeeling = (TextView) convertView.findViewById(R.id.textview_feeling);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
final Feeling feeling = getItem(position);
viewHolder.imageViewFeeling.setImageResource(feeling.getDrawable());
viewHolder.textViewFeeling.setText(feeling.getLabel());
return convertView;
}
The ViewHolder class is simple and just holds references to the views:
private static class ViewHolder {
ImageView imageViewFeeling;
TextView textViewFeeling;
}
The idea is that the inflate and findViewById methods are heavy and if you already have a View there is no need to recreate it.
You might also consider replacing the ListView with a RecyclerView which forces the view holder pattern.
I have created a ListView and its custom Adapter. But due to some reason I am not allowed to get items from ViewHolder.
In my case ViewHolder has only one variable and that is of LinearLayout. LinearLayout contains the other child views(which is decided and created at run time). When I use ViewHolder and set the tag of holder object, on scroll I am getting the same views again.
Is there any other way to stop adapter to create views while scrolling ?
Or, while scrolling how can we clear the references of views ?
I have find this but I don't think this will work.
setRecyclerListener(new RecyclerListener() {
#Override
public void onMovedToScrapHeap(View view) {
//from here can we use this to clean the memory
}
});
ViewHolder is meant as a holder to contain ids of listitem layout.
It is optimization to avoid calling findViewById everytime new listitem is created for display by going through data container e.g. arrayList.
You cannot stop adapter in between creating item views.
Only items on display are created.
convertView acts as object being recycled for creating subsequent view while scrolling up/down.
You will not be able to use view holder for the purpose you are trying to achieve.
Sample usage as below.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
ViewHolder viewHolder = null;
if(convertView == null)
{
v = LayoutInflater.from(StockDetailsActivity.this).inflate(R.layout.stock_details_list_item, null);
viewHolder = new ViewHolder();
viewHolder.model_name_tv = (TextView) v.findViewById(R.id.model_name);
viewHolder.model_type_iv = (ImageView) v.findViewById(R.id.model_type_icon);
viewHolder.model_type_tv = (TextView) v.findViewById(R.id.model_type_desc);
viewHolder.model_stock_tv = (TextView) v.findViewById(R.id.model_stock_value);
v.setTag(viewHolder);
}
else
viewHolder = (ViewHolder) v.getTag();
stockCursor.moveToPosition(position);
// logic to update data to views as appropriate goes here
return v;
}
public class ViewHolder{
public TextView model_name_tv;
public ImageView model_type_iv;
public TextView model_type_tv;
public TextView model_stock_tv;
}
i have a ListView with a onClicklListener.
The ListView has a row Layout of say /res/listitem_a
now after an onClickevent of the any listitem , i want to change the layout of
only that listitem to say /res/listitem_b..
any help on how shall i proceed.
Use BaseAdapter and modify in getView call.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.custom_layout, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// Change text size
holder.text.setTextAppearance(context,R.style.customStyle);
return convertView;
}
static class ViewHolder {
TextView text;
}
And you can use position variable in getView call to change specific row. Hope this help!!!
You can use ViewFlipper as layout of the rows. With ViewFlipper you can specify as many layouts as you want and flip among them when something happen (like a click event). Here is a good tutorial about ViewFlipper.
Moreover, you should implement a custom adapter, extending BaseAdapter, and overriding the getView method.
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.your_row_layout, null); //this will inflate the layout into each row
}
//from here on, assign the information to display to the layout widgets
Hope I've helped you.