Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
    }

enter image description here

enter image description here

like image 248
Chan Avatar asked Dec 27 '11 21:12

Chan


People also ask

How can you update a ListView dynamically?

This example demonstrates how do I dynamically update a ListView in android. Step 1 − Create a new project in Android Studio, go to File ⇒ New Project and fill all required details to create a new project. Step 2 − Add the following code to res/layout/activity_main. xml.

How do you create a dynamic list in flutter?

We can use ListView. builder(..) in Stateless Widgets if we want to display dynamic (different) contents every time the widget is loaded within an application. With Stateful widgets, it can change the contents of the screen dynamically.

Where do you put item of a list in Android Studio?

List items are automatically inserted to a list using an Adapter that pulls the content from a source such as an arraylist, array or database. ListView in Android Studio: Listview is present inside Containers. From there you can drag and drop on virtual mobile screen to create it.


3 Answers

If you have small number of possible variants (on your screenshots I can see 2 different list items) You have two possible variants:

  1. Setup count of different types by this method, and provide type for every item - and you can use convertView.

  2. 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;
    }
}
}
like image 138
Jin35 Avatar answered Oct 22 '22 11:10

Jin35


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.

like image 34
JoxTraex Avatar answered Oct 22 '22 13:10

JoxTraex


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.

like image 28
Ivan Bartsov Avatar answered Oct 22 '22 13:10

Ivan Bartsov