【问题标题】:Custom adapter to make listview with different items自定义适配器以使用不同的项目制作列表视图
【发布时间】:2014-01-05 00:29:38
【问题描述】:

我想要的是在列表视图的每一行中都有一个包含不同类型项目的列表视图。我知道我需要使用扩展 BaseAdapter 而不是 ArrayAdapter 的自定义适配器,但之后我不知道如何解决这个问题。以前我尝试过制作这样的适配器:

public class MyArrayAdapter<T> extends ArrayAdapter<T> {
    LayoutInflater mInflater;
    int[] mLayoutResourceIds;
    private final Context context;

    public MyArrayAdapter(Context context, int[] textViewResourceId, List<T> objects) {
        super(context, textViewResourceId[0], objects);
        this.context = context;
        mInflater = (LayoutInflater)context.getSystemService (Context.LAYOUT_INFLATER_SERVICE);
        mLayoutResourceIds = textViewResourceId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;

        int type = getItemViewType(position);
        // instead of if else you can use a case
        if (row  == null) {
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            if (type == TYPE_ITEM1) {
                //infalte layout of type1
                row = inflater.inflate(R.layout.item1, parent, false);
            }
            if (type == TYPE_ITEM2) {
                //infalte layout of type2
            }  else {
                //infalte layout of normaltype
            }
        }
        return super.getView(position, convertView, parent);
    }

    @Override
    public int getItemViewType(int position) {
        if (position== 0){
            type = TYPE_ITEM1;
        } else if  (position == 1){
            type = TYPE_ITEM2;
        }
        else
        {
            type= TYPE_ITEM3 ;
        }
        return type;    //To change body of overridden methods use File | Settings | File Templates.
    }

    @Override
    public int getViewTypeCount() {
        return 3;    //To change body of overridden methods use File | Settings | File Templates.
    }
}

但是当我运行活动时,我得到了错误:

java.lang.IllegalStateException: ArrayAdapter requires the resource ID to be a TextView

我明白为什么,但我不打算在我的所有布局中使用 textView。我需要一些关于如何进行的指导。谢谢

在我的 item1.xml 中更改 linearLayout 父级的背景会导致空指针异常。如果我保持原来的背景,它确实可以在没有空指针异常的情况下工作

【问题讨论】:

  • 您能描述一下“不同类型的项目”是什么意思吗?一般有几种方法。您可以有一个 CustomerAdapter,其中 T 是一个接口,然后每个实现类都有自己的 getView() 和用于在列表中显示元素的相关方法。
  • 意思是一行会有一个switch和一个textView,另一行会有几个imageView和一个textView,最后一个会有一个switch和一个textView
  • 哦,好吧,在这种情况下,我认为您的方法很好,只需使用 BaseAdapter 而不是 ArrayAdapter,您就不必担心 textview resourceId。

标签: java android listview android-listview


【解决方案1】:

您的 ArrayAdapter 实现本可以工作,但您需要返回您在 getView() 方法中膨胀的行 View,而不是委托给超级实现。

【讨论】:

  • 该错误意味着 ArrayAdapter 在构造时找不到作为参数提供的布局上的 TextView。即使他会直接返回视图,错误也会在调用 getView() 之前发生。
  • @corsair992 谢谢,这确实使它在没有空指针异常的情况下工作,但是我尝试为另一个列表项添加另一个布局并且它没有显示,并且在列表中滚动会导致另一个 null指针异常..
  • 另外,如果我在 item1.xml 中更改 linearLayout 父级的背景,则会导致空指针异常。如果我保持原来的背景,它确实可以在没有空指针异常的情况下工作。
  • @Daniel: ArrayAdapter 只会扩大getView() 中的布局,所以如果它被覆盖,则不会出现错误。
  • @ez4nick:您可能应该提出一个新问题,其中包含这些问题中的信息。
【解决方案2】:

您收到此错误的原因是因为 ArrayAdapters 需要一个 TextView 的资源 ID,该资源 ID 将存在于列表中的每个项目中。它将尝试使用此资源 ID 查找文本视图,然后根据 ArrayAdapter 中的内容将其设置为一些文本。但是,如果您覆盖适配器的 getView() 方法,则会忽略这一点。

要实现您想要的,有几种方法。如果您的列表中有可重复使用的组件(例如,您显示的项目类型不同,但您可能多次显示相同类型的项目),那么我建议您执行以下操作:

public class MultiTypeAdapter extends BaseAdapter {

private LayoutInflater mInflater;
private Context mContext;
private List<IAdapterItem> mItems;

public enum RowType {
    ITEM_TYPE_ONE, ITEM_TYPE_TWO
}

/**
 * @param context
 * @param itemsList
 */
private MultiTypeAdapter(Context context, List<IAdapterItem> itemsList) {
    mItems = itemsList;
    mContext = context;
    mInflater = LayoutInflater.from(context);
}

@Override
public int getViewTypeCount() {
    return RowType.values().length;
}

@Override
public int getItemViewType(int position) {
    return getItem(position).getViewType().ordinal();
}

//View is generated by children
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    return getItem(position).getView(mInflater, convertView, mContext);
}

@Override
public boolean isEnabled(int position) {
    return getItem(position).isEnabled();
}

@Override
public long getItemId(int position) {
    return getItem(position).getItemId();
}

@Override
public IAdapterItem getItem(int position) {
    return mItems.get(position); 
}

...
}

那么你就有了实现自己的getView()方法的子组件。

public class ItemTypeOne implements IAdapterItem {


@Override
public RowType getViewType() {
    return RowType.ITEM_TYPE_ONE;
}

@Override
public View getView(LayoutInflater inflater, View convertView, Context context) {
    View view = convertView;
        view = inflater.inflate(R.layout.item_type_one, null);
        // Set the view elements
        return view;
}

}

使用这种方法,您可以创建一个新类来为列表中的每个“类型”实现 IAdapterItem。这种方法非常干净,因为每个孩子都知道如何正确展示自己。如果您不使用不同的类型,我不会推荐这种方法。

【讨论】:

    【解决方案3】:

    corsair992 是对的。当您返回 row 而不是 super.getView... 这可能会起作用。当你做不同行的列表时要小心。变量 convertView 将无法正常工作!对于 TYPE_ITEM1 的一行,您可以获得 TYPE_ITEM2 的 convertView ,我想在您的情况下它看起来不太好:) 您必须检查 convertView 的类型,当它是您想要的类型时,您可以使用它,否则您必须再次膨胀。

    【讨论】:

    • 如果 getViewTypeCount()getItemViewType() 正确实现,回收不同类型的行不会有问题,就像在他的代码中一样。
    【解决方案4】:

    我不确定您为什么会遇到错误。如果您给我初始化适配器的代码,我可能会提供帮助。但我有我的自定义适配器,其中包含两个文本视图和一个复选框。所以它可能会有所帮助。

    这里是 xml 行文件...

        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="fill_parent"
            android:layout_height="?android:attr/listPreferredItemHeight"
            android:padding="6dip">
            <LinearLayout
                android:orientation="vertical"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="fill_parent">
                <TextView
                    android:id="@+id/toptext"
                    android:layout_width="fill_parent"
                    android:layout_height="0dip"
                    android:layout_weight="1"
                    android:gravity="center_vertical"
                />
                <TextView
                    android:layout_width="fill_parent"
                    android:layout_height="0dip"
                    android:layout_weight="1"
                    android:id="@+id/bottomtext"
                    android:singleLine="true"
                    android:ellipsize="marquee"
                />
            </LinearLayout>
    
            <CheckBox
                android:id="@+id/cbIsTaskCompleted"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:focusable="false"/>
    
        </LinearLayout>
    

    自定义适配器文件...

    package com.isaac.jeff;
    
    import java.util.ArrayList;
    
    import com.isaac.jeff.screen.GoalActivity;
    
    import android.content.Context;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ArrayAdapter;
    import android.widget.CheckBox;
    import android.widget.CompoundButton;
    import android.widget.CompoundButton.OnCheckedChangeListener;
    import android.widget.TextView;
    
    public class OurTasksArrayAdapter extends ArrayAdapter<Task> {
    
        private Context context;
    
        public OurTasksArrayAdapter(Context context, int textResourceId, ArrayList<Task> tasks) {
            super(context, textResourceId, tasks);
            this.context = context;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = convertView;
            if (view == null) {
                LayoutInflater layoutInflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = layoutInflater.inflate(R.layout.task_row, null);
            }
            Task task = getItem(position);
            if (task != null) {
    
                // CheckBox
                CheckBox cbIsTaskCompleted = (CheckBox) view.findViewById(R.id.cbIsTaskCompleted);
                cbIsTaskCompleted.setTag(position);
                cbIsTaskCompleted.setChecked(task.isTaskCompleted());
                cbIsTaskCompleted.setOnCheckedChangeListener(mListener);
    
                TextView tt = (TextView) view.findViewById(R.id.toptext);
                TextView bt = (TextView) view.findViewById(R.id.bottomtext);
                if (tt != null) {
                    tt.setText("Name: " + task.getTaskDescription());
                }
                if (bt != null) {
                    bt.setText("Status: " + task.getTaskDescription());
                }
            }
    
            GoalActivity ga = (GoalActivity) context;
            ga.updateTaskProgress();
    
            return view;
        }
    
        OnCheckedChangeListener mListener = new OnCheckedChangeListener() {
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 GoalActivity ga = (GoalActivity) context;
                 getItem((Integer)buttonView.getTag()).setTaskCompleted(isChecked); // get the tag so we know the row and store the status
                 ga.updateTaskProgress();
                 //notifyDataSetChanged();
             }
        };
        }
    
    Hope this helps!
    

    【讨论】:

      【解决方案5】:

      这就是我使用 2 种不同布局完成 ListView 的方式:

      public class Adapter extends BaseAdapter{
      
      private static final int VIEW_TYPE_COUNT = 2;
      private static final int ITEM_1 = 0;
      private static final int ITEM_2= 1;
      private final int VIEWTYPES[] = {Item_1, ITEM_2};
      private LayoutInflater mInflater;
      
      public Adapter(Context context){
               // Cache the LayoutInflate to avoid asking for a new one each time.
               mInflater = LayoutInflater.from(context);
      
          }
      
      @Override
              public int getItemViewType(int position) {
                   return VIEWTYPES[mPostList.get(position).getViewType()];
              }
      
      @Override
              public int getViewTypeCount(){
                  return VIEW_TYPE_COUNT;
              }
      
      
      @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;
                  ViewHolder2 holder2;
                  int type = getItemViewType(position);
      
      
                  if (convertView == null) {
                  holder = new ViewHolder();
                  holder2 = new ViewHolder2();
                      switch(type){
                      case ITEM_1:
                              convertView = mInflater.inflate(R.layout.ITEM_1_LAYOUT, null);
                              holder.text1 = (TextView)convertView.findViewById();
                              convertView.setTag(R.layout.ITEM_1_LAYOUT, holder);
                              break;
                      case ITEM_2:
                              convertView = mInflater.inflate(R.layout.iTEM_2_LAYOUT, null);
                              holder2.text2 = (TextView)convertView.findViewById();
                              convertView.setTag(R.layout.ITEM_2_LAYOUT, holder2);
                          break;
      
                      }
      
                  } else{
                      // Get the ViewHolder back to get fast access to the TextViews
                      holder = (ViewHolder) convertView.getTag(R.layout.ITEM_1_LAYOUT);
                      holder2 = (ViewHolder2)convertView.getTag(R.layout.ITEM_2_LAYOUT);
                  }
      
                  switch(type){
                  case ITEM_1:
                  holder.text1.setText("1");
                  break;
      
                  case ITEM_2:
                  holder.text2.setText("2");
                  break;
                  }
      
                    return convertView;
      }
      
      }
      
      
      static class ViewHolder {
        //Store layout1 views here
        TextView text1;
      }
      
      static class ViewHolder2{
        //Store layout2 views here
        TextView text2;
      
      }
      

      【讨论】:

        【解决方案6】:

        你已经很接近你的目标了。

        首先,您应该让您的类扩展 BaseAdapter。 ArrayAdapter 旨在用于使用数组内项目的 toString() 值填充每行的单个 TextView。您看到的错误似乎暗示您将布局传递给不只包含 TextView 的 ArrayAdapter 构造函数,而 ArrayAdapter 构造函数正在引发错误。

        您正在根据要显示的视图类型正确生成不同的视图。您应该考虑到 convertView 不为 null 的可能性,即 ListView 正在回收旧 View 以消耗更少的内存。您可以将 else 添加到 if (row == null) 条件并相应地重用或重新创建该行。

        您可以查看这些教程以在 ListView 上显示不同类型的项目:

        http://logc.at/2011/10/10/handling-listviews-with-multiple-row-types/

        http://android.amberfog.com/?p=296

        【讨论】:

          【解决方案7】:

          作为变体 - 您可以通过此示例显示或隐藏模式 xml 文件中的元素:

              if (position<1) { imageView.setImageResource(R.drawable.ok); }
              else { imageView.setImageResource(R.drawable.no);
              imageView.setVisibility(View.GONE); }
          

          【讨论】:

            猜你喜欢
            • 2020-03-14
            • 2015-12-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-14
            • 2011-12-31
            相关资源
            最近更新 更多