Search

Xamarin Android ListViews–Alternating Item Layouts

Tuesday 7 October 2014

If you want your list to show alternating layouts you need to create two layouts and do a bit more work in the adapter. The source for this, and the other articles in the series is available here. The end result looks like this.
AlternatingLayout

To do this you’ll need two separate layouts, one with the image on the left and one with it on the right.
PlayerListItem1
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="25px"
    android:minHeight="25px"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/relativeLayout1"
    android:padding="4dp">
    <ImageView
        android:src="@android:drawable/ic_menu_gallery"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:id="@+id/imgRoleIcon"
        android:layout_margin="4dp" />
    <TextView
        android:text="txtName"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/imgRoleIcon"
        android:id="@+id/txtName" />
    <TextView
        android:text="txtTeam"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/txtName"
        android:id="@+id/txtTeam"
        android:layout_alignLeft="@+id/txtName" />
</RelativeLayout>

PlayerListItem2
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="25px"
    android:minHeight="25px"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/relativeLayout1"
    android:padding="4dp">
    <ImageView
        android:src="@android:drawable/ic_menu_gallery"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:id="@+id/imgRoleIcon"
        android:layout_alignParentRight="true"
        android:layout_margin="4dp" />
    <TextView
        android:text="txtName"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/txtName" />
    <TextView
        android:text="txtTeam"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/txtName"
        android:id="@+id/txtTeam"
        android:layout_alignLeft="@+id/txtName" />
</RelativeLayout>

In the adapter we need to override ViewTypeCount and GetItemViewType. ViewTypeCount just needs to return the number of views that are used.
public override int ViewTypeCount
{
    get
    {
        return 2;
    }
}

GetItemViewType needs to return an int that identifies which layout is used for an item. In this example we are alternating the views so we return 0 for odd numbered items and 1 for evens.
public override int GetItemViewType(int position)
{
    int result = 0;

    if (position % 2 == 0)
    {
        result = 1;
    }

    return result;
}

We also need to make some changes to the GetView method so that the correct layout is inflated. Android is clever about this makes sure the correct view is passed as convertView; we only need to set the layout if convertView is blank. As both layouts have the same controls we can use common code for setting them up.
public override View GetView(int position, View convertView, ViewGroup parent)
{
    View view = convertView;

    if (view == null)
    {
        switch (this.GetItemViewType(position))
        {
            case 0:
                view = this.activity.LayoutInflater.Inflate(Resource.Layout.PlayerListItem1, null);
                break;

            case 1:
                view = this.activity.LayoutInflater.Inflate(Resource.Layout.PlayerListItem2, null);
                break;
        }
        
    }

    Player player = this.data[position];

    string name = player.Name;

    if (player.Captain)
    {
        name = string.Format("{0} (c)", name);
    }

    view.FindViewById<TextView>(Resource.Id.txtName).Text = name;
    view.FindViewById<TextView>(Resource.Id.txtTeam).Text = player.Team;

    ImageView imgRoleIcon = view.FindViewById<ImageView>(Resource.Id.imgRoleIcon);

    switch (player.Role)
    {
        case Player.RoleEnum.Batsman:
            imgRoleIcon.SetImageResource(Resource.Drawable.Bat);
            break;

        case Player.RoleEnum.Bowler:
            imgRoleIcon.SetImageResource(Resource.Drawable.Ball);
            break;

        case Player.RoleEnum.WicketKeeper:
            imgRoleIcon.SetImageResource(Resource.Drawable.Wicket);
            break;

        case Player.RoleEnum.Allrounder:
            throw new NotImplementedException();
    }

    return view;
}

The complete adapter class looks like this.
private class AlternatingPlayerAdapter : BaseAdapter<Player>
{
    private Activity activity;
    private List<Player> data;

    public AlternatingPlayerAdapter(Activity activity, List<Player> data)
    {
        this.activity = activity;
        this.data = data;
    }

    public override Player this[int position]
    {
        get { return this.data[position]; }
    }

    public override int Count
    {
        get { return this.data.Count(); }
    }

    public override long GetItemId(int position)
    {
        return 0;
    }

    public override int ViewTypeCount
    {
        get
        {
            return 2;
        }
    }

    public override int GetItemViewType(int position)
    {
        int result = 0;

        if (position % 2 == 0)
        {
            result = 1;
        }

        return result;
    }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        View view = convertView;

        if (view == null)
        {
            switch (this.GetItemViewType(position))
            {
                case 0:
                    view = this.activity.LayoutInflater.Inflate(Resource.Layout.PlayerListItem1, null);
                    break;

                case 1:
                    view = this.activity.LayoutInflater.Inflate(Resource.Layout.PlayerListItem2, null);
                    break;
            }
            
        }

        Player player = this.data[position];

        string name = player.Name;

        if (player.Captain)
        {
            name = string.Format("{0} (c)", name);
        }

        view.FindViewById<TextView>(Resource.Id.txtName).Text = name;
        view.FindViewById<TextView>(Resource.Id.txtTeam).Text = player.Team;

        ImageView imgRoleIcon = view.FindViewById<ImageView>(Resource.Id.imgRoleIcon);

        switch (player.Role)
        {
            case Player.RoleEnum.Batsman:
                imgRoleIcon.SetImageResource(Resource.Drawable.Bat);
                break;

            case Player.RoleEnum.Bowler:
                imgRoleIcon.SetImageResource(Resource.Drawable.Ball);
                break;

            case Player.RoleEnum.WicketKeeper:
                imgRoleIcon.SetImageResource(Resource.Drawable.Wicket);
                break;

            case Player.RoleEnum.Allrounder:
                throw new NotImplementedException();
        }

        return view;
    }
}

1 comment:

  1. Helpful article, resonated with me from start to finish. Hire now DevRabbit, an Authorized Xamarin Partners.

    ReplyDelete