Reputation: 10199
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
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
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
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
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
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
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
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