I have listview which contains textview and buttons. When i delete listview item and i try to scroll down, i get exception on this:
BuildQueue eile = countryList.get(position);
Exception:
02-08 19:11:04.279: E/AndroidRuntime(10509): java.lang.IndexOutOfBoundsException: Invalid index 15, size is 15
Seems i do not updating something when i delete item from listview. I think i have problem with ViewHolder, but i do not know what kind of...
My ArrayAdapter code:
public class MyCustomAdapter extends ArrayAdapter<BuildQueue> {
private ArrayList<BuildQueue> countryList;
public MyCustomAdapter(Context context, int textViewResourceId,ArrayList<BuildQueue> countryList) {
super(context, textViewResourceId, countryList);
this.countryList = new ArrayList<BuildQueue>();
this.countryList.addAll(countryList);
}
private class ViewHolder {
TextView code;
TextView field;
Button del;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.queue_buildings, null);
holder = new ViewHolder();
holder.code = (TextView) convertView.findViewById(R.id.code);
holder.field = (TextView) convertView.findViewById(R.id.field_text);
holder.del = (Button) convertView.findViewById(R.id.del_button);
convertView.setTag(holder);
holder.del.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button del_button = (Button) v;
BuildQueue building = (BuildQueue) del_button.getTag();
countryList.remove(building);
dataAdapter.notifyDataSetChanged();
}
});
} else {
holder = (ViewHolder) convertView.getTag();
}
BuildQueue eile = countryList.get(position);
holder.code.setText(" ( Level: " + eile.getOld_level() + " to "+eile.getNew_level()+")");
holder.field.setText(eile.getNameSort());
holder.field.setTag(eile);
holder.del.setText("Delete");
holder.del.setTag(eile);
return convertView;
}
}
You are using a two arrays in your Adapter, but only changing one of them.
Every Adapter uses getCount() to determine how many row should be drawn. ArrayAdapter's getCount() simply asks for the size of the array that you pass to the super constructor here: super(context, textViewResourceId, countryList);. But you are also using a second, local array and when you delete a value from this countryList getCount() has no idea this happened which results in getView() throwing an IndexOutOfBoundsException...
Either extend BaseAdapter, or use ArrrayAdapter's methods like getItem(), add(), and remove() and remove your local data set.
Related
I have a problem about listview. Each item of listview have an imageview and a textview. I extended BaseAdapter class for Listview adapter and overrided some methods that I must override. By the way I want to shrink the size of text in the textview if greater than 25. For this reason I created a method whose name is "shrinkText()". When I execute the application first time, this method works correctly.So the textviews whose size of text grater than 25 have been shrinked and other textviews keep their size. However, when I scrool down the listview, textviews that their text size less than 25 have been shrinked too. What should I do to fix this? Thanks
My listview adapter...
public class ListAdapter extends BaseAdapter{
private final ProgramInfo values;
private LayoutInflater mInflater;
public ListAdapter(Context context, ProgramInfo values) {
this.values = values;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount(){
return values.getSize();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.brd_stream_list_item,parent,false);
viewHolder = new ViewHolder();
viewHolder.p_Name = (TextView)convertView.findViewById(R.id.prgName);
viewHolder.p_Image = (ImageView)convertView.findViewById(R.id.prgImage);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.p_Name.setText(values.programNames.get(position));
viewHolder.p_Image.setImageResource(R.drawable.alarm_clock_ed);
CharSequence text = viewHolder.p_Name.getText();
shrinkText(text,viewHolder.p_Name); //Call for shrink
}
My shrinkText() method..
private void shrinkText(CharSequence text, TextView v){
if(text.length()>25){
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
}
}
You need to add the else statement :
private void shrinkText(CharSequence text, TextView v){
if(text.length()>25){
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
}
else {
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25); //the default text size
}
}
A tips : to avoid bug, always use if-else statement in getView (not only if).
I have made an Listview populated with list_row_layout.xml(which is populated with json serializable class), i have clickable textview and onclick changing text from "Accept" to "Accepted". But when i click it on first listview item, another textview listview items below are changing.
Here's some photos to descibe you better
this is the adapter class
public class CustomListAdapter extends BaseAdapter {
private ArrayList<FeedItem> listData;
private LayoutInflater layoutInflater;
private Context mContext;
public CustomListAdapter(Context context, ArrayList<FeedItem> listData) {
this.listData = listData;
layoutInflater = LayoutInflater.from(context);
mContext = context;
}
#Override
public int getCount() {
return listData.size();
}
#Override
public Object getItem(int position) {
return listData.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.list_row_layout, null);
holder = new ViewHolder();
holder.headlineView = (TextView)convertView.findViewById(R.id.sex);
holder.reportedDateView = (TextView) convertView.findViewById(R.id.confid);
holder.approve = (TextView) convertView.findViewById(R.id.approveTV);
holder.approve.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View argView)
{
holder.approve.setText("Accepted");
}
}
);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
FeedItem newsItem = (FeedItem) listData.get(position);
holder.headlineView.setText(Html.fromHtml(newsItem.getTitle()));
holder.reportedDateView.setText(Html.fromHtml(newsItem.getContent()));
return convertView;
}
static class ViewHolder {
TextView approve;
TextView headlineView;
TextView reportedDateView;
ImageView imageView;
}
}
Remember that views can be recycled via convertView.
In your onClick method you set the approve text to "Accepted" but when the view is recycled, you never set it back to "Accept"
Actually you need to update (something in) the list in response to an click and have the Accept/Accepted value toggle based on that value rather than simply updating what is currently visible on the screen.
-- to answer the "how" question (asked below)--
Add a new field to ViewHolder
static class ViewHolder {
TextView approve;
TextView headlineView;
TextView reportedDateView;
ImageView imageView;
FeedItem newsItem;
}
Change the onClick method:
public void onClick(View argView)
{
// note that holder no longer needs to be final in the parent class
// because it is not used here.
View parent = (View)argView.getParent();
ViewHolder clickedHolder = (ViewHolder)parent.getTag();
clickedHolder .newsItem.setAccepted(true); /// a new method
clickedHolder .approve.setText ("Accepted");
Log.d(TAG, "Accepted item #" + position);
}
After you have convertView created (if necessary)
FeedItem newsItem = (FeedItem) listData.get(position);
holder.newsItem = newsItem; // populate the new field.
holder.headlineView.setText(Html.fromHtml(newsItem.getTitle()));
holder.reportedDateView.setText(Html.fromHtml(newsItem.getContent()));
if(newsItem.isAccepted ()){ // another new method!
holder.approve.setText ("Accepted");
Log.d(TAG, "Set text to Accepted for item #" + position);
}else{
holder.approve.setText("Accept");
Log.d(TAG, "Set text to Accept for item #" + position);
}
Once it is working you should consider removing the Log.d() lines to cut down on the noise in LogCat.
I have a bit troublesome with view caching in listview (a.k.a convertView)
so here is my code,
private class CurrencyAdapter extends ArrayAdapter<CurrencyModel> {
Context ctx;
int layoutResourceId;
List<CurrencyModel> adapter_models = null;
public CurrencyAdapter(Context ctx, int layoutResourceId,
List<CurrencyModel> model) {
super(ctx, layoutResourceId, model);
this.ctx = ctx;
this.layoutResourceId = layoutResourceId;
adapter_models = model;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
Log.d(Constants.APP_TAG, "position: " + position);
View row = convertView;
CurrencyAdapterContainer holder = null;
if (row == null) {
Log.d(Constants.APP_TAG, "APP NULL");
row = ((Activity) ctx).getLayoutInflater().inflate(
layoutResourceId, parent, false);
holder = new CurrencyAdapterContainer();
holder.textView = (TextView) row
.findViewById(R.id.currencies_txt);
holder.imgView = (ImageView) row
.findViewById(R.id.currencies_flag_icon);
row.setTag(holder);
} else {
Log.d(Constants.APP_TAG, "APP NOT NULL");
holder = (CurrencyAdapterContainer) row.getTag();
}
CurrencyModel curr = getItem(position);
if (curr.currency_value == null) {
if (counter < MAX_COUNTER) {
++counter;
CurrencyJsonDownloader cDownloader = new CurrencyJsonDownloader(
curr, holder.textView); //download currency value in background, and set textview text if currency_value has been loaded in onpostExcecute (i'm using AsyncTask)
String url = CURRENCY_URL.replace("<symbol>", curr.symbol);
Log.d(Constants.APP_TAG, "Url currency: " + url);
cDownloader.execute(url);
}
holder.textView.setText("");
} else {
holder.textView.setText(curr.currency_value);
}
holder.imgView.setImageResource(curr.drawableId);
return row;
}
#Override
public CurrencyModel getItem(int position) {
// TODO Auto-generated method stub
return adapter_models.get(position);
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return adapter_models.size();
}
}
static class CurrencyAdapterContainer {
ImageView imgView;
TextView textView;
}
and here is the output in my logcat
position : 0
APP NULL
position : 1
APP NOT NULL
position : 2
APP NOT NULL
position : 3
APP NOT NULL
.
.
.
position : 10
APP NOT NULL
which make a disaster because it means that the textview being passed in the background job is the same textview and the the changed view is the same textview and the other textview will have blank view unless i scroll it of course which call again the getView() and everything is fine. But it's a problem when starting the app, because just one textview that always changing its value.
so why is this happen? and is there any hack that i can do??
thanks before...
ListView item Views are recycled, so never hold a reference to a particular item view and expect it to represent same data after ListView has been scrolled.
Pass the data item to your worker task instead and let it update the data to it.
Updating:
If your current item is off screen, It'll be requested from adapter (
when ListView scroll to it), and will show updated data.
If that item is currently being displayed , call notifyDataSetChanged() on adapter, this will make ListView refresh its displayed items.
I think problem is with your List adapter. Here i had posted a adapter class i think it will help you.
public class UploadListAdaptor extends BaseAdapter {
private Context context;
private List<UploadDetailsObj> uploadList;
public UploadListAdaptor(Context context, List<UploadDetailsObj> list) {
this.context = context;
uploadList = list;
}
public int getCount() {
return uploadList.size();
}
public Object getItem(int position) {
return uploadList.get(position);
}
public long getItemId(int position) {
return position;
}
/** LIST CATEGORY */
public View getView(int position, View convertView, ViewGroup viewGroup) {
final UploadDetailsObj chlListObj = uploadList.get(position);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater
.inflate(R.layout.inflator_progress_bar, null);
TextView photoName = (TextView) convertView
.findViewById(R.id.tv_photoname);
Button btnProgress=(Button)convertView.findViewById(R.id.btn_progress);
photoName.setText(chlListObj.getPhotoName());
}
return convertView;
}
}
You can call this adapter by using this code.
List<UploadDetailsObj> listofUploads= new ArrayList<UploadDetailsObj>();
UploadListAdaptor uploadListAdptr = new UploadListAdaptor(yourclass.this,
listofUploads);
uploadListView.setAdapter(uploadListAdptr);
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();
}
}
});
How do I refresh the content of a ListActivity using the custom ListAdapter that I created? I have in the arrayadapter a method that calls "notifyDataSetChanged();". That does not work. Neither have any of the related solutions on this site. Here's the code thus far:
private final Activity context;
private Message[] messages;
public RamRSSAdapter(Activity context, Message[] messages) {
super(context, R.layout.ram_rss_row);
this.context = context;
this.messages = messages;
}
// static to save the reference to the outer class and to avoid access to
// any members of the containing class
static class ViewHolder {
public ImageView imageView;
public TextView textView;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// ViewHolder will buffer the assess to the individual fields of the row
// layout
ViewHolder holder;
// Recycle existing view if passed as parameter
// This will save memory and time on Android
// This only works if the base layout for all classes are the same
View rowView = convertView;
//string code goes here
if (rowView == null) {
LayoutInflater inflater = context.getLayoutInflater();
rowView = inflater.inflate(R.layout.ram_rss_row, null, true);
holder = new ViewHolder();
holder.textView = (TextView) rowView.findViewById(R.id.label);
holder.imageView = (ImageView) rowView.findViewById(R.id.icon);
rowView.setTag(holder);
} else {
holder = (ViewHolder) rowView.getTag();
}
holder.textView.setText(messages[position].getTitle());
//code for image here
holder.imageView.setImageResource(getImageResID(getType(messages[position].getTitle())));
return rowView;
}
private String getType(String title){
int i1 = title.indexOf("[");
int i2 = title.indexOf("]");
if((i1==-1)||(i2==-1)){
return "";
}else{
return title.substring(i1+1, i2);
}
}
public void changeData(Message[] newData){
messages = newData;
notifyDataSetChanged();
}
/*
private int getImageResID(String type){
}
}
I have yet to see a case where notifyDataSetChanged() does anything. I've just gotten a new adapter based on the latest information, and changed the scroll position on the ListView to make it appear it's simply updated.
You should not overwrite the array of data directly- instead use the clear/insert/add/addAll/remove methods that are provided by the ArrayAdapter.