Changing a text view on one class with another class - android

I am building a calculator with a listview that is set dynamically. When its being built(with calculations), an onclickListener is set to each item to delete the row. This is all done with an AdapterClass. I have my MainClass that holds all the information. In this class, it has a running total of my items. This running total has a class that will add/subtract/retrieve my running total. The running total (as of now) is only set when the "CALCULATE" button is pressed. When I delete an item (row/calculation) I need it to update my running total in my main class. Keep in mind, the onClick used to delete the items is in my AdapterClass, not my MainClass. If you need further explanation let me know.
A better explanation
MainActivity
//This is called in my CALCULATE on click, after all the calulations have been made. THE ONLY PLACE THAT THE RUNNING TOTAL GETS CHANGED
newList=new ArrayList<Calculations>();
Calculations info = new Calculations();
info.SetType("Slab figure "+figureCount);
info.SetFigure(width+"x"+length+"x"+depth);
info.SetFigureAmount(String.format("%.2f",CubicYd)+"");
newList.add(info);
currentTotal.add(CubicYd);
total.setText(String.format("Total: "+"%.2f",currentTotal.getRunningTotal())+" Cubic Yards");
if(newList!=null&&newList.size()>0)
{
newAdapter.notifyDataSetChanged();
newAdapter.add(newList.get(0));
i++;
}
newAdapter.notifyDataSetChanged();
runningTotal
public class runningTotal {
double runningTotal = 0.0;
public double add(double newAmount) {
runningTotal = runningTotal + newAmount;
return runningTotal;
}
public double sub(double newAmount) {
runningTotal = runningTotal - newAmount;
return runningTotal;
}
public double getRunningTotal() {
return runningTotal;
}
public void setRunningTotal() {
runningTotal = 0.0;
}
}
ListAdapter
public class CustomListAdapter extends ArrayAdapter<Calculations> {
runningTotal currentTotal = new runningTotal();
Calculations c = new Calculations();
private Context appContext = null;
private ArrayList<Calculations> items = null;
public CustomListAdapter(Context context, int textViewResourceId,
ArrayList<Calculations> items) {
super(context, textViewResourceId, items);
this.appContext = context;
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) appContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_item, null);
}
c = items.get(position);
if (c != null) {
//Set the calculations to the list view
TextView type = (TextView) v.findViewById(R.id.tvType);
TextView figure = (TextView) v.findViewById(R.id.tvFullFigure);
TextView amount = (TextView) v.findViewById(R.id.tvAmount);
RelativeLayout layout = (RelativeLayout) v.findViewById(R.id.list_item_id);
layout.setTag(position);
//set on click to the layout that deletes the line.
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String pos = view.getTag().toString();
int _position = Integer.parseInt(pos);
currentTotal.sub(Double.parseDouble(c.getFigureAmount()));
<----This is where I need to update the textView "total" in my mainAcitivity----->
items.remove(_position);
notifyDataSetChanged();
Toast.makeText(appContext, "The amount to be released is " + Double
.parseDouble(c.getFigureAmount()), Toast.LENGTH_LONG).show();
}
});
if (type != null) {
type.setText(c.getType());
}
if (figure != null) {
figure.setText(c.getFigure());
}
if (amount != null) {
amount.setText(c.getFigureAmount());
}
}
return v;
}
}
Calculations
private String type = "";
private String figure = "";
private String figureTotal = "";
public void SetType(String type){
this.type = type;
}
public String getType(){
return this.type;
}
public void SetFigure(String figure) {
this.figure = figure;
}
public String getFigure(){
return this.figure;
}
public void SetFigureAmount(String figureTotal){
this.figureTotal = figureTotal;
}
public String getFigureAmount(){
return this.figureTotal;
}
UPDATE / this my CustomListAdapter with a ViewHolder from Beginner
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if(v == null) {
LayoutInflater vi = (LayoutInflater)appContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
holder = new ViewHolder();
holder.text = (TextView) v.findViewById(R.id.tvTotal);
v = vi.inflate(R.layout.list_item, null);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(currentTotal.getRunningTotal()+"");
c = items.get(position);
if (c != null){
//Set the calculations to the list view
TextView type = (TextView) v.findViewById(R.id.tvType);
TextView figure = (TextView) v.findViewById(R.id.tvFullFigure);
TextView amount = (TextView) v.findViewById(R.id.tvAmount);
RelativeLayout layout = (RelativeLayout) v.findViewById(R.id.list_item_id);
layout.setTag(position);
//set on click to the layout that deletes the line.
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String pos = view.getTag().toString();
int _position = Integer.parseInt(pos);
double newTotal;
double oldTotal;
oldTotal = Double.parseDouble(c.getFigureAmount());
newTotal = currentTotal.getRunningTotal() - oldTotal;
currentTotal.setRunningTotal(newTotal);
holder.text.setText("total after delete" );
items.remove(_position);
notifyDataSetChanged();
}
});
if(type!=null) {
type.setText(c.getType());
}
if(figure!=null){
figure.setText(c.getFigure());
}
if(amount !=null){
amount.setText(c.getFigureAmount());
}
}
return v;
}
static class ViewHolder{
TextView text;
}
}
I get nullPointer in the line
holder.text = (TextView) v.findViewById(R.id.tvTotal);

the problem is you're using the on click for a view inside your list, if you call list.setOnItemClickListener and wire up your onclick in your activity, rather then layout.setOnClickListener on your adapter. Note I'm not saying you can't do what you're asking to do, but this would be the recommended way to fix it.
something like this (on the main Activity where the text view lives):
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Calculations c = (Calculations) parent.getAdapter().getItem(position);
currentTotal.sub(Double.parseDouble(c.getFigureAmount()));
total.setText(String.format("Total: "+"%.2f",currentTotal.getRunningTotal())+" Cubic Yards");
items.remove(_position);
notifyDataSetChanged();
Toast.makeText(appContext, "The amount to be released is " + Double
.parseDouble(c.getFigureAmount()), Toast.LENGTH_LONG).show();
}
});
though you never posted where you're setting your adapter so I'm not sure what your list view is called.

In the adapter class use a holder and try to update the veiws using the holder.. here is an example
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary
// calls
// to findViewById() on each row.
ViewHolder holder;
// When convertView is not null, we can reuse it directly, there is no
// need
// to reinflate it. We only inflate a new View when the convertView
// supplied
// by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.sample, null);
// Creates a ViewHolder and store references to the two children
// views
// we want to bind data to.
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
// Bind the data efficiently with the holder.
holder.name.setText(myElements.get(id));
holder.icon.setImageBitmap(mIcon1);
return convertView;
}
static class ViewHolder {
TextView name;
ImageView icon;
}
So Now, suppose you want to update textview called total
Step 1
define total in ViewHolder class like
static class ViewHolder{
/* Define all your view's to update here*/
TextView total;
}
Step 2
create a instance of ViewHolder in getView() method.
ViewHolder holder;
Step 3
use a holder to update your textview in onclick method
holder.total.setText("The text u want to set");

Related

Android List View on Click position

I have a view where rows are being added through list view. The list item has following horizontally:
Checkbox -Checkbox,
Item Name - textView,
minus button - Imagebutton,
quantity - textview,
add button. - Imagebutton
I need to update quantity based on add or minus click. I am able to get the add button position through getTag(), but unable to get the corresponding quantity to update it.Below is the adapter class
public class CustomAdapter extends BaseAdapter {
private List<String> mListItems;
private LayoutInflater mLayoutInflater;
private Context mContext;
ItemDBAdapter itemDB;
ViewHolder holder;
int a =1;
public CustomAdapter(Context context,ArrayList<String> arrayList){
mContext=context;
mListItems=arrayList;
mContext=context;
//get the layout inflater
mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return mListItems.size();
}
#Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
#Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
private static class ViewHolder {
TextView itemName,quantity;
CheckBox checkbox;
ImageButton addBtn,minusBtn;
}
#Override
public View getView(int position, View convertview, ViewGroup viewGroup) {
// create a ViewHolder reference
//check to see if the reused view is null or not, if is not null then reuse it
if (convertview == null) {
holder = new ViewHolder();
convertview = mLayoutInflater.inflate(R.layout.list_item, null);
/* holder.itemName=new TextView(mContext);
holder.checkbox=new CheckBox(mContext);
holder.quantity=new TextView(mContext);
holder.addBtn=new ImageButton(mContext);
holder.minusBtn=new ImageButton(mContext)*/;
holder.itemName = (TextView) convertview.findViewById(R.id.item_name);
holder.addBtn = (ImageButton) convertview.findViewById(R.id.add);
holder.minusBtn = (ImageButton) convertview.findViewById(R.id.minus);
holder.quantity = (TextView) convertview.findViewById(R.id.item_quantity);
holder.checkbox = (CheckBox) convertview.findViewById(R.id.cbBox);
holder.checkbox.setOnCheckedChangeListener(checkListener);
holder.checkbox.setTag(position);
holder.minusBtn.setImageResource(R.drawable.minus);
holder.quantity.setText(String.valueOf(a));
holder.quantity.setTag(position);
holder.addBtn.setImageResource(R.drawable.add);
holder.addBtn.setOnClickListener(addBtnClick);
holder.addBtn.setTag(position);
//holder.minusBtn.setOnClickListener(minusBtnClick);
holder.minusBtn.setTag(position);
// the setTag is used to store the data within this view
convertview.setTag(holder);
} else {
// the getTag returns the viewHolder object set as a tag to the view
holder = (ViewHolder)convertview.getTag();
}
//get the string item from the position "position" from array list to put it on the TextView
String stringItem = mListItems.get(position);
if (stringItem != null) {
if (holder.itemName != null) {
//set the item name on the TextView
holder.itemName.setText(stringItem);
}
}
//this method must return the view corresponding to the data at the specified position.
return convertview;
}
private OnClickListener addBtnClick = new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stube
int b = (Integer) v.getTag();
a = a + 1;
holder.quantity.setText(String.valueOf(a));
}
};
}
Since the quantity is not clickable, I am unable to get the particular position to update quantity. Please advise. Thanks.
Try the below
In onClick
private OnClickListener addBtnClick = new OnClickListener() {
#Override
public void onClick(View v) {
View view = (View) v.getParent();
TextView tv = (TextView) view.findViewById(R.id.item_quantity);
int value = Integer.parseInt(tv.getText().toString());
value=value+1;
tv.setText(String.valueOf(value));
}
};
Edit: It is better to use a ViewHolder for smooth scrolling and performance
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
In if(convertView==null) block just get reference of views and setTag to them, in else part get previous tag. Apply all other things after else
change your code something like this....
public View getView(int position, View convertview, ViewGroup viewGroup) {
// create a ViewHolder reference
//check to see if the reused view is null or not, if is not null then reuse it
if (convertview == null) {
holder = new ViewHolder();
convertview = mLayoutInflater.inflate(R.layout.list_item, null);
holder.itemName = (TextView) convertview.findViewById(R.id.item_name);
holder.addBtn = (ImageButton) convertview.findViewById(R.id.add);
holder.minusBtn = (ImageButton) convertview.findViewById(R.id.minus);
holder.quantity = (TextView) convertview.findViewById(R.id.item_quantity);
holder.checkbox = (CheckBox) convertview.findViewById(R.id.cbBox);
// the setTag is used to store the data within this view
convertview.setTag(holder);
} else {
// the getTag returns the viewHolder object set as a tag to the view
holder = (ViewHolder)convertview.getTag();
}
holder.checkbox.setOnCheckedChangeListener(checkListener);
holder.minusBtn.setImageResource(R.drawable.minus);
holder.quantity.setText(String.valueOf(a));
holder.addBtn.setImageResource(R.drawable.add);
holder.addBtn.setOnClickListener(addBtnClick);
//get the string item from the position "position" from array list to put it on the TextView
String stringItem = mListItems.get(position);
if (stringItem != null) {
if (holder.itemName != null) {
//set the item name on the TextView
holder.itemName.setText(stringItem);
}
}
//this method must return the view corresponding to the data at the specified position.
return convertview;
}

What better way to implement OnClickListner on row Item in List Adabpter

I have implemented custom adapter and having multiple views (ImageView, TextView) in List row. I want to set listener on each of these.
This what I have done
if (containerRow == null) {
viewHolder.item.setOnClickListener(new OnImageViewClickListener(position, context, viewHolder));
}
Now this gives me wrong position in OnImageViewClickListener as viewHolder.item.setOnClickListener is getting called only once when (containerRow == null). If I do this in else part than lot of listener object creation for multiple items (ImageView, TextView) and that number of rows (Am I right?)
As per my understanding I cannot achieve this is in ListView's setOnItemClickListener as there I cannot get the item of the on which user has clicked.
Please suggest me some neat way to implement listener on these row items and to receive right position.
Set the position as a TAG for each view and set the listener on each clickable view
Define the listener as inner class, and get the tag from the view
if (containerRow == null) {
....
....
viewHolder.txv.setOnClickListener(clickListener);
viewHolder.img.setOnClickListener(clickListener);
.....
.....
}else{
viewHolder = containerRow.getTag();
}
viewHolder.txv.setTag(position);
viewHolder.img.setTag(position);
The OnClickListener:
private View.OnClickListener clickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
Integer position = (Integer) v.getTag();
// you have the correct position
switch(v.getId()){
......
......
}
}
};
This seems to work where ListItem is a POJO containing the data for the list item...
public class ListItemAdapter extends ArrayAdapter<ListItem>
{
private static final String TAG = "ListItemAdapter";
private Activity mContext;
private int mLayoutResourceId;
private List<ListItem> mItems = null;
static class ViewHolder
{
public TextView text1;
public TextView text2;
public ImageView image;
}
public ListItemAdapter(Activity context, int textViewResourceId, List<ListItem> items)
{
super(context, textViewResourceId, items);
mContext = context;
mLayoutResourceId = textViewResourceId;
mItems = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
if (v == null)
{
LayoutInflater inflater = mContext.getLayoutInflater();
v = inflater.inflate(mLayoutResourceId, parent, false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.text1 = (TextView) v.findViewById(android.R.id.text1);
viewHolder.text1.setOnClickListener(new ItemClickListener(position));
viewHolder.text2 = (TextView) v.findViewById(android.R.id.text2);
viewHolder.text2.setOnClickListener(new ItemClickListener(position));
viewHolder.image = (ImageView) v.findViewById(android.R.id.icon);
viewHolder.image.setOnClickListener(new ItemClickListener(position));
v.setTag(viewHolder);
}
ListItem item = mItems.get(position);
Log.v(TAG, item.toString());
ViewHolder holder = (ViewHolder) v.getTag();
String text = item.getText1();
holder.text1.setText(text);
text = item.getText2();
holder.text2.setText(text);
Drawable img = item.getImage();
Log.v(TAG, "image : " + img);
holder.image.setImageDrawable(img);
return v;
}
class ItemClickListener implements View.OnClickListener
{
int position;
public ItemClickListener(int pos)
{
this.position = pos;
}
public void onClick(View v)
{
Toast.makeText(mContext, "Item clicked in row - " + position, Toast.LENGTH_SHORT).show();
};
}
}

Set OnClick Listener on button inside list view in android

I have a list view in my screen, every list item contains 2 text views and one button.
On button click i want to take the list item selected index to get some data from a vector.
This is my List Custom Adapter.But i don't know how to do that.
private class CustomAdapter extends ArrayAdapter<ServicesItems> {
public CustomAdapter(Context context, int resource,
int textViewResourceId, List<ServicesItems> objects) {
super(context, resource, textViewResourceId, objects);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
TextView item = null;
TextView description = null;
Button subNowBtn;
ServicesItems ii = getItem(position);
if (null == convertView) {
convertView = mInflater.inflate(
R.layout.list_of_servics_item_2, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
}
holder = (ViewHolder) convertView.getTag();
// cat_name = holder.gettitle();
Resources resources = getResources();
ServicesItems bean1 = (ServicesItems) servicesVector
.elementAt(position);
String cat_name_str = bean1.getService_name().toString();
String descreption = bean1.getDescription().toString();
item = holder.getItem();
item.setText(bean1.getDescription());
description = holder.getDescription();
description.setText(bean1.getService_name());
subNowBtn=holder.getSubButton();
return convertView;
}
private class ViewHolder {
private View mRow;
private TextView description = null;
private TextView item = null;
private Button sub = null;
public ViewHolder(View row) {
mRow = row;
}
public TextView getDescription() {
if (null == description) {
description = (TextView) mRow
.findViewById(R.id.category_tv);
}
return description;
}
public TextView getItem() {
if (null == item) {
item = (TextView)
mRow.findViewById(R.id.descreption_tv);
}
return item;
}
public Button getSubButton(){
if(null==sub){
sub=(Button)findViewById(R.id.subscribe_now_btn);
}
return sub;
}
}
}
In your Adapter try this:
#Override
public View getView(final int position, View convertView, ViewGroup parent)
{
View row = convertView;
YourWrapper wrapper = null;
if (row == null)
{
row = inflater.inflate(R.layout.layout, parent, false);
wrapper = new YourWrapper (row);
row.setTag(wrapper);
}
else
wrapper = (YourWrapper) row.getTag();
wrapper.getButton().setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
// What you want
}
});
return row;
}
EDIT
Your wrapper class:
public class YourWrapper
{
private View base;
private Button button;
public YourWrapper(View base)
{
this.base = base;
}
public Button getButton()
{
if (button == null)
{
button = (Button) base.findViewById(R.id.your_button);
}
return (button`);
}
}
Change sub=(Button)findViewById(R.id.subscribe_now_btn); into sub=(Button) mRow.findViewById(R.id.subscribe_now_btn);
You can get the index of list-view on button click, here are two examples:--
You can write these in your onClick method of listener.
Example 1
View parentRow = (View) v.getParent();
ListView listView = (ListView) parentRow.getParent();
final int position = listView.getPositionForView(parentRow);
Example 2
Set position using setTag when creating view
mybutton.setTag(position);
Get position in the listener
int position = (Integer) view.getTag();
Hope this help :)

Is View regenerated as ListView scrolls?

I have a custom baseadapter that I am binding to a ListView. I am loading a list of objects from a custom class.
I am altering the layout of some of the rows, when the data changes, to create headers (yeah I know there is some logic I still have to fix but it works).
My problem is, when I scroll the listview past what is orginally-visible, the application crashes with a ClassCastException error on HeaderHolder (which I see if I set a breakpoint in my catch handler). I am thinking this is due to the View being destroyed and recreated, not sure. Can someone confirm this?
public class MyCustomBaseAdapter extends BaseAdapter {
private static ArrayList<Appointment> searchArrayList;
private LayoutInflater mInflater;
public MyCustomBaseAdapter(Context context, ArrayList<Appointment> results) {
searchArrayList = results;
mInflater = LayoutInflater.from(context);
}
public int getCount() {
return searchArrayList.size();
}
public Object getItem(int position) {
return searchArrayList.get(position);
}
public long getItemId(int position) {
return position;
}
public String lastDate = null;
public String thisItemDate = null;
public View getView(int position, View convertView, ViewGroup parent)
{
try
{
thisItemDate = searchArrayList.get(position).GetDTStart().toString().substring(0,10);
if(!thisItemDate.equals(lastDate))
{
HeaderHolder holder;
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.calendar_item_header, null);
holder = new HeaderHolder();
holder.txtHeader = (TextView) convertView.findViewById(R.id.header);
convertView.setTag(holder);
convertView.setPadding(2,2,2,2);
}
else
{
holder = (HeaderHolder) convertView.getTag();
}
holder.txtHeader.setText(thisItemDate);
lastDate = thisItemDate;
return convertView;
}
}
catch (Exception e)
{ //Catch exception if any
System.err.println("Error: " + e.getMessage());
}
ViewHolder holder;
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.calendar_item, null);
holder = new ViewHolder();
holder.txtAttendee = (TextView) convertView.findViewById(R.id.attendee);
holder.txtSummary = (TextView) convertView.findViewById(R.id.summary);
holder.txtStarts = (TextView) convertView.findViewById(R.id.starts);
holder.txtEnds = (TextView) convertView.findViewById(R.id.ends);
convertView.setTag(holder);
convertView.setPadding(4, 4, 4, 16);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.txtAttendee.setText(searchArrayList.get(position).GetAttendee());
holder.txtSummary.setText(">" + searchArrayList.get(position).GetSummary());
String st = searchArrayList.get(position).GetDTStart().toString();
String en = searchArrayList.get(position).GetDTEnd().toString();
holder.txtStarts.setText(st.substring(0,16));
holder.txtEnds.setText(en.substring(0,16));
return convertView;
}
static class ViewHolder
{
TextView txtAttendee;
TextView txtSummary;
TextView txtStarts;
TextView txtEnds;
}
static class HeaderHolder
{
TextView txtHeader;
You must tell ListView through your Adapter the amount of view types it internally creates / updates and what view type to find at which list position. This is done through the methods:
int getViewTypeCount()
int getItemViewType(int)
EDIT: Your implementation is a bit complicated. You should redesign your adapter to explicitly include the section titles as data items of your internal list data structure.
One way for doing that would be to back the adapter with an ArrayList<Object> instead of ArrayList<Appointment>. That way you can have both Appointment objects and section title strings in the same list.
Both methods getView and getItemViewType need to fetch the ArrayList item at the requested position and check the item object for its type:
public int getItemViewType(int position) {
Object item = getItem(position);
if(item instanceof Appointment) {
return 0;
} else {
// It's a section title:
return 1;
}
}
You would proceed similarly in your getView method:
public View getView(int position, View convertView, ViewGroup parent) {
Object item = getItem(position);
if(convertView == null) {
// Create item view for first time
if(item instanceof Appointment) {
convertView = ... // inflate appointment list view item layout
} else {
convertView = ... // inflate title section list view item layout
}
}
// Update list view item view according to type:
if(item instanceof Appointment) {
Appointment a = (Appointment) item;
// Retrieve TextViews etc from convertView, cache it as Tag in a ViewHolder
// and update these views based on Appointment a
} else {
// Item is a section header string:
String label = (String) item;
// Retrieve label TextView from convertView... etc...
}
return convertView;
}
There is actually nothing more to it.
Well, I'm almost there... it works fine with one exception: when I scroll down past the bottom of the listview, the entries start getting out of order. Using the debugger, I have verified that the arraylist is loaded in exactly the correct order and that the date labels are right where they should be. However, once I start scrolling, things get jumbled out of order (for instance, listview entries move around and the labels move up and/or down. I think the problem is due to a nullpointer exception I am getting (in the catch block with the note to the right of it). But I'm not sure why this is occurring, and only when the list is scrolled enough so that some elements pop off and others need to come into view.
public class MyCustomBaseAdapter extends BaseAdapter {
//private static ArrayList<Appointment> searchArrayList;
private static ArrayList<Object> searchArrayList;
private LayoutInflater mInflater;
//public MyCustomBaseAdapter(Context context, ArrayList<Appointment> results) {
public MyCustomBaseAdapter(Context context, ArrayList<Object> results) {
searchArrayList = results;
mInflater = LayoutInflater.from(context);
}
public int getCount() {
return searchArrayList.size();
}
public Object getItem(int position) {
return searchArrayList.get(position);
}
public long getItemId(int position) {
return position;
}
// One way for doing that would be to back the adapter with an ArrayList<Object> instead of ArrayList<Appointment>.
// That way you can have both Appointment objects and section title strings in the same list.
// Both methods getView and getItemViewType need to fetch the ArrayList item at the requested position and check the item object for its type:
public int getItemViewType(int position) {
Object item = getItem(position);
if(item instanceof Appointment) {
return 0;
} else {
// It's a section title:
return 1;
}
}
public View getView(int position, View convertView, ViewGroup parent)
{
Object item = getItem(position);
if(convertView == null)
{
// Create item view for first time
if(item instanceof Appointment)
{ // inflate appointment list view item layout
convertView = mInflater.inflate(R.layout.calendar_item, null);
}
else
{ // inflate title section list view item layout
convertView = mInflater.inflate(R.layout.calendar_item_header, null);
}
}
// Update list view item view according to type:
if(item instanceof Appointment)
{
Appointment a = (Appointment) item;
// Retrieve TextViews etc from convertView, cache it as Tag in a ViewHolder
// and update these views based on Appointment a
ViewHolder holder;
holder = new ViewHolder();
holder.txtAttendee = (TextView) convertView.findViewById(R.id.attendee);
holder.txtSummary = (TextView) convertView.findViewById(R.id.summary);
holder.txtStarts = (TextView) convertView.findViewById(R.id.starts);
holder.txtEnds = (TextView) convertView.findViewById(R.id.ends);
holder.txtCategories = (TextView) convertView.findViewById(R.id.categories);
convertView.setTag(holder);
convertView.setPadding(4, 4, 4, 16);
try
{
holder.txtAttendee.setText(a.GetAttendee());
holder.txtSummary.setText(">" + a.GetSummary());
String st = a.GetDTStart().toString();
String en = a.GetDTEnd().toString();
holder.txtStarts.setText(st.substring(0,16));
holder.txtEnds.setText(en.substring(0,16));
String cat = a.GetCategories();
holder.txtCategories.setText(cat);
}
catch (Exception e)
{
String err = e.getMessage(); // here's the error location
}
}
else
{
// Item is a section header string:
String label = (String) item;
// Retrieve label TextView from convertView... etc...
HeaderHolder holder;
holder = new HeaderHolder();
holder.txtHeader = (TextView) convertView.findViewById(R.id.header);
convertView.setTag(holder);
convertView.setPadding(2,2,2,2);
convertView.setClickable(false);
convertView.setFocusable(false);
try
{
holder.txtHeader.setText(" " + label);
}
catch (Exception e)
{
}
}
return convertView;
}
static class ViewHolder
{
TextView txtAttendee;
TextView txtSummary;
TextView txtStarts;
TextView txtEnds;
TextView txtCategories;
}
static class HeaderHolder
{
TextView txtHeader;
}

How to add a dynamic view to a ListView item at runtime?

My problem is that I don't know whether I should use multiple list view or a custom listview item adapter which can grows dynamically. For example, for a particular user, they can have multiple activities:
- Take a picture
- Say something
- Checking in
- ...
Apparently, this list can grows as the user has done more activities. Most of the time, I often create a custom item adapter which extends from BaseAdapter and use the ItemHolder pattern as follows:
public class PlaceItemAdapter extends BaseAdapter {
private Activity context;
private List<Place> places;
private boolean notifyChanged = false;
public PlaceItemAdapter(Activity context, List<Place> places) {
super();
this.context = context;
this.places = places;
}
public int getCount() {
return places.size();
}
public Object getItem(int position) {
return places.get(position);
}
public long getItemId(int position) {
return position;
}
public static class ItemViewHolder {
TextView nameTextView;
TextView typesTextView;
TextView ratingTextView;
ImageView mapIconImageView;
}
public View getView(int position, View convertView, ViewGroup parent) {
ItemViewHolder holder;
LayoutInflater inflater = context.getLayoutInflater();
if (convertView == null) {
convertView = inflater.inflate(R.layout.place_item, null);
holder = new ItemViewHolder();
holder.nameTextView = (TextView) convertView.findViewById(R.id.place_item_xml_textview_name);
holder.typesTextView = (TextView) convertView.findViewById(R.id.place_item_xml_textview_address);
holder.ratingTextView = (TextView) convertView.findViewById(R.id.place_item_xml_textview_rating);
holder.mapIconImageView = (ImageView) convertView.findViewById(R.id.place_item_xml_imageview_location_icon);
convertView.setTag(holder);
}
else {
holder = (ItemViewHolder) convertView.getTag();
}
holder.nameTextView.setText(places.get(position).getName());
holder.typesTextView.setText(places.get(position).getAddress());
holder.ratingTextView.setText(Integer.toString(places.get(position).getRating()));
/*
* This task is time consuming!
* TODO: find a workaround to handle the image
*/
// holder.mapIconImageView.setImageBitmap(DownloadImageHelper.downloadImage(places.get(position).getIconUrl()));
holder.mapIconImageView.setImageResource(R.drawable.adium);
return convertView;
}
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
notifyChanged = true;
}
}
Using this method, the number GUI widgets is fixed which means I can't make my listview item look like the picture below.
public static class ItemViewHolder {
TextView nameTextView;
TextView typesTextView;
TextView ratingTextView;
ImageView mapIconImageView;
}
My initial approach was to create a dynamic view nested inside an adapter item, however it will produce duplicate views. To avoid duplicate view, I have set convertView to null which means each time it loads, it will create a new ItemViewHolder which eventually eats up all my memory. :( So how could I handle this situation? A minimal working example would be greatly appreciated.
Duplicate View
public class FriendFeedItemAdapter extends BaseAdapter {
private List<FriendFeedItem> items;
private Activity context;
private static LayoutInflater inflater;
public ImageLoader imageLoader;
private ItemViewHolder viewHolder;
public FriendFeedItemAdapter(Activity context, List<FriendFeedItem> items) {
this.context = context;
this.items = items;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader = new ImageLoader(context.getApplicationContext());
}
public int getCount() {
return items.size();
}
public Object getItem(int position) {
return items.get(position);
}
public long getItemId(int position) {
return position;
}
public static class ItemViewHolder {
TableLayout table;
ImageView imageViewUserPicture;
TextView textViewUsername;
TextView textViewWhatUserDo;
TextView textViewWhere;
TextView textViewTime;
ImageView imageViewSnapPictureBox;
TextView textViewWriteOnWallMessageBox;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.friend_list_feed_item, null);
viewHolder = new ItemViewHolder();
viewHolder.table = (TableLayout) convertView.findViewById(R.id.friend_list_feed_item_xml_tablelayout_table);
viewHolder.imageViewUserPicture = (ImageView) convertView.findViewById(R.id.friend_list_feed_item_xml_imageview_user_picture);
viewHolder.textViewUsername = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_username);
viewHolder.textViewWhatUserDo = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_what_user_do);
viewHolder.textViewWhere = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_where);
viewHolder.textViewTime = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_at_what_time);
convertView.setTag(viewHolder);
}
else {
viewHolder = (ItemViewHolder) convertView.getTag();
}
imageLoader.displayImage(items.get(position).getFriendPictureUrl(), viewHolder.imageViewUserPicture);
viewHolder.textViewUsername.setText(items.get(position).getFriendName());
viewHolder.textViewWhere.setText("at " + items.get(position).getPlaceName());
viewHolder.textViewTime.setText("#" + items.get(position).getActivityTime());
if (items.get(position).getChallengeType() == Challenge.Type.CHECK_IN) {
viewHolder.textViewWhatUserDo.setText("has checked in.");
}
else if (items.get(position).getChallengeType() == Challenge.Type.SNAP_PICTURE) {
viewHolder.textViewWhatUserDo.setText("has snap a picture.");
// add picture box
View rowView = inflater.inflate(R.layout.snap_picture_row_item, null);
viewHolder.imageViewSnapPictureBox = (ImageView) rowView.findViewById(R.id.snap_picture_row_item_xml_imageview_picture);
imageLoader.displayImage(items.get(position).getActivitySnapPictureUrl(), viewHolder.imageViewSnapPictureBox);
viewHolder.table.addView(rowView);
}
else if (items.get(position).getChallengeType() == Challenge.Type.WRITE_ON_WALL) {
viewHolder.textViewWhatUserDo.setText("has written a message on wall.");
// add message box
View rowView = inflater.inflate(R.layout.write_on_wall_row_item, null);
viewHolder.textViewWriteOnWallMessageBox = (TextView) rowView.findViewById(R.id.write_on_wall_row_item_xml_textview_wall_message);
viewHolder.textViewWriteOnWallMessageBox.setText(items.get(position).getActivityComment());
viewHolder.table.addView(rowView);
}
else if (items.get(position).getChallengeType() == Challenge.Type.QUESTION_ANSWER) {
viewHolder.textViewWhatUserDo.setText("has answered a question.");
}
else { // Challenge.Type.OTHER
viewHolder.textViewWhatUserDo.setText("has done some other challenges.");
}
return convertView;
}
}
Extensive Memory Usage
public View getView(int position, View convertView, ViewGroup parent) {
ItemViewHolder holder = null;
LayoutInflater inflater = context.getLayoutInflater();
convertView = inflater.inflate(R.layout.friend_list_feed_item, null);
// create holder
holder = new ItemViewHolder();
// default field
holder.table = (TableLayout) convertView.findViewById(R.id.friend_list_feed_item_xml_tablelayout_table);
holder.imageViewUserPicture = (ImageView) convertView.findViewById(R.id.friend_list_feed_item_xml_imageview_user_picture);
holder.textViewUsername = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_username);
holder.textViewWhatUserDo = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_what_user_do);
holder.textViewWhere = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_where);
holder.textViewTime = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_at_what_time);
convertView.setTag(holder);
holder.imageViewUserPicture.setImageURI(items.get(position).getFriendPictureUri());
holder.textViewUsername.setText(items.get(position).getFriendName());
holder.textViewWhere.setText("at " + items.get(position).getPlaceName());
holder.textViewTime.setText("#" + items.get(position).getActivityTime());
if (items.get(position).getChallengeType() == Challenge.Type.CHECK_IN) {
holder.textViewWhatUserDo.setText("has checked in.");
}
else if (items.get(position).getChallengeType() == Challenge.Type.SNAP_PICTURE) {
holder.textViewWhatUserDo.setText("has snap a picture.");
// add picture box
View rowView = inflater.inflate(R.layout.snap_picture_row_item, null);
holder.imageViewSnapPictureBox = (ImageView) rowView.findViewById(R.id.snap_picture_row_item_xml_imageview_picture);
holder.imageViewSnapPictureBox.setImageURI(items.get(position).getActivitySnapPictureUri());
holder.table.addView(rowView);
}
else if (items.get(position).getChallengeType() == Challenge.Type.WRITE_ON_WALL) {
holder.textViewWhatUserDo.setText("has written a message on wall.");
// add message box
View rowView = inflater.inflate(R.layout.write_on_wall_row_item, null);
holder.textViewWriteOnWallMessageBox = (TextView) rowView.findViewById(R.id.write_on_wall_row_item_xml_textview_wall_message);
holder.textViewWriteOnWallMessageBox.setText(items.get(position).getActivityComment());
holder.table.addView(rowView);
}
else if (items.get(position).getChallengeType() == Challenge.Type.QUESTION_ANSWER) {
holder.textViewWhatUserDo.setText("has answered a question.");
}
else { // Challenge.Type.OTHER
holder.textViewWhatUserDo.setText("has done some other challenges.");
}
return convertView;
}
If you have small number of possible variants (on your screenshots I can see 2 different list items) You have two possible variants:
Setup count of different types by this method, and provide type for every item - and you can use convertView.
Create "full" list item view and set visibility for elements, that you don't want to see in particular item.
Some code for #2:
public class ListTestActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
List<Element> list = new ArrayList<Element>();
list.add(new Element(0));
list.add(new Element(0));
list.add(new Element(1));
list.add(new Element(0));
list.add(new Element(1));
list.add(new Element(1));
list.add(new Element(0));
list.add(new Element(0));
list.add(new Element(1));
list.add(new Element(1));
list.add(new Element(1));
list.add(new Element(0));
list.add(new Element(0));
list.add(new Element(1));
list.add(new Element(0));
list.add(new Element(0));
((ListView) findViewById(android.R.id.list)).setAdapter(new SampleAdapter(this, list));
}
private class SampleAdapter extends BaseAdapter {
private List<Element> list;
private Context context;
public SampleAdapter(Context context, List<Element> list) {
this.list = list;
this.context = context;
}
#Override
public int getCount() {
return list.size();
}
#Override
public Element getItem(int position) {
return list.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
switch (getItemViewType(position)) {
case 0:
convertView = new CheckBox(context);
break;
default:
convertView = new Button(context);
break;
}
// Output here shows that you can lay on getItemViewType(position) as indicator of convertView type or structure
Log.e("test", getItemViewType(position) + ": " + convertView.getClass().getSimpleName());
return convertView;
}
#Override
public int getItemViewType(int position) {
return getItem(position).type;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public long getItemId(int position) {
return position;
}
}
private class Element {
public int type;
public Element(int type) {
this.type = type;
}
}
}
A custom adapter would solve your problem. This is because you can change the views that are being added to each row in the Listview, because you can change the content via logic that you implement in the custom adapter.
When the getView() method returns a view that is not null, this means for that particular row there is a view that was already there. As such if this is the case, you may or may not want to change content in that specific view. Or you could build a brand new view with dynamic content for that particular row.
One thing to note is that getView() will be called as many times as there are items found in your adapter.
Here's an idea that'll probably enable you to introduce as many item types as you like without having to modify adapter every time you do:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
AbstractItem abstractItem = ((AbstractItem)getItem(position));
// if null or new item type is different than the one already there
if (convertView == null || (convertView.getTag() != null
&& ((AbstractItem)convertView.getTag()).getType().equals(abstractItem.getType())) {
convertView = abstractItem.inflateSelf(getContext());
}
abstractItem.fillViewWithData(convertView);
convertView.setTag(abstractItem);
return convertView;
}
public class AbstractItem {
public abstract View inflateSelf(Context context);
public abstract String getType();
public abstract void fillViewWithData(View view);
}
public class PictureSnapItem extends AbstractItem {
// data fields
WeakReference<Bitmap> wBitmap;
String pictureComment;
...
public abstract View inflateSelf(Context context) {
// get layout inflater, inflate a layout resource, return
return ((Activity)context).getLayoutInflater.inflate(R.layout.picture_snap_item);
}
public abstract String getType() {
// return something class-specific, like this
return getClass().getCanonicalName();
}
public abstract void fillViewWithData(View view) {
// fill the view with data from fields, assuming view has been
// inflated by this very class's inflateSelf(), maybe throw exception if
// required views can't be found
ImageView img = (ImageView) view.findViewById(R.id.picture);
TextView comment = (TextView) view.findViewById(R.id.picture_comment)
}
}
... and then extend AbstractItem and add instances to adapter, no need to add any if clauses to getView() any more.

Categories

Resources