Skip to content

Instantly share code, notes, and snippets.

@ytabuchi
Last active November 6, 2019 01:51
Show Gist options
  • Save ytabuchi/ab7af11fed122987278a9f03d9a8d157 to your computer and use it in GitHub Desktop.
Save ytabuchi/ab7af11fed122987278a9f03d9a8d157 to your computer and use it in GitHub Desktop.
Snippets for platform specific training

Snippet for CustomRenderer

Xamarin.Forms / NakayokunaruHandsOn.xaml.cs

public App()
{
    InitializeComponent();

    var rootPage = new ContentPage();

    var toRoundedBoxView = new Button
    {
        Text = "RoundedBoxView"
    };
    // toRoundedBoxView.Clicked += async (s, e) =>
    //     await rootPage.Navigation.PushAsync(new RoundedBoxViewPage());

    var toEventBasedWebView = new Button
    {
        Text = "EventBasedWebView"
    };
    // toEventBasedWebView.Clicked += async (s, e) =>
    //     await rootPage.Navigation.PushAsync(new EventBasedWebViewPage());

    var toMessageBasedWebView = new Button
    {
        Text = "MessageBasedWebView"
    };
    // toMessageBasedWebView.Clicked += async (s, e) =>
    //     await rootPage.Navigation.PushAsync(new MessageBasedWebViewPage());

    rootPage.Content = new StackLayout
    {
        VerticalOptions = LayoutOptions.Center,
        Children =
        {
            toRoundedBoxView,
            toEventBasedWebView,
            toMessageBasedWebView,
        },
    };

    MainPage = new NavigationPage(rootPage);
}

Xamarin.Forms / RoundedBoxView.cs

using System;
using Xamarin.Forms;

namespace NakayokunaruHandsOn
{
    public class RoundedBoxView : View
    {
        #region CornerRadius BindableProperty
        public static readonly BindableProperty CornerRadiusProperty =
            BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(RoundedBoxView), 5.0);

        public double CornerRadius
        {
            get { return (double)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }
        #endregion

        #region Color BindableProperty
        public static readonly BindableProperty ColorProperty =
            BindableProperty.Create(nameof(Color), typeof(Color), typeof(RoundedBoxView), Color.Accent);

        public Color Color
        {
            get { return (Color)GetValue(ColorProperty); }
            set { SetValue(ColorProperty, value); }
        }
        #endregion

        // public event EventHandler Clicked;

        // internal void SendClick()
        // {
        //  Clicked?.Invoke(this, EventArgs.Empty);
        // }
    }
}

Android / RoundedBoxViewRenderer.cs

using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(NakayokunaruHandsOn.RoundedBoxView), typeof(NakayokunaruHandsOn.Droid.RoundedBoxViewRenderer))]

namespace NakayokunaruHandsOn.Droid
{
    public class RoundedBoxViewRenderer : ViewRenderer<RoundedBoxView, Android.Views.View>
    {
        public RoundedBoxViewRenderer(Android.Content.Context context)
            : base(context)
        {
        }
        
        Android.Graphics.Drawables.GradientDrawable controlBackground;

        protected override void OnElementChanged(ElementChangedEventArgs<RoundedBoxView> e)
        {
            if (Control == null)
            {
                var nativeControl = new Android.Views.View(Context);
                controlBackground = new Android.Graphics.Drawables.GradientDrawable();
                nativeControl.Background = controlBackground;
                // nativeControl.Click += OnClick;
                SetNativeControl(nativeControl);
            }

            if (e.NewElement != null)
            {
                UpdateRadius();
                UpdateColor();
            }

            base.OnElementChanged(e);
        }

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

            if (e.PropertyName == RoundedBoxView.CornerRadiusProperty.PropertyName)
            {
                UpdateRadius();
            }
            if (e.PropertyName == RoundedBoxView.ColorProperty.PropertyName)
            {
                UpdateColor();
            }
        }

        private void UpdateRadius()
        {
            var radiusDp = (float)(Element.CornerRadius * Resources.DisplayMetrics.Density);
            controlBackground.SetCornerRadius(radiusDp);
        }

        private void UpdateColor()
        {
            controlBackground.SetColor(Element.Color.ToAndroid());
        }

        // private void OnClick(object sender, EventArgs e)
        // {
        //  Element?.SendClick();
        // }

        // protected override void Dispose(bool disposing)
        // {
        //  if (Control != null)
        //      Control.Click -= OnClick;

        //  base.Dispose(disposing);
        // }
    }
}

iOS / RoundedBoxViewRenderer.cs

using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using UIKit;

// Xamarin.FormsコントロールとRendererの対応を宣言
[assembly: ExportRenderer(typeof(NakayokunaruHandsOn.RoundedBoxView), typeof(NakayokunaruHandsOn.iOS.RoundedBoxViewRenderer))]

namespace NakayokunaruHandsOn.iOS
{
    public class RoundedBoxViewRenderer : ViewRenderer<RoundedBoxView, UIView>
    {
        // private UITapGestureRecognizer tapGesuture;

        protected override void OnElementChanged(ElementChangedEventArgs<RoundedBoxView> e)
        {
            if (Control == null)
            {
                var nativeControl = new UIView();
                // tapGesuture = new UITapGestureRecognizer(() => Element?.SendClick());
                // nativeControl.AddGestureRecognizer(tapGesuture);
                SetNativeControl(nativeControl);
            }

            if (e.NewElement != null)
            {
                UpdateRadius();
                UpdateColor();
            }

            base.OnElementChanged(e);
        }

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

            if (e.PropertyName == RoundedBoxView.CornerRadiusProperty.PropertyName)
            {
                UpdateRadius();
            }
            if (e.PropertyName == RoundedBoxView.ColorProperty.PropertyName)
            {
                UpdateColor();
            }
        }

        private void UpdateRadius()
        {
            Control.Layer.CornerRadius = (float)Element.CornerRadius;
        }

        private void UpdateColor()
        {
            Control.BackgroundColor = Element.Color.ToUIColor();
        }

        // protected override void Dispose(bool disposing)
        // {
        //  if (Control != null)
        //      Control.RemoveGestureRecognizer(tapGesuture);

        //  base.Dispose(disposing);
        // }
    }
}

Xamarin.Forms / RoundedBoxViewPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:NakayokunaruHandsOn"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             ios:Page.UseSafeArea="true"
             Title="RoundedBoxView"
             x:Class="NakayokunaruHandsOn.RoundedBoxViewPage">
    <StackLayout HorizontalOptions="Center"
                 VerticalOptions="Center">
        <local:RoundedBoxView x:Name="roundedBox"
                              HeightRequest="100"
                              WidthRequest="100"
                              CornerRadius="10"/>
        <Button Text="Next Color" Clicked="OnClicked" />
    </StackLayout>
</ContentPage>

Xamarin.Forms / RoundedBoxViewPage.xaml.cs

using System;
using Xamarin.Forms;

namespace NakayokunaruHandsOn
{
    public partial class RoundedBoxViewPage : ContentPage
    {
        public RoundedBoxViewPage()
        {
            InitializeComponent();
        }

        Random random = new Random ();

        private void OnClicked (object sender, EventArgs e)
        {
            roundedBox.Color = Color.FromRgb (
                random.Next (255),
                random.Next (255),
                random.Next (255));
        }
    }
}

Xamarin.Forms / AssemblyInfo.cs

[assembly: InternalsVisibleTo("NakayokunaruHandsOn.iOS")]
[assembly: InternalsVisibleTo("NakayokunaruHandsOn.Android")]

iOS / info.plist

<!-- ここを追加 -->
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

Xamarin.Forms / EventBasedWebView.cs

using System;
using Xamarin.Forms;

namespace NakayokunaruHandsOn
{
    public class EventBasedWebView : View
    {
        public void GoBack()
        {
            EventHandler handler = GoBackRequested;
            if (handler != null)
            {
                handler.Invoke(this, EventArgs.Empty);
            }
        }

        public void GoForward()
        {
            EventHandler handler = GoForwardRequested;
            if (handler != null)
            {
                handler.Invoke(this, EventArgs.Empty);
            }
        }

        public void Eval(string script)
        {
            EventHandler<EvalRequestedEventArgs> handler = EvalRequested;
            if (handler != null)
            {
                handler.Invoke(this, new EvalRequestedEventArgs(script));
            }
        }

        internal event EventHandler GoBackRequested;
        internal event EventHandler GoForwardRequested;
        internal event EventHandler<EvalRequestedEventArgs> EvalRequested;
    }

    public class EvalRequestedEventArgs
    {
        public string Script
        {
            get;
            private set;
        }

        public EvalRequestedEventArgs(string script)
        {
            Script = script;
        }
    }
}

Android / EventBasedWebViewRenderer.cs

using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using AWebView = Android.Webkit.WebView;
using Android.Webkit;

[assembly: ExportRenderer(typeof(NakayokunaruHandsOn.EventBasedWebView), typeof(NakayokunaruHandsOn.Droid.EventBasedWebViewRenderer))]

namespace NakayokunaruHandsOn.Droid
{
    public class EventBasedWebViewRenderer : ViewRenderer<EventBasedWebView, AWebView>
    {
        public EventBasedWebViewRenderer(Android.Content.Context context)
            : base(context)
        {
        }
        
        protected override void OnElementChanged(ElementChangedEventArgs<EventBasedWebView> e)
        {
            if (Control == null)
            {
                var nativeControl = new AWebView(Context);
                nativeControl.SetWebViewClient(new WebClient());
                nativeControl.SetWebChromeClient(new WebChromeClient());
                nativeControl.Settings.JavaScriptEnabled = true;
                nativeControl.LoadUrl("https://ticktack.hatenablog.jp/entry/2016/06/11/124751");
                SetNativeControl(nativeControl);
            }

            if (e.OldElement != null)
            {
                Element.GoBackRequested -= OnGoBackRequested;
                Element.GoForwardRequested -= OnGoForwardRequested;
                Element.EvalRequested -= OnEvalRequested;
            }

            if (e.NewElement != null)
            {
                Element.GoBackRequested += OnGoBackRequested;
                Element.GoForwardRequested += OnGoForwardRequested;
                Element.EvalRequested += OnEvalRequested;
            }

            base.OnElementChanged(e);
        }

        private void OnGoBackRequested(object sender, EventArgs e)
        {
            if (Control.CanGoBack())
            {
                Control.GoBack();
            }
        }

        private void OnGoForwardRequested(object sender, EventArgs e)
        {
            if (Control.CanGoForward())
            {
                Control.GoForward();
            }
        }

        private void OnEvalRequested(object sender, EvalRequestedEventArgs e)
        {
            Control.LoadUrl("javascript:" + e.Script);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                Element.GoBackRequested -= OnGoBackRequested;
                Element.GoForwardRequested -= OnGoForwardRequested;
                Element.EvalRequested -= OnEvalRequested;
            }

            base.Dispose(disposing);
        }

        private class WebClient : WebViewClient
        {
            public override bool ShouldOverrideUrlLoading(AWebView view, string url)
            {
                view.LoadUrl(url);
                return true;
            }
        }
    }
}

iOS / EventBasedWebViewRenderer.cs

using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using UIKit;
using Foundation;

[assembly: ExportRenderer(typeof(NakayokunaruHandsOn.EventBasedWebView), typeof(NakayokunaruHandsOn.iOS.EventBasedWebViewRenderer))]

namespace NakayokunaruHandsOn.iOS
{
    public class EventBasedWebViewRenderer : ViewRenderer<EventBasedWebView, UIWebView>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<EventBasedWebView> e)
        {
            if (Control == null)
            {
                var nativeControl = new UIWebView();
                nativeControl.LoadRequest(new NSUrlRequest(new NSUrl("https://ticktack.hatenablog.jp/entry/2016/06/11/124751")));
                SetNativeControl(nativeControl);
            }

            if (e.OldElement != null)
            {
                Element.GoBackRequested -= OnGoBackRequested;
                Element.GoForwardRequested -= OnGoForwardRequested;
                Element.EvalRequested -= OnEvalRequested;
            }

            if (e.NewElement != null)
            {
                Element.GoBackRequested += OnGoBackRequested;
                Element.GoForwardRequested += OnGoForwardRequested;
                Element.EvalRequested += OnEvalRequested;
            }

            base.OnElementChanged(e);
        }

        private void OnGoBackRequested(object sender, EventArgs e)
        {
            if (Control.CanGoBack)
            {
                Control.GoBack();
            }
        }

        private void OnGoForwardRequested(object sender, EventArgs e)
        {
            if (Control.CanGoForward)
            {
                Control.GoForward();
            }
        }

        private void OnEvalRequested(object sender, EvalRequestedEventArgs e)
        {
            Control.EvaluateJavascript(e.Script);
        }

        public override void LayoutSubviews()
        {
            base.LayoutSubviews();
            Control.Frame = this.Bounds;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                Element.GoBackRequested -= OnGoBackRequested;
                Element.GoForwardRequested -= OnGoForwardRequested;
                Element.EvalRequested -= OnEvalRequested;
            }

            base.Dispose(disposing);
        }
    }
}

Xamarin.Forms / EventBasedWebViewPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage  xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:NakayokunaruHandsOn"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             ios:Page.UseSafeArea="true"
             Title="EventBasedWebView"
             x:Class="NakayokunaruHandsOn.EventBasedWebViewPage">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <local:EventBasedWebView Grid.Row="0" x:Name="webView" />
        <StackLayout Grid.Row="1" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
            <Button Text="戻る" Clicked="GoBackClicked"/>
            <Button Text="進む" Clicked="GoForwardClicked"/>
        </StackLayout>
        <StackLayout Grid.Row="2" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
            <Entry Text="location.reload()" x:Name="entry" HorizontalOptions="FillAndExpand"/>
            <Button Text="Eval" Clicked="EvalClicked"/>
        </StackLayout>
    </Grid>
</ContentPage>

Xamarin.Forms / EventBasedWebViewPage.xaml.cs

using Xamarin.Forms;

namespace NakayokunaruHandsOn
{
    public partial class EventBasedWebViewPage : ContentPage
    {
        public EventBasedWebViewPage()
        {
            InitializeComponent();
        }

        void GoBackClicked(object sender, System.EventArgs e)
        {
            webView.GoBack();
        }

        void GoForwardClicked(object sender, System.EventArgs e)
        {
            webView.GoForward();
        }

        void EvalClicked(object sender, System.EventArgs e)
        {
            webView.Eval(entry.Text);
        }
    }
}

Xamarin.Forms / MessageBasedWebView.cs

using Xamarin.Forms;

namespace NakayokunaruHandsOn
{
    public class MessageBasedWebView : View
    {
        internal static readonly string GoBackKey = "MessageBasedWebView.GoBack";
        internal static readonly string GoForwardKey = "MessageBasedWebView.GoForward";
        internal static readonly string EvalKey = "MessageBasedWebView.Eval";

        public void GoBack()
        {
            MessagingCenter.Send(this, GoBackKey);
        }

        public void GoForward()
        {
            MessagingCenter.Send(this, GoForwardKey);
        }

        public void Eval(string script)
        {
            MessagingCenter.Send(this, EvalKey, script);
        }
    }
}

Android / MessageBasedWebViewRenderer.cs

using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using AWebView = Android.Webkit.WebView;
using Android.Webkit;

[assembly: ExportRenderer(typeof(NakayokunaruHandsOn.MessageBasedWebView), typeof(NakayokunaruHandsOn.Droid.MessageBasedWebViewRenderer))]

namespace NakayokunaruHandsOn.Droid
{
    public class MessageBasedWebViewRenderer : ViewRenderer<MessageBasedWebView, AWebView>
    {
        public MessageBasedWebViewRenderer(Android.Content.Context context)
            : base(context)
        {
        }
        
        protected override void OnElementChanged(ElementChangedEventArgs<MessageBasedWebView> e)
        {
            if (Control == null)
            {
                var nativeControl = new AWebView(Context);
                nativeControl.SetWebViewClient(new WebClient());
                nativeControl.SetWebChromeClient(new WebChromeClient());
                nativeControl.Settings.JavaScriptEnabled = true;
                nativeControl.LoadUrl("http://ticktack.hatenablog.jp/entry/2016/06/11/124751");
                SetNativeControl(nativeControl);
            }

            if (e.OldElement != null)
            {
                MessagingCenter.Unsubscribe<MessageBasedWebView>(this, MessageBasedWebView.GoBackKey);
                MessagingCenter.Unsubscribe<MessageBasedWebView>(this, MessageBasedWebView.GoForwardKey);
                MessagingCenter.Unsubscribe<MessageBasedWebView, string>(this, MessageBasedWebView.EvalKey);
            }

            if (e.NewElement != null)
            {
                MessagingCenter.Subscribe<MessageBasedWebView>(
                    this,
                    MessageBasedWebView.GoBackKey,
                    _ => OnGoBackRequested(),
                    e.NewElement);
                MessagingCenter.Subscribe<MessageBasedWebView>(
                    this,
                    MessageBasedWebView.GoForwardKey,
                    _ => OnGoForwardRequested(),
                    e.NewElement);
                MessagingCenter.Subscribe<MessageBasedWebView, string>(
                    this,
                    MessageBasedWebView.EvalKey,
                    (sender, args) => OnEvalRequested(args),
                    e.NewElement);
            }

            base.OnElementChanged(e);
        }

        private void OnGoBackRequested()
        {
            if (Control.CanGoBack())
            {
                Control.GoBack();
            }
        }

        private void OnGoForwardRequested()
        {
            if (Control.CanGoForward())
            {
                Control.GoForward();
            }
        }

        private void OnEvalRequested(string script)
        {
            Control.LoadUrl("javascript:" + script);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                MessagingCenter.Unsubscribe<MessageBasedWebView>(this, MessageBasedWebView.GoBackKey);
                MessagingCenter.Unsubscribe<MessageBasedWebView>(this, MessageBasedWebView.GoForwardKey);
                MessagingCenter.Unsubscribe<MessageBasedWebView, string>(this, MessageBasedWebView.EvalKey);
            }

            base.Dispose(disposing);
        }

        private class WebClient : WebViewClient
        {
            public override bool ShouldOverrideUrlLoading(AWebView view, string url)
            {
                view.LoadUrl(url);
                return true;
            }
        }
    }
}

iOS / MessageBasedWebViewRenderer.cs

using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using UIKit;
using Foundation;

[assembly: ExportRenderer(typeof(NakayokunaruHandsOn.MessageBasedWebView), typeof(NakayokunaruHandsOn.iOS.MessageBasedWebViewRenderer))]

namespace NakayokunaruHandsOn.iOS
{
    public class MessageBasedWebViewRenderer : ViewRenderer<MessageBasedWebView, UIWebView>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<MessageBasedWebView> e)
        {
            if (Control == null)
            {
                var nativeControl = new UIWebView();
                nativeControl.LoadRequest(new NSUrlRequest(new NSUrl("http://ticktack.hatenablog.jp/entry/2016/06/11/124751")));
                SetNativeControl(nativeControl);
            }

            if (e.OldElement != null)
            {
                MessagingCenter.Unsubscribe<MessageBasedWebView>(this, MessageBasedWebView.GoBackKey);
                MessagingCenter.Unsubscribe<MessageBasedWebView>(this, MessageBasedWebView.GoForwardKey);
                MessagingCenter.Unsubscribe<MessageBasedWebView, string>(this, MessageBasedWebView.EvalKey);
            }

            if (e.NewElement != null)
            {
                MessagingCenter.Subscribe<MessageBasedWebView>(
                    this,
                    MessageBasedWebView.GoBackKey,
                    _ => OnGoBackRequested(),
                    e.NewElement);
                MessagingCenter.Subscribe<MessageBasedWebView>(
                    this,
                    MessageBasedWebView.GoForwardKey,
                    _ => OnGoForwardRequested(),
                    e.NewElement);
                MessagingCenter.Subscribe<MessageBasedWebView, string>(
                    this,
                    MessageBasedWebView.EvalKey,
                    (sender, args) => OnEvalRequested(args),
                    e.NewElement);
            }

            base.OnElementChanged(e);
        }

        private void OnGoBackRequested()
        {
            if (Control.CanGoBack)
            {
                Control.GoBack();
            }
        }

        private void OnGoForwardRequested()
        {
            if (Control.CanGoForward)
            {
                Control.GoForward();
            }
        }

        private void OnEvalRequested(string script)
        {
            Control.EvaluateJavascript(script);
        }

        public override void LayoutSubviews()
        {
            base.LayoutSubviews();
            Control.Frame = this.Bounds;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                MessagingCenter.Unsubscribe<MessageBasedWebView>(this, MessageBasedWebView.GoBackKey);
                MessagingCenter.Unsubscribe<MessageBasedWebView>(this, MessageBasedWebView.GoForwardKey);
                MessagingCenter.Unsubscribe<MessageBasedWebView, string>(this, MessageBasedWebView.EvalKey);
            }

            base.Dispose(disposing);
        }
    }
}

Xamarin.Forms / MessageBasedWebViewPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:NakayokunaruHandsOn"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             ios:Page.UseSafeArea="true"
             Title="MessageBasedWebView"
             x:Class="NakayokunaruHandsOn.MessageBasedWebViewPage">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <local:MessageBasedWebView Grid.Row="0" x:Name="webView" />
        <StackLayout Grid.Row="1" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
            <Button Text="戻る" Clicked="GoBackClicked"/>
            <Button Text="進む" Clicked="GoForwardClicked"/>
    </StackLayout>
    <StackLayout Grid.Row="2" Orientation="Horizontal">
      <Entry Text="location.reload()" x:Name="entry" HorizontalOptions="FillAndExpand"/>
      <Button Text="Eval" Clicked="EvalClicked"/>
    </StackLayout>
    </Grid>
</ContentPage>

Xamarin.Forms / MessageBasedWebViewPage.xaml.cs

using Xamarin.Forms;

namespace NakayokunaruHandsOn
{
    public partial class MessageBasedWebViewPage : ContentPage
    {
        public MessageBasedWebViewPage()
        {
            InitializeComponent();
        }

        void GoBackClicked(object sender, System.EventArgs e)
        {
            webView.GoBack();
        }

        void GoForwardClicked(object sender, System.EventArgs e)
        {
            webView.GoForward();
        }

        void EvalClicked(object sender, System.EventArgs e)
        {
            webView.Eval(entry.Text);
        }
    }
}

Android / BorderEffect.cs

using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ResolutionGroupName("NakayokunaruHandsOn")]
[assembly: ExportEffect(typeof(NakayokunaruHandsOn.Droid.BorderEffect), "BorderEffect")]

namespace NakayokunaruHandsOn.Droid
{
    public class BorderEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            if(Control == null)
                return;

            if(Control.Background == null)
            {
                var background = new Android.Graphics.Drawables.GradientDrawable();
                Control.Background = background;
            }

            var drawable = Control.Background as Android.Graphics.Drawables.GradientDrawable;
            if(drawable == null)
                return;

            drawable?.SetStroke(1, Color.Black.ToAndroid());
        }

        protected override void OnDetached()
        {
            var drawable = Control?.Background as Android.Graphics.Drawables.GradientDrawable;
            if(drawable == null)
                return;
            
            drawable?.SetStroke(0, Color.Black.ToAndroid());
        }
    }
}

iOS / BorderEffect.cs

using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ResolutionGroupName("NakayokunaruHandsOn")]
[assembly: ExportEffect(typeof(NakayokunaruHandsOn.iOS.BorderEffect), "BorderEffect")]

namespace NakayokunaruHandsOn.iOS
{
    public class BorderEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            if(Control == null)
                return;

            Control.Layer.BorderWidth = 1;
            Control.Layer.BorderColor = Color.Black.ToCGColor();
        }

        protected override void OnDetached()
        {
            if(Control == null)
                return;

            Control.Layer.BorderWidth = 0;
        }
    }
}

Xamarin.Forms / EffectsSamplePage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:NakayokunaruHandsOn"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             ios:Page.UseSafeArea="true"
             Title="EffectsSample"
             x:Class="NakayokunaruHandsOn.EffectsSamplePage">

    <Label x:Name="label" Text="Effects Sample" VerticalOptions="Center" HorizontalOptions="Center"/>
</ContentPage>

Xamarin.Forms / EffectsSamplePage.xaml.cs

using System;
using System.Collections.Generic;
using Xamarin.Forms;

namespace NakayokunaruHandsOn
{
    public partial class EffectsSamplePage : ContentPage
    {
        public EffectsSamplePage()
        {
            InitializeComponent();

            label.Effects.Add( Effect.Resolve("NakayokunaruHandsOn.BorderEffect") );
        }
    }
}

Xamarin.Forms / App.xaml.cs

public App()
{
    ...(省略)...

    // ボタンを追加
    var toEffectsSample = new Button
    {
        Text = "EffectsSample"
    };
    toEffectsSample.Clicked += async (s, e) =>
        await rootPage.Navigation.PushAsync(new EffectsSamplePage());
    
    
    rootPage.Content = new StackLayout
    {
        VerticalOptions = LayoutOptions.Center,
        Children =
        {
            toRoundedBoxView,
            toEventBasedWebView,
            toMessageBasedWebView,
            toEffectsSample, // この行を追加
        },
    };
    
    MainPage = new NavigationPage(rootPage);
}

Xamarin.Forms / BorderEffect.cs

using System;
using Xamarin.Forms;

namespace NakayokunaruHandsOn
{
    public class BorderEffect : RoutingEffect
    {
        public BorderEffect() : base("NakayokunaruHandsOn.BorderEffect")
        {
        }
    }
}

Xamarin.Forms / EffectsSamplePage.xaml.cs

//label.Effects.Add( Effect.Resolve("NakayokunaruHandsOn.BorderEffect") );
label.Effects.Add( new BorderEffect() ); // 代わりに追加するコード

Xamarin.Forms / ViewExtensions.cs

using System;
using System.Linq;
using Xamarin.Forms;

namespace NakayokunaruHandsOn
{
    public static class ViewExtensions
    {
        public static readonly BindableProperty HasBorderProperty =
            BindableProperty.CreateAttached("HasBorder", typeof(bool), typeof(ViewExtensions), false, propertyChanged: OnHasBorderChanged);

        static void OnHasBorderChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var view = bindable as View;
            if(view == null)
                return;

            var hasBorder = (bool)newValue;
            if(hasBorder)
            {
                view.Effects.Add(new BorderEffect());
            }
            else
            {
                var toRemove = view.Effects.FirstOrDefault(x => x is BorderEffect);
                if(toRemove != null)
                {
                    view.Effects.Remove(toRemove);
                }
            }
        }

        public static readonly BindableProperty BorderColorProperty =
            BindableProperty.CreateAttached("BorderColor", typeof(Color), typeof(ViewExtensions), Color.Accent);

        public static void SetHasBorder(BindableObject view, bool hasBorder)
        {
            view.SetValue(HasBorderProperty, hasBorder);
        }

        public static bool GetHasBorder(BindableObject view)
        {
            return (bool)view.GetValue(HasBorderProperty);
        }

        public static void SetBorderColor(BindableObject view, Color color)
        {
            view.SetValue(BorderColorProperty, color);
        }

        public static Color GetBorderColor(BindableObject view)
        {
            return (Color)view.GetValue(BorderColorProperty);
        }

        class BorderEffect : RoutingEffect
        {
            public BorderEffect() : base("NakayokunaruHandsOn.BorderEffect")
            {
            }
        }
    }
}

Xamarin.Forms / EffectsSamplePage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:NakayokunaruHandsOn"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             ios:Page.UseSafeArea="true"
             Title="EffectsSample"
             x:Class="NakayokunaruHandsOn.EffectsSamplePage">

    <Label x:Name="label" Text="Effects Sample" VerticalOptions="Center" HorizontalOptions="Center"
           local:ViewExtensions.HasBorder="true"
           local:ViewExtensions.BorderColor="Red" />
</ContentPage>

Android / BorderEffect.cs

public class BorderEffect : PlatformEffect
{
    protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
    {
        if(args.PropertyName == ViewExtensions.BorderColorProperty.PropertyName)
        {
            UpdateBorder();
        }
    }

    protected override void OnAttached()
    {
        if(Control == null)
            return;

        if(Control.Background == null)
        {
            var background = new Android.Graphics.Drawables.GradientDrawable();
            Control.Background = background;
        }

        UpdateBorder();
    }

    protected override void OnDetached()
    {
        var drawable = Control?.Background as Android.Graphics.Drawables.GradientDrawable;
        if(drawable == null)
            return;
        
        drawable?.SetStroke(0, Color.Black.ToAndroid());
    }

    void UpdateBorder()
    {
        var drawable = Control?.Background as Android.Graphics.Drawables.GradientDrawable;
        if(drawable == null)
            return;

        var color = ViewExtensions.GetBorderColor(Element);

        drawable?.SetStroke(1, color.ToAndroid());
    }
}

Snippet for platform specific

Xamarin.Forms / MainPage.xaml

<StackLayout VerticalOptions="Center" Spacing="0">
    <Label Text="Dependency Service" BackgroundColor="Silver" FontSize="Large" FontAttributes="Bold" />
    <Label x:Name="_fromDependencyServiceLabel" Text="Battery Level: --%" HorizontalOptions="Center" />
    <Button x:Name="_fromDependencyServiceButton" Text="更新"/>
        
    <Label Text="Plugin" BackgroundColor="Silver" FontSize="Large" FontAttributes="Bold" />
    <Label x:Name="_fromPluginLabel" Text="Battery Level: --%" HorizontalOptions="Center" />
    <Button x:Name="_fromPluginButton" Text="更新" />
        
    <Label Text="Xamarin Essentials" BackgroundColor="Silver" FontSize="Large" FontAttributes="Bold" />
    <Label x:Name="_fromEssentialsLabel" Text="Battery Level: --%" HorizontalOptions="Center" />
    <Button x:Name="_fromEssentialsButton"  Text="更新" />
</StackLayout>

Xamaron.Forms / MainPage.xaml.cs

public MainPage ()
{
    InitializeComponent ();

    _fromDependencyServiceButton.Clicked += (s, e) => UpdateFromDependencyService ();
    _fromPluginButton.Clicked += (s, e) => UpdateFromPlugin ();
    _fromEssentialsButton.Clicked += (s, e) => UpdateFromEssentials ();
}

void UpdateFromDependencyService ()
{ 

}

void UpdateFromPlugin ()
{ 

}

void UpdateFromEssentials ()
{ 
    
}

Xamaron.Forms / PlatformSpecific/IBattery.cs

public interface IBattery
{
    double ChargeLevel { get; }
}

iOS / PlatformSpecific/Battery.cs

using PlatformSpecificSample.PlatformSpecific;

[assembly: Xamarin.Forms.Dependency(typeof(PROJECT_NAME.iOS.PlatformSpecific.Battery))]
public class Battery : IBattery
{
    public double ChargeLevel
    {
        get
        {
            UIKit.UIDevice.CurrentDevice.BatteryMonitoringEnabled = true;
            return UIKit.UIDevice.CurrentDevice.BatteryLevel;
        }
    }
}

Android / PlatformSpecific/Battery.cs

using Android.App;
using Android.Content;
using PlatformSpecificSample.PlatformSpecific;

[assembly: Xamarin.Forms.Dependency(typeof(PROJECT_NAME.Droid.PlatformSpecific.Battery))]
public class Battery : IBattery
{
    public double ChargeLevel
    {
        get
        {
            using (var filter = new IntentFilter (Intent.ActionBatteryChanged))
            using (var battery = Application.Context.RegisterReceiver (null, filter))
            {
                var level = battery.GetIntExtra (Android.OS.BatteryManager.ExtraLevel, -1);
                var scale = battery.GetIntExtra (Android.OS.BatteryManager.ExtraScale, -1);

                if (scale <= 0)
                    return 1.0;

                return (double) level / (double) scale;
            }
        }
    }
}

Xamarin.Forms / MainPage.xaml.cs

void UpdateFromDependencyService ()
{
    var level = DependencyService.Get<PlatformSpecific.IBattery> ().ChargeLevel;
    _fromDependencyServiceLabel.Text = $"Battery level: {Math.Floor(level * 100)}%";
}

Xamarin.Forms / MainPage.xaml.cs

void UpdateFromPlugin()
{
    var remainingChargePercent = Plugin.Battery.CrossBattery.Current.RemainingChargePercent;
    _fromPluginLabel.Text = $"Battery level: {remainingChargePercent}%";
}

Xamarin.Forms / MainPage.xaml.cs

void UpdateFromEssentials()
{
    var level = Xamarin.Essentials.Battery.ChargeLevel;
    _fromEssentialsLabel.Text = $"Battery level: {Math.Floor(level * 100)}%";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment