I have a custom adapter that extends the baseadapter. It has a variety of different layouts some with thumbnails some without. When I add a new item that is the same type as the one before the row displays the same values. But when I check what is set in the holder it tells me that the correct items are set.
public View getView(int position, View convertView, ViewGroup parent) {
int layoutType = getItemViewType(position);
ListViewRow item = null;
if(convertView == null) {
item = atIndex(position);
holder = new ViewHolder();
convertView = getConvertView(layoutType, parent);//ViewHolder items declared here
convertView.setTag(-1, holder);
}
else if(!viewMatchesType(layoutType, convertView.getId())){
item = atIndex(position);
holder = new ViewHolder();
convertView = getConvertView(layoutType, parent);//ViewHolder items declared here
convertView.setTag(-1, holder);
}
else {
holder = (ViewHolder) convertView.getTag(-1);
item = atIndex(position);
}
setDisplay(item, convertView);//I set values here
if(holder.tv_name.getText() != item.getName()) {
notifyDataSetInvalidated();
}
return convertView;
}
//For performance, ui elements are saved in a holder
private static class ViewHolder {
public ImageView iv_thumbnail;
public TextView tv_name;
}
i havent really found optimal solution for this problem myself, but removing this if condition seems to work for me. Instead of this
if(convertView == null) {
item = atIndex(position);
holder = new ViewHolder();
convertView = getConvertView(layoutType, parent);//ViewHolder items declared here
convertView.setTag(-1, holder);
}
just use this
item = atIndex(position);
holder = new ViewHolder();
convertView = getConvertView(layoutType, parent);//ViewHolder items declared here
convertView.setTag(-1, holder);
it sure is heavy on memory, but i haven't really found any other solution.
Related
The code below repeats the last element 4 times. This ListView is used in a tabbed activity fragment. I searched on the internet but found nothing helpful. Please help!
My adapter class:
public class MyAdapter extends ArrayAdapter<ElementList> {
public MyAdapter(Context context, ArrayList<ElementList> EventList){
super(context,0,EventList);
}
#Override
public View getView(final int position, View convertView, ViewGroup parent){
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_adapter, parent, false);
holder = new ViewHolder();
holder.id = (TextView) convertView.findViewById(R.id.textView9);
holder.name = (TextView) convertView.findViewById(R.id.textView6);
holder.date =(TextView)convertView.findViewById(R.id.textView7);
holder.time =(TextView)convertView.findViewById(R.id.textView8);
convertView.setTag( holder );
} else {
holder = (ViewHolder) convertView.getTag();
}
ElementList elementList = getItem(position);
if(elementList != null) {
holder.id.setText(ElementList.id);
holder.name.setText(ElementList.name);
holder.date.setText(ElementList.date);
holder.time.setText(ElementList.time);
}
return convertView;
}
Usage of that ListView:
ArrayList<ElementList> EventList = new ArrayList<ElementList>();
EventList.add( new ElementList("1","bla1","12/34/45","12:40"));
EventList.add( new ElementList("1","bla2","12/34/45","12:40"));
EventList.add( new ElementList("1","bla3","12/34/45","12:40"));
EventList.add( new ElementList("1","bla4","12/34/45","12:40"));
MyAdapter adapter = new MyAdapter(getActivity(),EventList);
ListView myList = (ListView) rootView.findViewById(R.id.listView);
myList.setAdapter(adapter);
Any suggestions ? .
I think this
if(elementList != null) {
holder.id.setText(ElementList.id);
holder.name.setText(ElementList.name);
holder.date.setText(ElementList.date);
holder.time.setText(ElementList.time);
}
should be this
if(elementList != null) {
holder.id.setText(elementList.id);
holder.name.setText(elementList.name);
holder.date.setText(elementList.date);
holder.time.setText(elementList.time);
}
I don't know if this will help, though. You can add in some log statements to see what's actually in your arrayList right after you populate it as well as when the adapter is getting ready to read from it.
I'm working on an expandable list that has different layouts in different child items. I'm also using viewHolder method to cached the views. The app crashes with this error:
java.lang.IllegalArgumentException: The key must be an application-specific resource id.
I know it needs something like a layout id as the key, but the only way to bring up the right viewHolder is the childType in this case:
int childType = getChildType(groupPosition, childPosition);
if (convertView == null || convertView.getTag(childType) != null) {
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.list_item, null);
viewHolder.text = (TextView)convertView.findViewById(R.id.text);
convertView.setTag(childType,viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag(childType);
}
if(childType == 0){
/***Render Stuff***/
}
How can I use childType to call up the viewHolder for different types of child items?
Complete Code:
public class ExpandableListAdapter extends BaseExpandableListAdapter{
private Context context;
private Map<String, List<?>> dataCollections;
public ExpandableListAdapter(FragmentActivity context, List<String> laptops,
Map<String, List<?>> dataCollections) {
this.context = context;
this.dataCollections = dataCollections;
}
public View getChildView(final int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
MappingItem row = (MappingItem) getChild(groupPosition, childPosition);
int childType = getChildType(groupPosition, childPosition);
ViewHolder viewHolder;
if (convertView == null || convertView.getTag(childType) != null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
viewHolder = new ViewHolder();
if(childType == 0){
convertView = inflater.inflate(R.layout.list_item, null);
viewHolder.text = (TextView)convertView.findViewById(R.id.text);
}
convertView.setTag(childType,viewHolder);
^^^^^^
}else {
viewHolder = (ViewHolder) convertView.getTag(childType);
}
if(childType == 0){
/********Render Something**********/
}
return convertView;
}
}
I'm new to android, I've tried many solutions but nothing works!
Can you please see what's wrong with my code:
I get NullPointerException in holder.CardContent.setText(card.getString("content"));
I've tried to change layoutInflater from null to parent, false
The ids are not the false one!
CardAdapter.java
public class CardAdapter extends ArrayAdapter<ParseObject> {
protected Context mContext;
protected List<ParseObject> mCards;
public CardAdapter(Context context, List<ParseObject> cards) {
super(context, R.layout.card_item, cards);
mContext = context;
mCards = cards;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.card_item, null);
holder = new ViewHolder();
holder.CardContent = (TextView) convertView.findViewById(R.id.cardText);
} else{
holder = (ViewHolder) convertView.getTag();
}
ParseObject card = mCards.get(position);
holder.CardContent.setText(card.getString("content")); // <-- NPE here.
return convertView;
}
private static class ViewHolder{
TextView CardContent;
}
}
Logcat:
at com.kardapps.lifehacks.activities.CardAdapter.getView(CardAdapter.java:64)
at android.widget.AbsListView.obtainView(AbsListView.java:2255)
at android.widget.ListView.measureHeightOfChildren(ListView.java:1263)
at android.widget.ListView.onMeasure(ListView.java:1175)
.......
The problem is that holder is null at least for the beginning.
card could be null too but its not clear at the moment.
Although the adapter recycling logic is right you have forgotten to setTag to the View.
The logic of the recycling is that the way you scroll you save (using setTag) the already read/seen data to the view (here convertView).
Then each time you pass the same position you use setTag to retrieve them instead of recreating them (LayoutInflater.from...)
#Override
public View getView(int position, #Nullable View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
// This is not an error but using parent you avoid Lint warnings
convertView = LayoutInflater.from(mContext).inflate(R.layout.card_item, parent);
holder = new ViewHolder();
holder.CardContent = (TextView) convertView.findViewById(R.id.cardText);
convertView.setTag(holder); // <-- As suggested for improvement
} else {
holder = (ViewHolder) convertView.getTag();
}
ParseObject card = mCards.get(position);
holder.CardContent.setText(card.getString("content")); // <-- NPE here.
// convertView.setTag(holder); // <-- This line is missing
return convertView;
}
You need to set convertView's tag to the holder in your if-block.
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.card_item, null);
holder = new ViewHolder();
holder.CardContent = (TextView) convertView.findViewById(R.id.cardText);
// Add this line
convertView.setTag(holder);
}
You can see that in the else-block, you're retrieving the tag and casting it to ViewHolder. If you haven't set the tag, getTag() returns null, causing the Exception.
See this code:
if(convertView == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.card_item, null);
holder = new ViewHolder();
holder.CardContent = (TextView) convertView.findViewById(R.id.cardText);
}
else{
holder = (ViewHolder) convertView.getTag();
}
ParseObject card = mCards.get(position);
holder.CardContent.setText(card.getString("content"));
Two possibilities:
if convertView is not null, you are just initialising holder object and not cardContent.
mCards.get(position); returns null
I am getting following warning in eclipse:
Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling.
on:
convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
I have a base adapter with a CheckBox implemented and I have added a tag to make the CheckBox work.
Here is the code:
public View getView(final int position, View convertView, ViewGroup parent)
{
ViewHolder mViewHolder;
mViewHolder = new ViewHolder();
LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
mViewHolder.cb = (CheckBox) convertView.findViewById(R.id.checkBox);
convertView.setTag(mViewHolder);
if (InviteFriends.isChecked[position] == true)
{
mViewHolder.cb.setChecked(true);
}
else
{
mViewHolder.cb.setChecked(false);
}
mViewHolder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean ischecked)
{
if (buttonView.isChecked())
{
InviteFriends.isChecked[position] = true;
}
else
{
InviteFriends.isChecked[position] = false;
}
}
});
TextView friendsname = (TextView) convertView.findViewById(R.id.friendsName); // title
ImageView thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb image
HashMap<String, String> song = new HashMap<String, String>();
song = data.get(position);
// Setting all values in listview
friendsname.setText(song.get(InviteFriends.KEY_DISPLAY_NAME));
imageLoader.DisplayImage(song.get(InviteFriends.KEY_IMAGEPROFILE_URL), thumb_image);
return convertView;
}
The results are coming up properly. How do I fix this warning? I am not able to get a solution for this yet?
Thanks!
Try this
static class ViewHolder {
private TextView friendsname;
private ImageView thumb_image;
private CheckBox cb;
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder mViewHolder = null;
HashMap<String, String> song = null;
if (convertView == null) {
song = new HashMap <String, String>();
mViewHolder = new ViewHolder();
LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
mViewHolder.friendsname = (TextView) convertView.findViewById(R.id.friendsName); // title
mViewHolder.thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb image
mViewHolder.cb = (CheckBox) convertView.findViewById(R.id.checkBox);
convertView.setTag(mViewHolder);
mViewHolder.cb.setTag(data.get(position));
mViewHolder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean ischecked) {
InviteFriends.isChecked[position] = buttonView.isChecked();
}
});
} else {
mViewHolder = (ViewHolder) convertView.getTag();
}
song = mViewHolder.cb.getTag();
mViewHolder.friendsname.setText(song.get(InviteFriends.KEY_DISPLAY_NAME));
mViewHolder.imageLoader.DisplayImage(song.get(InviteFriends.KEY_IMAGEPROFILE_URL), thumb_image);
mViewHolder.cb.setChecked(InviteFriends.isChecked[position]);
return convertView;
}
you should init the convert view only if it is null
these lines
LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
// [...] the rest of initialization part
// [...] some changes that must be done at refresh
return convertView;
should look like this:
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
// [...] the rest of initialization part
}
// [...] some changes that must be done at refresh
return convertView;
the goal is to recycle the already existing view in that list, not to init it each time you display it when scrolling the list for example.
Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling.
It means that you need to use View Holder pattern in your Adapter. The point of using View Holder is to reusing the views because inflating and using findViewById are slow.
When you're using the following code:
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder mViewHolder;
mViewHolder = new ViewHolder();
LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
mViewHolder.cb = (CheckBox) convertView.findViewById(R.id.checkBox);
convertView.setTag(mViewHolder);
...
return convertView;
}
you're not reusing the views but instead you always create new views.
You need to change your code to something like this (please check the comment):
// class for holding the cached view
static class ViewHolder {
TextView tvFriendsName;
ImageView imvThumbImage;
CheckBox cbInviteFriend;
}
public View getView(final int position, View convertView, ViewGroup parent) {
// holder of the views to be reused.
ViewHolder viewHolder;
// get data based on the position
HashMap<String, String> song = data.get(position);
// if no previous views found
if (convertView == null) {
// create the container ViewHolder
viewHolder = new ViewHolder();
// inflate the views from layout for the new row
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
convertView = inflater.inflate(R.layout.rowlayout, parent, false);
// set the view to the ViewHolder.
viewHolder.cbInviteFriend = convertView.findViewById(R.id.checkBox);
viewHolder.tvFriendsName = convertView.findViewById(R.id.friendsName);
viewHolder.imvThumbImage = convertView.findViewById(R.id.list_image);
// save the viewHolder to be reused later.
convertView.setTag(viewHolder);
} else {
// there is already ViewHolder, reuse it.
viewHolder = (ViewHolder) convertView.getTag();
}
// now we can set populate the data via the ViewHolder into views
viewHolder.tvFriendsName.setText(song.get(InviteFriends.KEY_DISPLAY_NAME));
imageLoader.DisplayImage(song.get(InviteFriends.KEY_IMAGEPROFILE_URL), viewHolder.imvThumbImage);
viewHolder.cbInviteFriend.isChecked(InviteFriends.isChecked[position]);
return convertView;
}
My suggestion is try to use convertView = vi.inflate(R.layout.activity_friend_list_row, null); insted of convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false); this may help you.
:-
okey.. insted of accessing like this TextView friendsname = (TextView) convertView.findViewById(R.id.friendsName); // title
ImageView thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb image
you have to use viewholder class in your adapter
for example
static class ViewHolder {
public TextView text;
public ImageView image;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
// reuse views
if (rowView == null) {
LayoutInflater inflater = context.getLayoutInflater();
rowView = inflater.inflate(R.layout.rowlayout, null);
// configure view holder
ViewHolder viewHolder = new ViewHolder();
viewHolder.text = (TextView) rowView.findViewById(R.id.TextView01);
viewHolder.image = (ImageView) rowView
.findViewById(R.id.ImageView01);
rowView.setTag(viewHolder);
}
// fill data
ViewHolder holder = (ViewHolder) rowView.getTag();
String s = names[position];
holder.text.setText(s);
if (s.startsWith("Windows7") || s.startsWith("iPhone")
|| s.startsWith("Solaris")) {
holder.image.setImageResource(R.drawable.no);
} else {
holder.image.setImageResource(R.drawable.ok);
}
return rowView;
}
What i have done is created a BaseSpinnerAdapter class and reusing it if needed
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import androidx.annotation.LayoutRes
/**
* #param T Model class
* #param VH ViewHolder class, which holds the view to reuse it
* */
abstract class BaseSpinnerAdapter<T, VH>(context: Context?, private val items: ArrayList<T>) :
BaseAdapter() {
private var inflater: LayoutInflater = LayoutInflater.from(context)
/** use viewHolder pattern to reusing the views
* #param p0 current view position
* #param p1
* #param viewGroup
* */
override fun getView(p0: Int, p1: View?, viewGroup: ViewGroup?): View {
var pClone = p1
val viewHolder: VH
if (pClone == null) {
pClone = inflater.inflate(setSpinnerItemLayout(), viewGroup, false)
viewHolder = createViewHolder(pClone)
pClone?.tag = viewHolder
} else {
viewHolder = pClone.tag as VH
}
getView(viewHolder, p0)
return pClone!!
}
override fun getItem(p0: Int): T {
return items[p0]
}
override fun getItemId(p0: Int): Long {
return p0.toLong()
}
override fun getCount(): Int {
return items.size
}
#LayoutRes
abstract fun setSpinnerItemLayout(): Int
abstract fun getView(viewHolder: VH, position: Int)
abstract fun createViewHolder(view: View?): VH
}
Here is an example How you can use BaseSpinnerAdapter in your implementation. I believe that there is no need to detail out the code description.
class SpinnerImplAdapter(context: Context?, private val items: ArrayList<AnyModelClass>) : BaseSpinnerAdapter<AnyModelClass, SpinnerImplAdapter.ViewHolderImp>(context, items) {
override fun setSpinnerItemLayout(): Int {
return R.layout.spinner_item
}
override fun createViewHolder(view: View?): ViewHolderImp {
return ViewHolderImp(view)
}
override fun getView(viewHolder: ViewHolderImp, position: Int) {
val model = items[position]
viewHolder.textView?.text = "" // model.etc get anything you want
}
/**
* I have used kotlin extension to get the viewId,
* if you are not using then simply call the view.findViewById(R.id.yourView)
* */
class ViewHolderImp(view: View?) {
val textView: TextView? = view?.textView
}
}
Is it possible to get an item view based on its position in the adapter and not in the visible views in the ListView?
I am aware of functions like getChildAt() and getItemIdAtPosition() however they provide information based on the visible views inside ListView. I am also aware that Android recycles views which means that I can only work with the visible views in the ListView.
My objective is to have a universal identifier for each item since I am using CursorAdapter so I don't have to calculate the item's position relative to the visible items.
Here's how I accomplished this. Within my (custom) adapter class:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView == null) {
LayoutInflater inflater = context.getLayoutInflater();
view = inflater.inflate(textViewResourceId, parent, false);
final ViewHolder viewHolder = new ViewHolder();
viewHolder.name = (TextView) view.findViewById(R.id.name);
viewHolder.button = (ImageButton) view.findViewById(R.id.button);
viewHolder.button.setOnClickListener
(new View.OnClickListener() {
#Override
public void onClick(View view) {
int position = (int) viewHolder.button.getTag();
Log.d(TAG, "Position is: " +position);
}
});
view.setTag(viewHolder);
viewHolder.button.setTag(items.get(position));
} else {
view = convertView;
((ViewHolder) view.getTag()).button.setTag(items.get(position));
}
ViewHolder holder = (ViewHolder) view.getTag();
return view;
}
Essentially the trick is to set and retrieve the position index via the setTag and getTag methods. The items variable refers to the ArrayList containing my custom (adapter) objects.
Also see this tutorial for in-depth examples. Let me know if you need me to clarify anything.
See below code:
public static class ViewHolder
{
public TextView nm;
public TextView tnm;
public TextView tr;
public TextView re;
public TextView membercount;
public TextView membernm;
public TextView email;
public TextView phone;
public ImageView ii;
}
class ImageAdapter extends ArrayAdapter<CoordinatorData>
{
private ArrayList<CoordinatorData> items;
public FoodDriveImageLoader imageLoader;
public ImageAdapter(Context context, int textViewResourceId,ArrayList<CoordinatorData> items)
{
super(context, textViewResourceId, items);
this.items = items;
}
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
ViewHolder holder = null;
if (v == null)
{
try
{
holder=new ViewHolder();
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader = new FoodDriveImageLoader(FoodDriveModule.this);
v = vi.inflate(R.layout.virtual_food_drive_row, null);
//System.out.println("layout is null.......");
holder.nm = (TextView) v.findViewById(R.id.name);
holder.tnm = (TextView) v.findViewById(R.id.teamname);
holder.tr = (TextView) v.findViewById(R.id.target);
holder.re = (TextView) v.findViewById(R.id.received);
holder.membercount = new TextView(FoodDriveModule.this);
holder.membernm = new TextView(FoodDriveModule.this);
holder.email = new TextView(FoodDriveModule.this);
holder.phone = new TextView(FoodDriveModule.this);
holder.ii = (ImageView) v.findViewById(R.id.icon);
v.setTag(holder);
}
catch(Exception e)
{
System.out.println("Excption Caught"+e);
}
}
else
{
holder=(ViewHolder)v.getTag();
}
CoordinatorData co = items.get(position);
holder.nm.setText(co.getName());
holder.tnm.setText(co.getTeamName());
holder.tr.setText(co.getTarget());
holder.re.setText(co.getReceived());
holder.ii.setTag(co.getImage());
imageLoader.DisplayImage(co.getImage(), FoodDriveModule.this , holder.ii);
if (co != null)
{
}
return v;
}
}
A better option is to identify using the data returned by the CursorAdapter rather than visible views.
For example if your data is in a Array , each data item has a unique index.
Here, Its very short described code, you can simple re-use viewholder pattern to increase listview performance
Write below code in your getview() method
ViewHolder holder = new ViewHolder();
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.skateparklist, null);
holder = new ViewHolder();
holder.headlineView = (TextView) convertView
.findViewById(R.id.textView1);
holder.DistanceView = (TextView) convertView
.findViewById(R.id.textView2);
holder.imgview = (NetworkImageView) convertView
.findViewById(R.id.imgSkatepark);
convertView.setTag(holder); //PLEASE PASS HOLDER AS OBJECT PARAM , CAUSE YOU CAN NOY PASS POSITION IT WILL BE CONFLICT Holder and Integer can not cast
//BECAUSE WE NEED TO REUSE CREATED HOLDER
} else {
holder = (ViewHolder) convertView.getTag();
}
// your controls/UI setup
holder.DistanceView.setText(strDistance);
......
return convertview
Listview lv = (ListView) findViewById(R.id.previewlist);
final BaseAdapter adapter = new PreviewAdapter(this, name, age);
confirm.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
View view = null;
String value;
for (int i = 0; i < adapter.getCount(); i++) {
view = adapter.getView(i, view, lv);
Textview et = (TextView) view.findViewById(R.id.passfare);
value=et.getText().toString();
Toast.makeText(getApplicationContext(), value,
Toast.LENGTH_SHORT).show();
}
}
});