ez4nick
ez4nick

Reputation: 10199

Custom adapter to make listview with different items

What I want is to have a listview with different types of items in each row of the listview. I know that I need to use a custom adapter that extends BaseAdapter and not an ArrayAdapter but after that I don't know how to approach the problem. Previously I tried making an adapter like this:

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

but when I ran the activity I got the error:

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

and I do understand why, but I don't intend on using a textView in all of my layouts. I need some guidance as to how to proceed. Thanks

Changing the background of the linearLayout parent in my item1.xml causes a null pointer exception. It does work without a null pointer exception if I leave the background as it originally was though

Upvotes: 1

Views: 3374

Answers (7)

Lyusten Elder
Lyusten Elder

Reputation: 3303

As variant - you can show or hide elements in pattern xml file by this example:

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

Upvotes: 0

Daniel
Daniel

Reputation: 869

You're quite close to your objective.

First, you should make your class extend BaseAdapter. ArrayAdapter is intended to be used to fill a single TextView per row with the toString() value of an item inside an array. The error that you are seeing seems to imply that you are passing a Layout to your ArrayAdapter constructor that doesn't just contain a TextView, and the ArrayAdapter constructor is throwing an error.

You are correctly generating the different views according to the type of view you would like to show. You should take into account the possibility of convertView not being null, that is, that the ListView is recycling older Views to consume less memory. You could add an else to the if (row == null) conditional and reuse or recreate the row accordingly.

You can check these tutorials for displaying different types of items on a ListView:

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

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

Upvotes: 1

user1888162
user1888162

Reputation: 1735

This is how i have done ListView with 2 different layout:

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;

}

Upvotes: 1

jargetz
jargetz

Reputation: 624

The reason you got this error is because ArrayAdapters require a Resource Id for a TextView that will exist in each item in the list. It will try to find a textview with this resource Id and then set it to some text based on what's in the ArrayAdapter. This however, is ignored if you override the getView() method of the adapter.

To implement what you want, there are a few approaches. If you have reusable components in your list (e.g. You different types of items you show, but you may show the same type of item multiple times), then I suggest you do something like:

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

...
}

Then you have child components that implement their own getView() method.

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

}

With this approach, you create a new class to implement IAdapterItem for each 'type' in your list. This approach is pretty clean as each child knows how to display itself properly. I wouldn't recommend this approach if you aren't using different types.

Upvotes: 2

Wind up Beanie
Wind up Beanie

Reputation: 64

I'm not sure why you are getting the error you are. I may be able to help if you give me the code that initializes the adapter. But I have my custom adapter that contains two textviews and a checkbox. So it might be able to help.

Here is the xml row file...

    <?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>

Custom adapter file...

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!

Upvotes: 1

Mark
Mark

Reputation: 5566

corsair992 is right. When you return row istead of super.getView... this could work. Just be careful when you do lists with different rows. The variable convertView will not work properly! For a row with TYPE_ITEM1 you can get a convertView of TYPE_ITEM2 and I suppose it will not look fine in your case :) You would have to check the type of convertView and when it's the one you want you can use it, otherwise you have to inflate agian.

Upvotes: 1

corsair992
corsair992

Reputation: 3070

Your ArrayAdapter implementation could have worked, but you needed to return the row View you inflated in the getView() method, instead of delegating to the super implementation.

Upvotes: 4

Related Questions