自定义 Grid-List 适配器
制作自定义 ListAdapter。
引言
在本文中,我将解释我是如何为列表活动制作自定义列表适配器的,如果您感兴趣,可以从中受益。
我需要一个与所有经典网格不同的网格,我想要两列来加载我的卡片项目,但不需要行对齐。
背景
我们将使用 BaseAdapter
和 ListView
,来制作卡片 gridView
的混合外观。
Using the Code
一、卡片项目
首先,让我们定义卡片项目示例。
它由以下部分组成:
- 标题
- Subtitle
- Logo
internal class CardItem
{
public string Title { get; set; }
public string SubTitle { get; set; }
public int ResId { get; set; }
}
其对应的布局如下:
<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>
二、卡片项目 BaseAdapter
下一步是在我们的自定义用法中实现我们的 BaseAdapter<IList<CardItem>> 。
自定义视图类似于 gridView
布局。
我创建了一个 RowView
布局,其中包含 4 个卡片布局项,并且每个卡片都有其随机定义的高度。这张截图会说明:
卡片布局将在创建时具有单个随机宽度/高度,并将被存储,以便在滚动事件中获取它们。
此行布局在 CardsViewMaker.cs 类中动态加载,GenerateView(int position)
将生成 cardItemsArray[position]
布局。
// Generate empty raw layout with no sizing and data..
public View GenerateView(int position)
{
//Call the inflater to inflate CardItem.axml Layout later and get their View..
var inflater = _context.LayoutInflater;
/* Our Row Layout: Horizontal Layout containing 2 Vertical Layout
[ E1 ][ E2 ]
[ E3 ][ E4 ]
*/
// It will be the root view element, Linear Horizontal Layout
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;
}
现在,一旦行(视图)生成(或回收),它就可以为其赋予新的随机大小(如果它是新创建的),或者在生成时将其恢复到原始大小。这在 ReSize(View view, int position)
和 SetViewInfo(View view, int id, int position)
函数中。
//used to store the generated sizes for each card item
private readonly Dictionary<int, string> _sizes = new Dictionary<int, string>();
//check if the card is alread fetched before, to give it back original generated size
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
{//random values use to generate the CardItemLayout random size...
var leftrnd = _gen.Next(30, 70);
var rightrnd = _gen.Next(30, 70);
SetSize(view, leftrnd, rightrnd);//Sizing function
_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);
}
//leftRand and rightRand are random numbers used to generate cards height
public void SetSize(View view, int leftRand, int rightRand)
{
//get the screen width to divide half half cards with minus padding...
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);
//defining layout parameters for our cards
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);
//Set the right geometric margin dimensions...
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);
//setting our layout parameters
card1.LayoutParameters = params1;
card2.LayoutParameters = params2;
card3.LayoutParameters = params3;
card4.LayoutParameters = params4;
}
//used to fill our cards with some data
private void SetViewInfo(View view, int id, int position)
{
//if the position of resultant card is higher than the data items count
// do not fill the card, hide it.
if (position >= _items.Count)
view.FindViewById(id).Visibility = ViewStates.Invisible;
else
{ // setting title, subtitle and logo
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;
}
}
现在让我们深入研究 CardItemsAdapter
,它实现了 BaseAdapter
。
重要的是要知道 override int Count
必须返回 listview
的行数。
所以,如果我们有“X
”个卡片元素,如果 X 是 4 的倍数,我们将至少有 X/4 行,否则我们将有 1 + X/4 行。
覆盖 override View GetView(int position, View convertView, ViewGroup parent)
将在滚动或获取行数据时将行视图布局返回给 listview
。
我们必须回收获取的视图,并重用它们以获得更好的性能和内存使用率。
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)
{
//check if convertView is null => Generate a new View
// if it's not null, recycle, reuse it..
var view = convertView ?? _gen.GenerateView(position);
// tell the CardViewMaker _gen to Resize and fill the card data
_gen.ReSize(view, position);
return view;
}
public override int Count
{
get
{
return _values.Count / 4 + (_values.Count % 4 == 0 ? 0 : 1);
}
}
}
最后,让我们在 ListActivity
中测试卡片适配器。
var mAdapter = CardItem.GenerateSampleCardItems();//Generate Sample Cards Data
ListAdapter = new CardItemsAdapter(this, mAdapter );
ListView.DividerHeight = 0; // Hide the divider between the rows
// it's the manifest, compile with ics,15 minimum sdk, with light theme.
// remove android:theme="@android:style/Theme.DeviceDefault.Light" for a lower version..
<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>
关注点
希望这能成为您有用的工作。谢谢。