如何在运行时向ListView项添加动态视图?

8
我的问题是我不确定是否应该使用多个列表视图或自定义列表视图项适配器,它可以动态增长。例如,对于某个用户,他们可以有多个活动:
- 拍照片
- 说些什么
- 签到
- ...

显然,这个列表会随着用户完成更多活动而增长。大多数情况下,我经常创建一个扩展自BaseAdapter的自定义项目适配器,并使用以下的ItemHolder模式:

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

使用这种方法,GUI小部件的数量是固定的,这意味着我无法使我的列表项看起来像下面的图片。
    public static class ItemViewHolder {
        TextView nameTextView;
        TextView typesTextView;
        TextView ratingTextView;
        ImageView mapIconImageView;
    }

我的初始方法是在适配器项内创建一个动态视图,但这将产生重复的视图。为了避免重复视图,我将 convertView 设置为 null,这意味着每次加载时,它都会创建一个新的 ItemViewHolder,最终耗尽所有内存。 :( 那么我该如何处理这种情况?非常感谢提供一个最小工作示例。

重复视图

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

广泛的内存使用

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

3个回答

9
如果您只有少量的可能变量(从您的截图中可以看到有2个不同的列表项),则有两种可能的变量:
  1. 通过方法设置不同类型的数量,并为每个项目提供类型 - 然后您可以使用convertView。

  2. 创建“完整”的列表项视图,并为您不想在特定项目中看到的元素设置可见性。

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

那就是正确的方式。我喜欢使用一个名为 ItemViewType 的接口,它提供了创建视图(在 convertView 为 null 的情况下)和绑定数据到视图的方法。实现类将其特定的 ViewHolder 声明为内部类。你可以通过添加一个 accepts 方法来扩展该接口,以帮助你在适配器的 getItemViewType 方法中使用。 - Oderik
@Jin35:非常感谢您提供的示例。这是我长期以来一直在寻找的答案。 - roxrook
@Oderik - 我同意我的示例不适合“生产代码”,但这是展示具有多个视图类型的适配器想法最简单的方法。 - Jin35

2
一个自定义的适配器可以解决你的问题。这是因为你可以通过在自定义适配器中实现的逻辑来更改每一行中添加的视图,从而更改内容。
当getView()方法返回一个非空的视图时,这意味着对于该特定行,已经有了一个视图。因此,如果是这种情况,您可能希望或不希望更改该特定视图中的内容。或者,您可以为该特定行构建一个全新的、动态内容的视图。
需要注意的一点是,getView()将被调用和适配器中找到的项目数一样多次。

1

这里有一个想法,可能会让你能够引入任意数量的项目类型,而无需每次修改适配器:

@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)
    }
}

...然后扩展AbstractItem并向适配器添加实例,不再需要在getView()中添加任何if语句。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接