I've been reading tutorials and articles on how to create a sort of UserControl
for Android Xamarin and I still have no clue on how to do it due to the poor explanation of those tutorials/articles or due to the lack of examples.
What I need is to create an UserControl
that will hold some info about a store ( an image, store name, store address, store contact, and a few more details, but these should be enough to build a sample here ).
ASCII Sample:
+-----------------------------------+
| +----------+ |
| |/ / / / / | STORE NAME |
| | / / / / /| \ |
| |/ / / / / | STORE ADDRESS > |
| | / / / / /| / |
| |/ / / / / | STORE CONTACT |
| +----------+ |
+-----------------------------------+
Now, how would I build this on Android Xamarin? I get that, from the tutorials I've seen, I need a layout and a code-behind (duh!), but exactly which one - Activity
or Fragment
? Even though in the Visual Studio when I'm editing the layout file it shows the layout within a phone frame that doesn't mean that I'm building an page layout rather that the UserControl
layout?
Solution found
With the answer given by Jon Douglas, I was able to do what I wanted.
I'll leave here the sample code necessary to replicate the layout I've made above and some explanation - maybe it can help someone later.
To the store buttons list I needed to create 4 files:
- The
Button
layout template, which would hold the structure of the button; - The
ListView
adapter, which will contain the code necessary to the initialization of theListView
and events; - The Stores layout, which will have basically only a
ListView
that will be populated with a list of Stores; - The Stores fragment, which will have the logic when this specific fragment is used.
Although I'm using fragments, I don't think this would be that much difference between an sample using activities.
Button Layout Template
<?xml version="1.0" encoding="utf-8"?>
<!-- The root of the layout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp" >
<!-- Needed to add the elements side-by-side,
for some reason didn't work to me setting
the `orientation` on the root layout -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp" >
<!-- The store image -->
<ImageView
android:id="@+id/StoreImage"
android:layout_gravity="top"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="55"
android:paddingTop="0dp" />
<!-- This `LinearLayout` will hold the store name,
address and contact vertically -->
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="100"
android:layout_marginLeft="15px" >
<!-- The store name -->
<TextView
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFDCBB73"
android:id="@+id/StoreName"
android:layout_marginBottom="5dp" />
<!-- The store address -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- Store address label -->
<TextView
android:text="@string/AddressText"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="#FF9D9D9D"
android:layout_marginRight="5dp" />
<!-- Store address value -->
<TextView
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="#FF9D9D9D"
android:textStyle="bold"
android:id="@+id/StoreAddress" />
</LinearLayout>
<!-- The store contact -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- Store contact label -->
<TextView
android:text="@string/ContactsText"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="#FF9D9D9D"
android:layout_marginRight="5dp" />
<!-- Store contact value -->
<TextView
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="#FF9D9D9D"
android:textStyle="bold"
android:id="@+id/StoreContacts" />
</LinearLayout>
</LinearLayout>
<ImageView
android:src="@drawable/arrowright"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="5dp"
android:paddingRight="5dp"
android:paddingLeft="5dp"
android:id="@+id/StoreEntry_ViewStore" />
</LinearLayout>
</LinearLayout>
ListView Adapter
using Android.App;
using Android.Graphics;
using Android.Views;
using Android.Widget;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Project {
public class ListViewAdapter : BaseAdapter<StoreObject> {
// Properties
private Activity Context {
get; set;
}
private List<StoreObject> StoresList {
get; set;
}
// Methods
public override Int32 Count {
get {
return StoresList.Count;
}
}
public override Int64 GetItemId( Int32 position ) {
return position;
}
public override StoreObject this[ Int32 position ] {
get {
return StoresList[ position ];
}
}
public override View GetView( Int32 position, View convertView, ViewGroup parent ) {
StoreObject
store = StoresList[ position ];
View
view = convertView ?? Context.LayoutInflater.Inflate( Resource.Layout.StoreEntry, null );
// Set the store image
Double
imageWidth = Context.Resources.DisplayMetrics.WidthPixels / Context.Resources.DisplayMetrics.Density * (3.0 / 7.0);
Bitmap
storeBitmap = Utils.GetBitmap( store.Image, (Int32) imageWidth );
view.FindViewById<ImageView>( Resource.Id.StoreImage ).SetImageBitmap( storeBitmap );
// Set the store name
view.FindViewById<TextView>( Resource.Id.StoreName ).SetText( store.Name, TextView.BufferType.Normal );
// Set the store schedule, whether is open or closed
String
storeSchedule = store.isOpen ?
Context.Resources.GetString( Resource.String.OpenText ) :
Context.Resources.GetString( Resource.String.ClosedText );
view.FindViewById<TextView>( Resource.Id.StoreSchedule ).SetText( storeSchedule, TextView.BufferType.Normal );
// Set the store address
String
storeLocation = Context.Resources.GetString( Resource.String.NotAvailableText );
if (store.Address != null) {
storeLocation = store.Address.City;
}
view.FindViewById<TextView>( Resource.Id.StoreAddress ).SetText( storeLocation, TextView.BufferType.Normal );
// Set the store contact
String
storeContact = Context.Resources.GetString( Resource.String.NotAvailableText );
if (store.contacts != null && store.contacts.Count > 0) {
storeContact = store.contacts.FirstOrDefault().PhoneNumber.ToString();
}
view.FindViewById<TextView>( Resource.Id.StoreContacts ).SetText( storeContact, TextView.BufferType.Normal );
return view;
}
// Constructors
public ListViewAdapter( Activity context, List<StoreObject> items ) : base() {
Context = context;
StoresList = items;
}
}
}
Stores Fragment Layout
<?xml version="1.0" encoding="utf-8"?>
<ListView 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/StoresHolder"
android:listSelector="#7FDCBB73" />
Stores Fragment
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Android.App;
using Android.OS;
using Android.Views;
using Android.Widget;
namespace Project {
public class Stores : Fragment {
// Properties
private List<StoreObject> StoresList {
get; set;
}
private View CurrentView {
get; set;
}
private ListView StoresHolder {
get {
return CurrentView.FindViewById<ListView>( Resource.Id.StoresHolder );
}
}
// Events
public override void OnCreate( Bundle savedInstanceState ) {
base.OnCreate( savedInstanceState );
}
public override View OnCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) {
base.OnCreateView( inflater, container, savedInstanceState );
CurrentView = inflater.Inflate( Resource.Layout.Stores, container, false );
if (StoresList == null) {
StoresList = GetStores();
}
StoresHolder.ItemClick += ItemClick;
StoresHolder.Adapter = new ListViewAdapter( Activity, StoresList );
return CurrentView;
}
private void ItemClick( Object sender, AdapterView.ItemClickEventArgs e ) {
// Do stuff...
}
}
}
I hope that this can help someone in the future.