Search code examples
xamarin.formsxamarin.androidandroid-cardviewcustom-renderer

How to migrate a clickable Frame with ripple touch to Xamarin.Forms 2.5?


I have implemented a clickable Frame in Xamarin.Forms 2.3.4 with a custom FrameRenderer that set Clickable (and LongPressable FWIW) to true, subscribed the respective events and set the FrameRenderers foreground

TypedValue typedValue = new TypedValue();
this.Context.Theme.ResolveAttribute(Android.Resource.Attribute.SelectableItemBackground, typedValue, true);
this.Foreground = this.Resources.GetDrawable(typedValue.ResourceId, this.Context.Theme);

to achieve Material motion (ripple touch).

After updating to XF 2.5 (most likely as of 2.3.5, since fast renderers have been introduced with that release) my touch events have ceased to work. My custom renderer is assigned correctly, so are the Clickable and LongPressable properties, but nothing happens. Partially I have been able to work around the issue - at least for the moment - by subscribing to FrameRenderer.Touch and call OnClick from that event handler. This renders the app usable, but unfortunately lacks the visual feedback in form of the ripple touch effect.

Is there any way I can restore the original behavior? Is there any way to implement a clickable frame with ripple touch in XF 2.5?


Solution

  • With the help of this answer I have figured out a solution. Here's a draft of it:

    First of all I store a local reference to my RippleDrawable (see documentation)

    private void SetMaterialMotion()
    {
        TypedValue typedValue = new TypedValue();
        this.Context.Theme.ResolveAttribute(Android.Resource.Attribute.SelectableItemBackground, typedValue, true);
        this.Foreground = this.Resources.GetDrawable(typedValue.ResourceId, this.Context.Theme);
        this._layerDrawable = this.Foreground as RippleDrawable;
    }
    

    then I have to subscribe to the Touch event

    private void SubscribeClickEvent()
    {
        if (this.ClickableFrame.IsClickable)
        {
            this.Touch += ClickableFrameRenderer_Touch;
        }
    }
    

    In my ClickableFrameRenderer_Touch I set the Pressed property and the hotspot of the RippleDrawable

    private void ClickableFrameRenderer_Touch(object sender, TouchEventArgs e)
    {
        if(e.Event.Action == Android.Views.MotionEventActions.Down)
        {
            this.Pressed = true;
        }
    
        if(e.Event.Action == Android.Views.MotionEventActions.Up)
        {
            this._layerDrawable.SetHotspot(e.Event.GetX(), e.Event.GetY());
            this.Pressed = false;
        }
    }
    

    This will show the ripple touch motion (more or less) as expected. Of course this does not handle long presses, yet, but I'm on a way.

    Anyway, this is not a solution I like very much. I still think that there has to be a supported way on making FastRenderers.FrameRenderer clickable. If anyone knows it: Please share your knowledge.