Dbl
Dbl

Reputation: 5894

How to render a xamarin.forms view in a custom renderer

I've got the following code:

Shared project:

using System;
using Xamarin.Forms;

namespace XamarinForms.Framework.Controls
{
       public enum DrawerPosition
       {
             Left,
             Right
       }

       public interface ISideDrawerNativeEventProxy
       {
             void RaiseSlideChanged(float percentage);
       }

       public class SideDrawer : Grid, ISideDrawerNativeEventProxy
       {
             #region DrawerSize

             public static BindableProperty DrawerSizeProperty = BindableProperty.Create<SideDrawer, MaxedPercentSize>(d => d.DrawerSize, new MaxedPercentSize(MaxedPercentSizeType.Width, 80, 400));

             public MaxedPercentSize DrawerSize
             {
                    get { return (MaxedPercentSize) GetValue(DrawerSizeProperty); }
                    set { SetValue(DrawerSizeProperty, value); }
             }

             #endregion DrawerSize

             #region IsOpen

             public static BindableProperty IsOpenProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsOpen, default(bool));

             public bool IsOpen
             {
                    get { return (bool) GetValue(IsOpenProperty); }
                    set { SetValue(IsOpenProperty, value); }
             }

             #endregion IsOpen

             #region DrawerPosition

             public static BindableProperty DrawerPositionProperty = BindableProperty.Create<SideDrawer, DrawerPosition>(d => d.DrawerPosition, default(DrawerPosition));

             public DrawerPosition DrawerPosition
             {
                    get { return (DrawerPosition) GetValue(DrawerPositionProperty); }
                    set { SetValue(DrawerPositionProperty, value); }
             }

             #endregion DrawerPosition

             public static BindableProperty MainContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.MainContent, default(View));

             public View MainContent
             {
                    get { return (View) GetValue(MainContentProperty); }
                    set { SetValue(MainContentProperty, value); }
             }

             public static BindableProperty DrawerContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.DrawerContent, default(View));

             public View DrawerContent
             {
                    get { return (View) GetValue(DrawerContentProperty); }
                    set { SetValue(DrawerContentProperty, value); }
             }

             #region DrawerLength

             public static BindableProperty DrawerLengthProperty = BindableProperty.Create<SideDrawer, int>(d => d.DrawerLength, default(int), defaultValueCreator: DrawerLengthDefault);

             private static int DrawerLengthDefault(SideDrawer bindable)
             {
                    return 300;
             }

             public int DrawerLength
             {
                    get { return (int) GetValue(DrawerLengthProperty); }
                    set { SetValue(DrawerLengthProperty, value); }
             }

             #endregion DrawerLength

             #region IsContentTranslated

             public static BindableProperty IsContentTranslatedProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsContentTranslated, true);

             public bool IsContentTranslated
             {
                    get { return (bool) GetValue(IsContentTranslatedProperty); }
                    set { SetValue(IsContentTranslatedProperty, value); }
             }

             #endregion IsContentTranslated

             void ISideDrawerNativeEventProxy.RaiseSlideChanged(float percentage)
             {
             }
       }
}

Android:

using System;
using System.ComponentModel;
using Android.Support.V4.Widget;
using Android.Views;
using Android.Widget;
using Mobile.XamarinForms.Droid.Controls.SideDrawer;
using Mobile.XamarinForms.Framework.Controls;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Graphics;
using Application = Android.App.Application;
using Color = Android.Graphics.Color;
using RelativeLayout = Android.Widget.RelativeLayout;
using View = Android.Views.View;

[assembly: ExportRenderer(typeof(SideDrawer), typeof(SideDrawerRenderer))]
namespace Mobile.XamarinForms.Droid.Controls.SideDrawer
{
//     public class SideDrawerRenderer : Xamarin.Forms.Platform.Android.AppCompat.ViewRenderer<Framework.Controls.SideDrawer, DrawerLayout>, DrawerLayout.IDrawerListener
       public class SideDrawerRenderer : ViewRenderer<Framework.Controls.SideDrawer, DrawerLayout>, DrawerLayout.IDrawerListener
       {
             private DrawerLayout _nativeDrawerLayout;
             private MarginLayoutParams _contentLayoutParameters;
             private RelativeLayout _contentView;
             private TextView _drawerView;

             protected override void OnElementChanged(ElementChangedEventArgs<Framework.Controls.SideDrawer> e)
             {
                    base.OnElementChanged(e);

                    if (this.Control == null)
                    {
                           InitializeNativeControl();
                    }
                    if (e.OldElement != null)
                    {
                           _nativeDrawerLayout.SetDrawerListener(null);
            }
                    if (e.NewElement != null)
                    {
                           _nativeDrawerLayout.SetDrawerListener(this);
                    }
             }

             private void InitializeNativeControl()
             {
                    _nativeDrawerLayout = new DrawerLayout(Context.ApplicationContext);
                    _nativeDrawerLayout.SetBackgroundColor(Element.BackgroundColor.ToAndroid());


                    _contentLayoutParameters = new MarginLayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
                    var layoutParamsDrawer = new DrawerLayout.LayoutParams(Element.DrawerLength, LinearLayout.LayoutParams.MatchParent);
                    layoutParamsDrawer.Gravity = GetDrawerGravity();

                    _drawerView = GetDrawerView(layoutParamsDrawer);
                    _contentView = GetContentView(_contentLayoutParameters);

                    // this one works, but i need the content from my forms property
                    var contentChild = new RelativeLayout(Context.ApplicationContext);
                    var contentChildLParams = new RelativeLayout.LayoutParams(300, 300);
                    contentChild.SetBackgroundColor(Color.Yellow);
                    _contentView.AddView(contentChild, contentChildLParams);

                    // i need to figure out how to make this work
//                  var contentRenderer = RendererFactory.GetRenderer(Element.MainContent);
//                  _contentView.AddView(contentRenderer.ViewGroup, new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent));

                    _nativeDrawerLayout.AddView(_drawerView);
                    _nativeDrawerLayout.AddView(_contentView);

                    SetNativeControl(_nativeDrawerLayout);
             }

             private int GetDrawerGravity()
             {
                    switch (Element.DrawerPosition)
                    {
                           case DrawerPosition.Left:
                                  return (int)GravityFlags.Start;
                           case DrawerPosition.Right:
                                  return (int)GravityFlags.End;
                           default:
                                  throw new ArgumentOutOfRangeException();
                    }
             }

             private RelativeLayout GetContentView(LayoutParams layoutParameters)
             {
                    var view = new RelativeLayout(Context.ApplicationContext);
                    view.LayoutParameters = layoutParameters;
                    view.SetBackgroundColor(Color.Red);
                    return view;
             }

             private TextView GetDrawerView(LayoutParams layoutParameters)
             {
                    var view = new TextView(Context.ApplicationContext);
                    view.LayoutParameters = layoutParameters;
                    view.SetBackgroundColor(Color.Purple);
                    view.SetTextColor(Color.Blue);
                    view.SetText("just some text", TextView.BufferType.Editable);
                    return view;
             }

             protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
             {
                    base.OnElementPropertyChanged(sender, e);

                    switch (e.PropertyName)
                    {
                           case nameof(Framework.Controls.SideDrawer.Height):
                                  break;
                           case nameof(Framework.Controls.SideDrawer.Width):
                                  break;
                    }
             }

             public void OnDrawerClosed(View drawerView)
             {
                    Element.IsOpen = false;
                    System.Diagnostics.Debug.WriteLine("OnDrawerClosed");
             }

             public void OnDrawerOpened(View drawerView)
             {
                    Element.IsOpen = true;
                    System.Diagnostics.Debug.WriteLine("OnDrawerOpened");
             }

             public void OnDrawerSlide(View drawerView, float slideOffset)
             {
                    switch (Element.DrawerPosition)
                    {
                           case DrawerPosition.Left:
                                  _contentView.TranslationX = (int) Math.Abs(Element.DrawerLength*slideOffset);
                                  break;
                           case DrawerPosition.Right:
                                  _contentView.TranslationX = (int) Math.Abs(Element.DrawerLength*slideOffset)*-1;
                                  break;
                           default:
                                  throw new ArgumentOutOfRangeException();
                    }

                    _nativeDrawerLayout.BringChildToFront(_drawerView);
                    _nativeDrawerLayout.RequestLayout();

                    ((ISideDrawerNativeEventProxy) Element)?.RaiseSlideChanged(slideOffset);
             }

             public void OnDrawerStateChanged(int newState)
             {
                    // not really needed
//                  System.Diagnostics.Debug.WriteLine($"OnDrawerStateChanged {newState}");
             }
       }
}

How do i output the contents of e.g. MainContent (Shared project property)?

I was unable to find anything in xamarin docs about this and support is rather quiet about this topic so far (guess they are too busy).

Does anyone have experience with this issue?

Update: Reproduction solution

Upvotes: 3

Views: 2557

Answers (1)

Dbl
Dbl

Reputation: 5894

Android container:

internal sealed class FormsElementWrapper : FormsViewGroup
{
        public override bool OnInterceptTouchEvent(MotionEvent ev)
    {
        return false;
    }

    private readonly IVisualElementRenderer _view;

    public FormsElementWrapper(Xamarin.Forms.View content) : base(Application.Context)
    {
        _view = content != null ? Platform.CreateRenderer(content) : null;
        if (_view == null)
            return;
        AddView(_view.ViewGroup);
    }

    protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
    {
        if (_view == null)
            return;
        _view.Element.Layout(new Rectangle(0.0, 0.0, ContextExtensions.FromPixels(Context, right - left), ContextExtensions.FromPixels(Context, bottom - top)));
        _view.UpdateLayout();
    }

    protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        MeasureSpecMode widthMode = MeasureSpec.GetMode(widthMeasureSpec);
        MeasureSpecMode heightMode = MeasureSpec.GetMode(heightMeasureSpec);
        int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
        int heightSize = MeasureSpec.GetSize(heightMeasureSpec);
        int pxHeight = (int) ContextExtensions.ToPixels(Context, _view.Element.HeightRequest);
        int pxWidth = (int) ContextExtensions.ToPixels(Context, _view.Element.WidthRequest);
        var measuredWidth = widthMode != MeasureSpecMode.Exactly ? (widthMode != MeasureSpecMode.AtMost ? pxHeight : Math.Min(pxHeight, widthSize)) : widthSize;
        var measuredHeight = heightMode != MeasureSpecMode.Exactly ? (heightMode != MeasureSpecMode.AtMost ? pxWidth : Math.Min(pxWidth, heightSize)) : heightSize;
        SetMeasuredDimension(measuredWidth, measuredHeight);
    }
}

android drawer:

using System;
using System.ComponentModel;
using Android.Support.V4.Widget;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Color = Android.Graphics.Color;
using RelativeLayout = Android.Widget.RelativeLayout;
using View = Android.Views.View;

[assembly: ExportRenderer(typeof(SideDrawer), typeof(SideDrawerRenderer))]
namespace MyNamespace.SideDrawer
{
    public class SideDrawerRenderer : ViewRenderer<SideDrawer, DrawerLayout>, DrawerLayout.IDrawerListener
    {
        private DrawerLayout _nativeDrawerLayout;
        private FormsElementWrapper _contentView;
        private FormsElementWrapper _drawerView;

        protected override void OnElementChanged(ElementChangedEventArgs<Framework.Controls.SideDrawer> e)
        {
            base.OnElementChanged(e);

            if (this.Control == null)
            {
                InitializeNativeControl();
            }
            if (e.OldElement != null)
            {
                _nativeDrawerLayout.SetDrawerListener(null);
            }
            if (e.NewElement != null)
            {
                _nativeDrawerLayout.SetDrawerListener(this);
            }
        }

        private void InitializeNativeControl()
        {
            _nativeDrawerLayout = new DrawerLayout(Context.ApplicationContext);
            _nativeDrawerLayout.SetBackgroundColor(Element.BackgroundColor.ToAndroid());

            AddDrawerLayer(_nativeDrawerLayout);
            AddContentLayer(_nativeDrawerLayout);

            SetNativeControl(_nativeDrawerLayout);
        }

        private void AddContentLayer(DrawerLayout nativeDrawerLayout)
        {
            _contentView = new FormsElementWrapper(Element.MainContent);

            nativeDrawerLayout.AddView(_contentView);
        }

        private void AddDrawerLayer(DrawerLayout nativeDrawerLayout)
        {
            _drawerView = new FormsElementWrapper(Element.DrawerContent);
            UpdateDrawerLength();

            nativeDrawerLayout.AddView(_drawerView);
        }

        private int GetDrawerGravity()
        {
            switch (Element.DrawerPosition)
            {
                case DrawerPosition.Left:
                    return (int)GravityFlags.Start;
                case DrawerPosition.Right:
                    return (int)GravityFlags.End;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            switch (e.PropertyName)
            {
                case nameof(Framework.Controls.SideDrawer.Height):
                    break;
                case nameof(Framework.Controls.SideDrawer.Width):
                    break;
                case nameof(Framework.Controls.SideDrawer.MainContent):
                    _contentView = new FormsElementWrapper(Element.MainContent);
                    break;
                case nameof(Framework.Controls.SideDrawer.DrawerContent):
                    _drawerView = new FormsElementWrapper(Element.DrawerContent);
                    break;
                case nameof(Framework.Controls.SideDrawer.IsOpen):
                    UpdateDrawerStateProgramatically();
                    break;
                case nameof(Framework.Controls.SideDrawer.DrawerLength):
                case nameof(Framework.Controls.SideDrawer.DrawerPosition):
                    UpdateDrawerLength();
                    break;
            }
        }

        private void UpdateDrawerLength()
        {
            var layoutParamsDrawer = new DrawerLayout.LayoutParams(Element.DrawerLength, ViewGroup.LayoutParams.MatchParent);
            layoutParamsDrawer.Gravity = GetDrawerGravity();
            _drawerView.LayoutParameters = layoutParamsDrawer;
        }

        private void UpdateDrawerStateProgramatically()
        {
            if (Element.IsOpen)
            {
                Control.OpenDrawer(GetDrawerGravity());
            }
            else
            {
                Control.CloseDrawer(GetDrawerGravity());
            }
        }

        public void OnDrawerClosed(View drawerView)
        {
            Element.IsOpen = false;
            System.Diagnostics.Debug.WriteLine("OnDrawerClosed");
        }

        public void OnDrawerOpened(View drawerView)
        {
            Element.IsOpen = true;
            System.Diagnostics.Debug.WriteLine("OnDrawerOpened");
        }

        public void OnDrawerSlide(View drawerView, float slideOffset)
        {
            switch (Element.DrawerPosition)
            {
                case DrawerPosition.Left:
                    _contentView.TranslationX = (int)Math.Abs(Element.DrawerLength * slideOffset);
                    break;
                case DrawerPosition.Right:
                    _contentView.TranslationX = (int)Math.Abs(Element.DrawerLength * slideOffset) * -1;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            _nativeDrawerLayout.BringChildToFront(_drawerView);
            _nativeDrawerLayout.RequestLayout();

            ((ISideDrawerNativeEventProxy)Element)?.RaiseSlideChanged(slideOffset);
        }

        public void OnDrawerStateChanged(int newState)
        {
        }
    }
}

Xamarin.Forms Control:

public enum DrawerPosition
{
    Left,
    Right
}

public interface ISideDrawerNativeEventProxy
{
    void RaiseSlideChanged(float percentage);
}

public class SideDrawer : Grid, ISideDrawerNativeEventProxy
{
    #region IsOpen

    public static BindableProperty IsOpenProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsOpen, default(bool), defaultBindingMode: BindingMode.TwoWay);

    public bool IsOpen
    {
        get { return (bool) GetValue(IsOpenProperty); }
        set { SetValue(IsOpenProperty, value); }
    }

    #endregion IsOpen

    #region DrawerPosition

    public static BindableProperty DrawerPositionProperty = BindableProperty.Create<SideDrawer, DrawerPosition>(d => d.DrawerPosition, default(DrawerPosition));

    public DrawerPosition DrawerPosition
    {
        get { return (DrawerPosition) GetValue(DrawerPositionProperty); }
        set { SetValue(DrawerPositionProperty, value); }
    }

    #endregion DrawerPosition

    public static BindableProperty MainContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.MainContent, default(View), propertyChanging: MainContentPropertyChanging, propertyChanged: MainContentPropertyChanged);

    private static void MainContentPropertyChanged(BindableObject bindable, View oldValue, View newValue)
    {
        if (newValue == null)
            return;
        newValue.Parent = (View)bindable;
    }

    private static void MainContentPropertyChanging(BindableObject bindable, View oldValue, View newValue)
    {
        if (oldValue == null)
            return;
        oldValue.Parent = null;
    }

    public View MainContent
    {
        get { return (View) GetValue(MainContentProperty); }
        set { SetValue(MainContentProperty, value); }
    }

    public static BindableProperty DrawerContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.DrawerContent, default(View), propertyChanging: DrawerContentPropertyChanging, propertyChanged: DrawerContentPropertyChanged);

    private static void DrawerContentPropertyChanged(BindableObject bindable, View oldValue, View newValue)
    {
        if (newValue == null)
            return;
        newValue.Parent = (View)bindable;
    }

    private static void DrawerContentPropertyChanging(BindableObject bindable, View oldValue, View newValue)
    {
        if (oldValue == null)
            return;
        oldValue.Parent = null;
    }

    public View DrawerContent
    {
        get { return (View) GetValue(DrawerContentProperty); }
        set { SetValue(DrawerContentProperty, value); }
    }

    #region DrawerLength

    public static BindableProperty DrawerLengthProperty = BindableProperty.Create<SideDrawer, int>(d => d.DrawerLength, default(int), defaultValueCreator: DrawerLengthDefault);

    private static int DrawerLengthDefault(SideDrawer bindable)
    {
        return 300;
    }

    public int DrawerLength
    {
        get { return (int) GetValue(DrawerLengthProperty); }
        set { SetValue(DrawerLengthProperty, value); }
    }

    #endregion DrawerLength

    #region IsContentTranslated

    public static BindableProperty IsContentTranslatedProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsContentTranslated, true);

    public bool IsContentTranslated
    {
        get { return (bool) GetValue(IsContentTranslatedProperty); }
        set { SetValue(IsContentTranslatedProperty, value); }
    }

    #endregion IsContentTranslated

    void ISideDrawerNativeEventProxy.RaiseSlideChanged(float percentage)
    {
    }

    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();

        if (MainContent != null)
            SetInheritedBindingContext(MainContent, BindingContext);
        if (DrawerContent != null)
            SetInheritedBindingContext(DrawerContent, BindingContext);
    }
}

The major flaws in my original implementation were:

  • lack of measurement data being forwarded
  • not forwarding parent hierarchy
  • not inheriting bindingcontext

Upvotes: 1

Related Questions