0

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 the ListView 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.

auhmaan
  • 726
  • 1
  • 8
  • 28
  • Based on your ASCII sample, this looks like it might be grouped with other similar members(i.e. JcPennys, Macys, etc)? Thus I believe this would be best as a custom listview/recyclerview row: https://developer.xamarin.com/guides/android/user_interface/working_with_listviews_and_adapters/part_3_-_customizing_a_listview's_appearance/#Creating_Custom_Row_Layouts Otherwise you can see some documentation on custom controls here: http://developer.android.com/guide/topics/ui/custom-components.html – Jon Douglas Apr 06 '16 at 16:17
  • It seems that the `Custom Row Layouts` you mentioned is what I'm looking for. I'll give it a try then I'll post here something with the result. – auhmaan Apr 06 '16 at 16:23

4 Answers4

1

It looks like you're after creating a Listview or RecyclerView to house a list of "Store" items based on your ASCII sample.

ListView with custom row: https://developer.xamarin.com/guides/android/user_interface/working_with_listviews_and_adapters/part_3_-_customizing_a_listview's_appearance/#Creating_Custom_Row_Layouts

RecyclerView with custom card: http://developer.android.com/training/material/lists-cards.html

If you need something a bit more custom, you can always make your own custom View:

http://developer.android.com/guide/topics/ui/custom-components.html

Whether or not it goes in an Activity or Fragment; there are many other SO questions regarding when you should use one over the other:

Dilemma: when to use Fragments vs Activities:

Community
  • 1
  • 1
Jon Douglas
  • 13,006
  • 4
  • 38
  • 51
0

I don't know how to do it with Android Xamarin, I think it is similar to Android Studio. In this case, 1st think you will have to do is create a LinearLayout which have a ImageView and another LinearLayout which have three TextFields.

For the codebehind don't know what are you asking, remember to put id on all the elements in the XML code to later find by id.

0

You would be looking to create a Fragment, unless these will be in a list. In that case, you would use the RecyclerView, ViewHolder pattern, and a layout. If it's possible you could use it in both, then create the layout once, and you can use it as the layout for a Fragment and as a view holder layout.

Ryan Alford
  • 7,514
  • 6
  • 42
  • 56
0

An Activity is a full page (layout + code behind). It is always full screen, but can have transparency.

A Fragment is a user control (layout + code behind). You can use it in another activity, another fragment or by code. It can have any shape.

When creating a fragment, the editor will show a full phone view. But in reality the space in which the fragment will be put is defined by the parent layout, i.e. the layout where you added the fragment.

You can also write custom views by inheriting from the View class (or any other control). A custom view is always written in code (no layout).

Another option is to use the "include" tag in your layout. It adds another layout inside this layout. The drawback is that there will be no specific code behind for the included part, the owner's code behind is in charge of using the included content.

Hope it helps.

Softlion
  • 12,281
  • 11
  • 58
  • 88