How to use, convert, or create 3rd party android/iOS custom controls in Xamarin Forms?

BGardnerBGardner USUniversity ✭✭
edited August 2017 in Xamarin.Forms

Lately I have been interested in creating custom controls for android and iOS, but also want them to be usable in Xamarin Forms. How possible is this? Does forms do some really weird stuff behind the scenes? How does it work for sizing such as in a stacklayout or grid?

As a really simple example one of the tutorials on Pluralsighti have followed for custom android controls creates a "lengthpicker" control. It consists 2 buttons and a text view.

Resources\layout\length_picker.axml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <Button
        android:id="@+id/minus_button"
        android:text="-"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/text"
        android:layout_width="96dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:textAppearance="@android:style/TextAppearance.Large" />
    <Button
        android:id="@+id/plus_button"
        android:text="+"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</merge>

LengthPicker.cs

public class LengthPicker : LinearLayout
    {
        private View mPlusButton;
        private TextView mTextView;
        private View mMinusButton;

        private int mNumInches = 0;

        public LengthPicker(Context context) :base(context)
        {
            Init();
        }

        public LengthPicker(Context context, IAttributeSet attrs): base(context, attrs)
        {
            Init();
        }

        private void Init()
        {
            LayoutInflater inflater = LayoutInflater.From(Context);
            inflater.Inflate(Resource.Layout.length_picker,this);

            mPlusButton = FindViewById<Button>(Resource.Id.plus_button);
            mTextView = FindViewById<TextView>(Resource.Id.text);
            mMinusButton = FindViewById<Button>(Resource.Id.minus_button);

            UpdateControls();
            ApplyButtonClickHandlers();

            Orientation = Orientation.Horizontal;

            this.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
        }

        private void ApplyButtonClickHandlers()
        {
            mPlusButton.Click += MPlusButton_Click;
            mMinusButton.Click += MMinusButton_Click;
        }

        private void UpdateControls()
        {
            int feet = mNumInches / 12;
            int inches = mNumInches % 12;

            string text = $"{feet}' {inches}\"";
            if(feet == 0)
            {
                text = $"{inches}\"";
            }
            else
            {
                if(inches == 0)
                {
                    text = $"{feet}'";
                }
            }

            mTextView.Text = text;

            mMinusButton.Enabled = mNumInches > 0;
        }

        private void MMinusButton_Click(object sender, EventArgs e)
        {
            mNumInches--;
            UpdateControls();
        }

        private void MPlusButton_Click(object sender, EventArgs e)
        {
            mNumInches++;
            UpdateControls();
        }
    }

How would a person convert this to a Xamarin Forms usable control? I have attempted to do so and it works (with some minor caveats, but i still dont know if i'm doing it "right"

I have a class in the PCL that extends from View.
PCL LengthPicker.cs

public class LengthPicker : Xamarin.Forms.View
{
        public static readonly BindableProperty MinusColorProperty = BindableProperty.Create("MinusColor", typeof(Color), typeof(LengthPicker), Color.Default);

        public Color MinusColor
        {
            get { return (Color)GetValue(MinusColorProperty); }
            set { SetValue(MinusColorProperty, value); }
        }
}

In the android project i have a CustomRenderer that extends
LengthPickerRenderer.cs

using ALengthPicker = MobileControls.Samples.LengthPicker; // LengthPicker in separater Android class library
using AButton = Android.Widget.Button;
using ATextView = Android.Widget.TextView;

namespace FormsMobileControlApp.Droid.Renderers
{
    public class LengthPickerRenderer :ViewRenderer<LengthPicker,ALengthPicker>
    {
        private AButton _minusButton;
        private AButton _plusButton;
        private ATextView _text;

        protected override void OnElementChanged(ElementChangedEventArgs<Controls.LengthPicker> e)
        {
            base.OnElementChanged(e);
            if (e.OldElement == null)
            {
                if (Control == null)
                    SetNativeControl(new ALengthPicker(Forms.Context));

                _minusButton = Control.FindViewById<AButton>(MobileControls.Resource.Id.minus_button);
                _text = Control.FindViewById<ATextView>(MobileControls.Resource.Id.text);
                _plusButton = Control.FindViewById<AButton>(MobileControls.Resource.Id.plus_button);
            }

            . . .

        }
    }
}

Now, like i said this works but there are some weird things that happen, and i also dont know if this is "proper".
1.) The tutorial for lengthpicker had the control XML have it's root as merge. This does not work well when using it in XF, so i changed it to be LinearLayout
2.) Having a bindable property for something like Color for the buttons is weird. In order to change the background color on the custom renderer i have to call _minusButton.SetBackground(Element.MinusColor.ToAndroid()) which makes the button appear very flat and even larger than other xamarin forms buttons

I have seen James Montemagno's blog post about custom xamarin forms controls on android but it doesn't do anything with inflating layouts, but instead overriding the draw method.

Anyone have experience in doing things like this?

Answers

Sign In or Register to comment.