0

I wanted to Implement Spotlight feature in my app, as the Spotlight library is Android specific and I didn't knew how to access platform specific libraries from the core Forms project, @Jason recommended to use DependencyService and @Colex - MSFT helped in its implementation. There are few small things, which aren't working as expected.

I've basically this 3 buttons (1st on top-left, 2nd on top-right, 3rd on bottom-center).

  1. The line animation has to start from the component (button), like this Preview from Spotlight library, but in my app they are starting from the top-left (all of them).

  2. I want that when 1st button's spotlight is fade out, then the 2nd button's spotlight should fade in and when it's fade out, the third button's spotlight should fade in. But what's happening now is that when the 1st button's spotlight is fade out, the 2nd button's spotlight is already faded in (already enabled), and hence no animation (line starts from the component (button) and the text appears).

Droid\Control\SpotLightService.cs

[assembly: Xamarin.Forms.Dependency(typeof(SpotLightService))]
namespace CustomRenderer.Droid.Control
{
    public class SpotLightService : ISpotLight
    {
        private bool isRevealEnabled_FirstButton_SpotLight = true;
        private bool isRevealEnabled_SecondButton_SpotLight = true;
        private bool isRevealEnabled_ThirdButton_SpotLight = true;
        private SpotlightView FirstButton_SpotLight;
        private SpotlightView SecondButton_SpotLight;
        private SpotlightView ThirdButton_SpotLight;

        public void ShowSpotLight_FirstButton(Xamarin.Forms.View view, string usageId)
        {
            FirstButton_SpotLight = new SpotlightView.Builder(MainActivity.Instance)
                .IntroAnimationDuration(400)
                .EnableRevealAnimation(isRevealEnabled_FirstButton_SpotLight)
                .PerformClick(true)
                .FadeinTextDuration(400)
                .HeadingTvColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .HeadingTvSize(32)
                .HeadingTvText("First Button")
                .SubHeadingTvColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .SubHeadingTvSize(16)
                .SubHeadingTvText("Lorem ipsum dolor sit amet, consectetur adipiscing elit")
                .MaskColor(Android.Graphics.Color.ParseColor("#dc000000"))
                .Target(ConvertFormsToNative(view))
                .LineAnimDuration(400)
                .LineAndArcColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .DismissOnTouch(true)
                .DismissOnBackPress(true)
                .EnableDismissAfterShown(true)
                .UsageId(usageId)
                .ShowTargetArc(true)
                .Show();
        }

        public void ShowSpotLight_SecondButton(Xamarin.Forms.View view, string usageId)
        {
            SecondButton_SpotLight = new SpotlightView.Builder(MainActivity.Instance)
                .IntroAnimationDuration(400)
                .EnableRevealAnimation(isRevealEnabled_SecondButton_SpotLight)
                .PerformClick(true)
                .FadeinTextDuration(400)
                .HeadingTvColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .HeadingTvSize(32)
                .HeadingTvText("Second Button")
                .SubHeadingTvColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .SubHeadingTvSize(16)
                .SubHeadingTvText("Sed do eiusmod tempor incididunt ut labore eta")
                .MaskColor(Android.Graphics.Color.ParseColor("#dc000000"))
                .Target(ConvertFormsToNative(view))
                .LineAnimDuration(400)
                .LineAndArcColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .DismissOnTouch(true)
                .DismissOnBackPress(true)
                .EnableDismissAfterShown(true)
                .UsageId(usageId)
                .ShowTargetArc(true)
                .Show();
        }

        public void ShowSpotLight_ThirdButton(Xamarin.Forms.View view, string usageId)
        {
            ThirdButton_SpotLight = new SpotlightView.Builder(MainActivity.Instance)
                .IntroAnimationDuration(400)
                .EnableRevealAnimation(isRevealEnabled_ThirdButton_SpotLight)
                .PerformClick(true)
                .FadeinTextDuration(400)
                .HeadingTvColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .HeadingTvSize(32)
                .HeadingTvText("Third Button")
                .SubHeadingTvColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .SubHeadingTvSize(16)
                .SubHeadingTvText("Ut enim ad minim veniam, quis nostrud exercitation")
                .MaskColor(Android.Graphics.Color.ParseColor("#dc000000"))
                .Target(ConvertFormsToNative(view))
                .LineAnimDuration(400)
                .LineAndArcColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .DismissOnTouch(true)
                .DismissOnBackPress(true)
                .EnableDismissAfterShown(true)
                .UsageId(usageId)
                .ShowTargetArc(true)
                .Show();
        }

        public View ConvertFormsToNative(Xamarin.Forms.View view)
        {
            var vRenderer = Platform.CreateRendererWithContext(view, MainActivity.Instance);
            var Androidview = vRenderer.View;
            vRenderer.Tracker.UpdateLayout();

            var size = view.Bounds;
            var layoutParams = new ViewGroup.LayoutParams((int)size.Width, (int)size.Height);
            Androidview.LayoutParameters = layoutParams;
            view.Layout(size);
            Androidview.Layout((int)size.X, (int)size.Y, (int)view.WidthRequest, (int)view.HeightRequest);
            Androidview.SetBackgroundColor(Android.Graphics.Color.Red);
            return Androidview;
        }
    }
}

MainPage.xml.cs

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        LayoutChanged += FirstButton_SpotLight;
        LayoutChanged += SecondButton_SpotLight;
        LayoutChanged += ThirdButton_SpotLight;
    }


    bool isShown_FirstButton_SpotLight = false;
    private void FirstButton_SpotLight(object sender, EventArgs e)
    {
        if (!isShown_FirstButton_SpotLight)
        {
            DependencyService.Get<ISpotLight>().ShowSpotLight_FirstButton(FirstButton, "FirstButton");
            isShown_FirstButton_SpotLight = true;
        }
    }


    bool isShown_SecondButton_SpotLight = false;
    private void SecondButton_SpotLight(object sender, EventArgs e)
    {
        if (!isShown_SecondButton_SpotLight)
        {
            DependencyService.Get<ISpotLight>().ShowSpotLight_SecondButton(SecondButton, "SecondButton");
            isShown_SecondButton_SpotLight = true;
        }
    }

    bool isShown_ThirdButton_SpotLight = false;
    private void ThirdButton_SpotLight(object sender, EventArgs e)
    {
        if (!isShown_ThirdButton_SpotLight)
        {
            DependencyService.Get<ISpotLight>().ShowSpotLight_ThirdButton(ThirdButton, "ThirdButton");
            isShown_ThirdButton_SpotLight = true;
        }
    }
}
Stavrogin
  • 135
  • 1
  • 15
  • it sounds like `ConvertFormsToNative` is returning the view for the Page, not the individual element you are targeting – Jason Aug 10 '21 at 20:20
  • thanks jason, can u please give me an example, how to fix it? – Stavrogin Aug 10 '21 at 21:03
  • that's only a guess, I don't know that it is actually the problem. You would need to debug it to be sure – Jason Aug 10 '21 at 21:05

1 Answers1

2

As json mentioned, I realized that it is true , the function ConvertFormsToNative does not return the real native element .

The only workaround for your requirement is

Create a custom renderer for the whole Page , and add the control in native(android) project.

For details you can refer to Customizing a ContentPage .

  1. Create custom renderer for Page .
[assembly: ExportRenderer(typeof(Page1), typeof(MainPageRenderer))]
namespace CustomRenderer.Droid
{
    class MainPageRenderer : PageRenderer
    {
        private Android.Views.View view;
        private Android.Widget.Button button1;
        private Android.Widget.Button button2;
        private Android.Widget.Button button3;
        public MainPageRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || Element == null)
            {
                return;
            }


            view = MainActivity.Instance.LayoutInflater.Inflate(Resource.Layout.MyPage, this, false);
            AddView(view);


            view.Click += View_Click;

            button1 = view.FindViewById<Android.Widget.Button>(Resource.Id.firstButton);
            button2 = view.FindViewById<Android.Widget.Button>(Resource.Id.secondButton);
            button3 = view.FindViewById<Android.Widget.Button>(Resource.Id.thirdButton);

            
        }

        bool isShown_FirstButton_SpotLight = false;
        bool isShown_SecondButton_SpotLight = false;
        bool isShown_ThirdButton_SpotLight = false;
        private void View_Click(object sender, EventArgs e)
        {
            if (!isShown_FirstButton_SpotLight)
            {
                show(button1 ,"First Button" ,"Lorem ipsum dolor sit amet, consectetur adipiscing elit", "FirstButton");
                isShown_FirstButton_SpotLight = true;
                return;
            }
            if (!isShown_SecondButton_SpotLight)
            {
                show(button2,"Second Button", "Sed do eiusmod tempor incididunt ut labore eta", "SecondButton");
                isShown_SecondButton_SpotLight = true;
                return;
            }
            if (!isShown_ThirdButton_SpotLight)
            {
                show(button3,"Third Button", "Ut enim ad minim veniam, quis nostrud exercitation", "ThirdButton");
                isShown_ThirdButton_SpotLight = true;
                return;
            }
        }

        void show(Android.Views.View view, string title, string subTitle, string usageId)
        {
            new SpotlightView.Builder(MainActivity.Instance)
                .IntroAnimationDuration(400)
                .EnableRevealAnimation(true)
                .PerformClick(true)
                .FadeinTextDuration(400)
                .HeadingTvColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .HeadingTvSize(32)
                .HeadingTvText(title)
                .SubHeadingTvColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .SubHeadingTvSize(16)
                .SubHeadingTvText(subTitle)
                .MaskColor(Android.Graphics.Color.ParseColor("#dc000000"))
                .Target(view)
                .LineAnimDuration(400)
                .LineAndArcColor(Android.Graphics.Color.ParseColor("#eb273f"))
                .DismissOnTouch(true)
                .DismissOnBackPress(true)
                .EnableDismissAfterShown(true)
                .UsageId(usageId)
                .ShowTargetArc(true)
                .Show();
        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);

            var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
            var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);

            view.Measure(msw, msh);
            view.Layout(0, 0, r - l, b - t);
        }
    }
}
  1. Create a xml and place it in folder Resources/layout in android project .
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <Button
        android:id="@+id/firstButton"
        android:layout_width="50dp"    
        android:layout_height="50dp"
        android:layout_marginLeft="50dp"    
        android:layout_marginTop="50dp"       
        android:text="First"
        android:background="#00ff00"/>

    <Button
        android:id="@+id/secondButton"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginLeft="100dp"
        android:layout_marginTop="100dp"     
        android:text="Second"
        android:background="#ff0000"/>

    <Button
        android:id="@+id/thirdButton"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginLeft="150dp"
        android:layout_marginTop="150dp"
        android:text="Third"
        android:background="#0000ff"/>
</LinearLayout>

enter image description here enter image description hereenter image description here

Little thing

  • Click white space area to show the Spotlight in turn.

  • The plugin only supports circle spotlight, you can make the control round ,check here .

ColeX
  • 14,062
  • 5
  • 43
  • 240
  • and what should I do with DependecyService? `DependencyService.Get().ShowSpotLight_FirstButton(FirstButton, "FirstButton");` is throwing an error. – Stavrogin Aug 11 '21 at 09:03
  • Remove it . The page in forms is empty. – ColeX Aug 11 '21 at 09:09
  • yes, I removed it, and now no error but also no SpotLight.. and in my real app the UI of the Page in Xamarin.Forms is very complex, can I copy it in Andriod page UI? also this page in Xamarin.Forms has data bindings too. – Stavrogin Aug 11 '21 at 09:12
  • Remove the app from the device and try again , no, you can't, this is just for the simple UI using Android layout .. – ColeX Aug 11 '21 at 09:14
  • I test with your code ,it works perfectly . Just click the white space and wait for second . – ColeX Aug 12 '21 at 01:40
  • I want the spotlight to start automatically, as it happened earlier also what about UI? how could i use Forms page? – Stavrogin Aug 12 '21 at 04:25
  • `start automatically` is easy to achieve , but using forms page can't be able to implement.. I was trying everything but on luck , we can only use android UI. – ColeX Aug 12 '21 at 05:47
  • If you want to use this plugin in forms project , just apply it on the simple page which could create UI in android project . – ColeX Aug 12 '21 at 05:51
  • Is there any other problem ,bro? – ColeX Aug 12 '21 at 08:59
  • sorry, I was away.. can you please tell me how can I achieve "start spotlight automatically" as in the earlier example? – Stavrogin Aug 13 '21 at 07:05
  • and one more thing. I want that when for the first time the app opens up, this spotlight should start automatically (android page), and when it went off, the forms page should appear. I mean, how can I detect if all (3) spotlights are disappeared? so then I can navigate to the forms page, which will have actual UI i want to show. – Stavrogin Aug 13 '21 at 07:08
  • Ok , this is another question , could you open a new thread to ask ? I will provide the detailed solution . – ColeX Aug 13 '21 at 07:27