Introduction
In this article, I will explain how I made my custom list-adapter for a list activity, to take benefits if you are interested in.
I needed a grid that looks different from all classic ones, I wanted two columns to load my Card Items but with no row alignment:
Background
We will use the BaseAdapter
and ListView
, to make the hybrid look of the Cards gridView
.
Using the Code
I. CardItem
First of all, let's define the Card Item sample.
It is composed of:
internal class CardItem
{
public string Title { get; set; }
public string SubTitle { get; set; }
public int ResId { get; set; }
}
Its corresponding layout is as follows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_margin="16dp"
android:layout_height="wrap_content"
android:background="@drawable/card_background">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingRight="4dp"
android:paddingLeft="4dp"
android:text="Title"
android:textSize="18dp"
android:textColor="@android:color/black"
android:id="@+id/card_title" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingRight="6dp"
android:paddingLeft="6dp"
android:text="Subtitle"
android:textSize="14dp"
android:id="@+id/card_subtitle"
android:textColor="#6D7B8D" />
<ImageView
android:id="@+id/card_logo"
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:background="@drawable/custom_background"
android:layout_gravity="center"
android:src="@drawable/android"
android:scaleType="centerInside"
android:padding="5dp" />
</LinearLayout>
II. Card Item BaseAdapter
The next step is to implement our BaseAdapter<IList<CardItem>> in our custom usage.
The custom view is similar to a gridView
layout.
I created a RowView
layout containing 4 cards layout items, and each card of them will have its random defined height. this screen shot will explain:
Cards Layouts will have a single random width/height at creation, and will be stored, to get them back on scroll events.
This row layout is loaded dynamically in the CardsViewMaker.cs class, GenerateView(int position)
will generate the cardItemsArray[position]
layout.
public View GenerateView(int position)
{
var inflater = _context.LayoutInflater;
var rootView = new LinearLayout(_context)
{
Orientation = Orientation.Horizontal
};
var rootParams = new AbsListView.LayoutParams
(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
rootView.LayoutParameters = rootParams;
var card1 = (LinearLayout)inflater.Inflate(Resource.Layout.CardItem, null);
var card2 = (LinearLayout)inflater.Inflate(Resource.Layout.CardItem, null);
var card3 = (LinearLayout)inflater.Inflate(Resource.Layout.CardItem, null);
var card4 = (LinearLayout)inflater.Inflate(Resource.Layout.CardItem, null);
card1.Id = 1;
card2.Id = 2;
card3.Id = 3;
card4.Id = 4;
var leftView = new LinearLayout(_context)
{
Orientation = Orientation.Vertical
};
var leftParams = new AbsListView.LayoutParams
(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
leftView.LayoutParameters = leftParams;
leftView.AddView(card1);
leftView.AddView(card2);
var rightView = new LinearLayout(_context)
{
Orientation = Orientation.Vertical
};
var rightParams = new AbsListView.LayoutParams
(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
rightView.LayoutParameters = rightParams;
rightView.AddView(card3);
rightView.AddView(card4);
rootView.AddView(leftView);
rootView.AddView(rightView);
rootView.SetBackgroundColor(Android.Graphics.Color.ParseColor("#FFE5E5E5"));
return rootView;
}
Now once the row (view) is generated (or recycled), it's ready to give it its new random size if it's newly created, or give it back its original size on generation, it's in the ReSize(View view, int position)
and SetViewInfo(View view, int id, int position)
functions:
private readonly Dictionary<int, string> _sizes = new Dictionary<int, string>();
public void ReSize(View view, int position)
{
if (_sizes.ContainsKey(position))
{
var gens = _sizes[position].Split(' ').Select(int.Parse);
var enumerable = gens as int[] ?? gens.ToArray();
SetSize(view, enumerable.ElementAt(0), enumerable.ElementAt(1));
}
else
{
var leftrnd = _gen.Next(30, 70);
var rightrnd = _gen.Next(30, 70);
SetSize(view, leftrnd, rightrnd);
_sizes.Add(position, string.Format("{0} {1}", leftrnd, rightrnd));
}
var id = 4*position;
SetViewInfo(view, 1, id);
SetViewInfo(view, 3, id+1);
SetViewInfo(view, 2, id+2);
SetViewInfo(view, 4, id+3);
}
public void SetSize(View view, int leftRand, int rightRand)
{
var width = _context.WindowManager.DefaultDisplay.Width;
var card1 = view.FindViewById<LinearLayout>(1);
var card2 = view.FindViewById<LinearLayout>(2);
var card3 = view.FindViewById<LinearLayout>(3);
var card4 = view.FindViewById<LinearLayout>(4);
var bh = DefaultHeight + Pad;
var bh1 = (int)(bh * leftRand / 100.0f - Pad / 2.0f);
var bh2 = (int)(bh * (1 - leftRand / 100.0f) - Pad / 2.0f);
var params1 = new LinearLayout.LayoutParams(width / 2 - 3 * Pad / 4, bh1);
var params2 = new LinearLayout.LayoutParams(width / 2 - 3 * Pad / 4, bh2);
bh1 = (int)(bh * rightRand / 100.0f - Pad / 2.0f);
bh2 = (int)(bh * (1 - rightRand / 100.0f) - Pad / 2.0f);
var params3 = new LinearLayout.LayoutParams(width / 2 - 3 * Pad / 4, bh1);
var params4 = new LinearLayout.LayoutParams(width / 2 - 3 * Pad / 4, bh2);
params1.SetMargins(Pad / 2, Pad / 4, 0, Pad / 4);
params2.SetMargins(Pad / 2, Pad / 4, 0, 0);
params3.SetMargins(Pad / 2, Pad / 2, 0, Pad / 4);
params4.SetMargins(Pad / 2, Pad / 4, 0, 0);
card1.LayoutParameters = params1;
card2.LayoutParameters = params2;
card3.LayoutParameters = params3;
card4.LayoutParameters = params4;
}
private void SetViewInfo(View view, int id, int position)
{
if (position >= _items.Count)
view.FindViewById(id).Visibility = ViewStates.Invisible;
else
{
var card = _items[position];
view.FindViewById(id).FindViewById<TextView>
(Resource.Id.card_title).Text = card.Title;
view.FindViewById(id).FindViewById<TextView>
(Resource.Id.card_subtitle).Text = card.SubTitle;
view.FindViewById(id).FindViewById<ImageView>
(Resource.Id.card_logo).SetImageResource(card.ResId);
view.FindViewById(id).Visibility = ViewStates.Visible;
}
}
Now let's dig into the CardItemsAdapter
that implements the BaseAdapter
.
It is important to know that override int Count
must returns the rows count for the listview
.
So if we have "X
" card elements, we will have at least X/4 rows if X is a multiple of 4, else we'll have 1 + X/4 rows.
The override override View GetView(int position, View convertView, ViewGroup parent)
will return the row view layout to the listview
when scrolling or fetching rows data.
We must recycle fetched views, and reuse them for better performance and memory usage.
internal class CardItemsAdapter : BaseAdapter<CardItem>
{
private readonly IList<CardItem> _values;
private readonly CardsViewMaker _gen;
public CardItemsAdapter(Activity context, IList<CardItem> values)
{
_gen = new CardsViewMaker(context,values);
_values = values;
}
public CardItemsAdapter(Activity context,int height, int spacing, IList<CardItem> values)
{
_gen = new CardsViewMaker(context,height, spacing, values);
_values = values;
}
public override CardItem this[int position]
{
get { return _values[position]; }
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var view = convertView ?? _gen.GenerateView(position);
_gen.ReSize(view, position);
return view;
}
public override int Count
{
get
{
return _values.Count / 4 + (_values.Count % 4 == 0 ? 0 : 1);
}
}
}
Finally, let's test the Cards adapter in a ListActivity
:
var mAdapter = CardItem.GenerateSampleCardItems();
ListAdapter = new CardItemsAdapter(this, mAdapter );
ListView.DividerHeight = 0;
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1" android:versionName="1.0" package="CustomAdapter.CustomAdapter">
<uses-sdk android:targetSdkVersion="15" android:minSdkVersion="15" />
<application android:theme="@android:style/Theme.DeviceDefault.Light"
android:label="CustomAdapter" android:icon="@drawable/icon"></application>
</manifest>
Points of Interest
Hope it's a handy work that you will use. Thanks.