📜 ⬆️ ⬇️

Add the effect of clicking on Xamarin.Forms

Xamarin.Forms is gaining momentum and, unfortunately, very few options are available from the box, everything needs to be added through the Dependency service or renderers. On this wave there are a lot of different libraries, which often add basic functionality.
My decision is no exception.


My task was to make a small extension, which allows adding the effect of clicking on almost any element for iOS and Android.


Initially, I had the idea to create a container with the effect of pressing and add the necessary elements to it. From this idea had to be abandoned in the form of additional nesting and incorrect selection. That is, putting in this container a non-rectangular element of the CircleImage or Frame type, I would get a selection outside the rounded area.


It would be foolish to rewrite and expand all controls, so I decided to add a static extension.


How it should look


For Android 5+, obviously, you need to use the Ripple effect. But for iOS and Android <5, this solution will look inappropriate. For these platforms, I decided to implement a color animated selection triggered when touched.


Implementation


PCL


To begin with, a static TouchEffect class with BindableProperty was created in the PCL project, responsible for the color of the effect.


Android


You need to define a variable that identifies whether to use the Ripple effect or not, depending on the version of Android:


public bool EnableRipple => Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop; 

The implementation of standard waves for android is quite simple:


 private void AddRipple() { if (Element is Layout) { _rippleOverlay = new FrameLayout(Container.Context) { LayoutParameters = new ViewGroup.LayoutParams(-1, -1) }; _rippleListener = new ContainerOnLayoutChangeListener(_rippleOverlay); _view.AddOnLayoutChangeListener(_rippleListener); ((ViewGroup)_view).AddView(_rippleOverlay); _rippleOverlay.BringToFront(); _rippleOverlay.Foreground = CreateRipple(Color.Accent.ToAndroid()); } else { _orgDrawable = _view.Background; _view.Background = CreateRipple(Color.Accent.ToAndroid()); } _ripple.SetColor(GetPressedColorSelector(_color)); } private void RemoveRipple() { if (Element is Layout) { var viewgrp = (ViewGroup)_view; viewgrp?.RemoveOnLayoutChangeListener(_rippleListener); viewgrp?.RemoveView(_rippleOverlay); _rippleListener?.Dispose(); _rippleListener = null; _rippleOverlay?.Dispose(); _rippleOverlay = null; } else { _view.Background = _orgDrawable; _orgDrawable?.Dispose(); _orgDrawable = null; } _ripple?.Dispose(); _ripple = null; } private RippleDrawable CreateRipple(Android.Graphics.Color color) { if (Element is Layout) { var mask = new ColorDrawable(Android.Graphics.Color.White); return _ripple = new RippleDrawable(GetPressedColorSelector(color), null, mask); } var back = _view.Background; if (back == null) { var mask = new ColorDrawable(Android.Graphics.Color.White); return _ripple = new RippleDrawable(GetPressedColorSelector(color), null, mask); } else if (back is RippleDrawable) { _ripple = (RippleDrawable) back.GetConstantState().NewDrawable(); _ripple.SetColor(GetPressedColorSelector(color)); return _ripple; } else { return _ripple = new RippleDrawable(GetPressedColorSelector(color), back, null); } } 

The background is taken from the control and an effect is added to it.


For older versions of android, I decided to add FrameLayout on top of an element with background channel Alpha animation. This method is subscribed to the element's Touch event:


 private void OnTouch(object sender, View.TouchEventArgs args) { switch (args.Event.Action) { case MotionEventActions.Down: Container.RemoveView(_layer); Container.AddView(_layer); _layer.Top = 0; _layer.Left = 0; _layer.Right = _view.Width; _layer.Bottom = _view.Height; _layer.BringToFront(); TapAnimation(250, 0, 65, false); break; case MotionEventActions.Up: case MotionEventActions.Cancel: TapAnimation(250, 65, 0); break; } } 

Which, when clicked, adds a new layout to the container with an A-channel animation from 0 to 65, and when released, animates back from 65 to 0 and deletes it from the container.


Then, in the OnAttached method, we define what to do, create a Ripple effect or subscribe to a Touch:


 if (EnableRipple) AddRipple(); else _view.Touch += OnTouch; 

iOS


For iOS, the approach is similar to the previous step, a UIView is added over the main element when pressed, and the A-channel is also animated. For this, UITapGestureRecognizer and UILongPressGestureRecognizer are created and added to the element:


 _tapGesture = new UITapGestureRecognizer(async (obj) => { await TapAnimation(0.3, _alpha, 0); }); _longTapGesture = new UILongPressGestureRecognizer(async (obj) => { switch (obj.State) { case UIGestureRecognizerState.Began: await TapAnimation(0.5, 0, _alpha, false); break; case UIGestureRecognizerState.Ended: case UIGestureRecognizerState.Cancelled: case UIGestureRecognizerState.Failed: await TapAnimation(0.5, _alpha); break; } }); _view.AddGestureRecognizer(_longTapGesture); _view.AddGestureRecognizer(_tapGesture); 

With a long press, another animation time is set and, unlike a simple press, the mask is removed only after releasing the finger.


Actually everything.


Using


XAML:


 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:XamEffects.Sample" xmlns:xe="clr-namespace:XamEffects;assembly=XamEffects" x:Class="XamEffects.Sample.MainPage"> <Grid HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="100" WidthRequest="200" BackgroundColor="LightGray" xe:TouchEffect.Color="Red"> <Label Text="Test touch effect" HorizontalOptions="Center" VerticalOptions="Center"/> </Grid> </ContentPage> 

iOSAndroid API> = 21Android API <21

Results


I gave the basic idea of ​​implementing a touch effect, all the code, as well as Nuget packages are available on GitHub .


PS: I have a small experience in the native development, I will be glad to advice that you can improve / modify.
PPS: Habrastorage has a bit clumsily transformed gifs.


')

Source: https://habr.com/ru/post/334166/


All Articles