XamEffects icon indicating copy to clipboard operation
XamEffects copied to clipboard

TouchEffect and scrolling (Android)

Open AlleSchonWeg opened this issue 3 years ago • 7 comments

Hi, i have the same problem as described in this post: https://github.com/mrxten/XamEffects/issues/41 and i played a litte bit with your code. If i comment out the TouchCollector.Add and TouchCollector.Delete calls the ripple effect is not called while scrolling, like items in a ListView. But my problem is now, that the Command is not executed. Do you still support your library and could you fix it?

AlleSchonWeg avatar Jun 01 '21 06:06 AlleSchonWeg

I got the Command working. I added a TapGestureRecognizer to my control with the touch effect. In the effect is used the click event from the viewOverlay: _viewOverlay.Click += _viewOverlay_Click; and fire the command:

	private void _viewOverlay_Click(object sender, EventArgs e) {
			foreach(var recognizer in ((Xamarin.Forms.View)Element).GestureRecognizers) {
				if(recognizer is TapGestureRecognizer gesture) {
					if(gesture.Command != null) {
						gesture.Command.Execute(gesture.CommandParameter);
					}
				}
			}
		}

My question is now: Can you remove the TouchCollector from the effect and implement the click event like i do? Then it should work in ScrollViews.

AlleSchonWeg avatar Jun 01 '21 09:06 AlleSchonWeg

Hello. I thinking about new version with fixes all bugs. But haven't time for that...

mrxten avatar Jun 02 '21 19:06 mrxten

@AlleSchonWeg Could you please share your whole solution?

isness avatar Aug 09 '21 13:08 isness

@isness : Here are the effect for android:

using System;
using System.ComponentModel;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Color = Android.Graphics.Color;
using ListView = Android.Widget.ListView;
using ScrollView = Android.Widget.ScrollView;
using View = Android.Views.View;
using VisualTouchFeedbackRoutingEffect = xxxx.Droid.Effects.VisualTouchFeedbackRoutingEffect;

[assembly: ExportEffect(typeof(VisualTouchFeedbackRoutingEffect), nameof(xxxx.Effects.VisualTouchFeedbackRoutingEffect))]

namespace xxx.Droid.Effects {
	class VisualTouchFeedbackRoutingEffect : PlatformEffect {
		public bool IsDisposed => (Container as IVisualElementRenderer)?.Element == null;
		public View View => Control ?? Container;

		//Color _color;
		//RippleDrawable _ripple;
		FrameLayout _viewOverlay;

		protected override void OnAttached() {
			if(Control is ListView || Control is ScrollView) {
				return;
			}
			View.Clickable = true;
			View.LongClickable = true;
			_viewOverlay = new FrameLayout(Container.Context) {
				LayoutParameters = new ViewGroup.LayoutParams(-1, -1),
				Clickable = false,
				Focusable = false,
			};
			Container.LayoutChange += ViewOnLayoutChange;
			_viewOverlay.Touch += _viewOverlay_Touch;
			_viewOverlay.Click += _viewOverlay_Click;
			_viewOverlay.Foreground = CreateRipple(Android.Resource.Attribute.SelectableItemBackground);
			Container.AddView(_viewOverlay);
			_viewOverlay.BringToFront();
		}

		private void _viewOverlay_Touch(object sender, View.TouchEventArgs e) {
			e.Handled = false;
			switch(e.Event.Action) {
				case MotionEventActions.Down: {
						if(IsDisposed || !(_viewOverlay.Foreground is RippleDrawable bc))
							return;
						bc.SetHotspot(e.Event.GetX(), e.Event.GetY());
						break;
					}
			}
		}

		private void _viewOverlay_Click(object sender, EventArgs e) {
			foreach(var recognizer in ((Xamarin.Forms.View)Element).GestureRecognizers) {
				if(recognizer is TapGestureRecognizer gesture) {
					if(gesture.Command != null) {
						gesture.Command.Execute(gesture.CommandParameter);
					}
				}
			}
		}

		RippleDrawable CreateRipple(int color) {
			var attrs = new int[] { color };

			var typedArray = Xamarin.Essentials.Platform.AppContext.ObtainStyledAttributes(attrs);
			var drawableFromTheme = (RippleDrawable)typedArray.GetDrawable(0);
			typedArray.Recycle();
			return drawableFromTheme;
		}

		static ColorStateList GetPressedColorSelector(int pressedColor) {
			return new ColorStateList(
				new[] { new int[] { } },
				new[] { pressedColor, });
		}

		protected override void OnDetached() {
			if(IsDisposed)
				return;

			Container.RemoveView(_viewOverlay);
			_viewOverlay.Click -= _viewOverlay_Click;
			_viewOverlay.Touch -= _viewOverlay_Touch;
			_viewOverlay.Pressed = false;
			_viewOverlay.Foreground = null;
			_viewOverlay.Dispose();
			Container.LayoutChange -= ViewOnLayoutChange;
			//_ripple?.Dispose();
		}

		private void ViewOnLayoutChange(object sender, View.LayoutChangeEventArgs layoutChangeEventArgs) {
			var group = (ViewGroup)sender;
			if(group == null || IsDisposed)
				return;
			_viewOverlay.Right = group.Width;
			_viewOverlay.Bottom = group.Height;
		}

	}
}

And the effect in the shared project:

using System;
using Xamarin.Forms;
using XamEffects;

namespace xxx.Effects {
	public class VisualTouchFeedbackRoutingEffect : RoutingEffect {
		public VisualTouchFeedbackRoutingEffect() : base($"{nameof(xxx)}.{nameof(VisualTouchFeedbackRoutingEffect)}") {
		}
	}

	public static class VisualTouchFeedbackEffect {
		public static readonly BindableProperty ColorProperty =
			BindableProperty.CreateAttached(
				"Color",
				typeof(Color),
				typeof(VisualTouchFeedbackEffect),
				Color.Default,
				propertyChanged: PropertyChanged
			);

		public static void SetColor(BindableObject view, Color value) {
			view.SetValue(ColorProperty, value);
		}

		public static Color GetColor(BindableObject view) {
			return (Color)view.GetValue(ColorProperty);
		}

		private static void PropertyChanged(BindableObject bindable, object oldValue, object newValue) {
			var color = GetColor(bindable);
			//Für iOS das TouchEffect Nuget nutzen.
			if(Device.RuntimePlatform == Device.iOS) {
				TouchEffect.SetColor(bindable, color);
			}
			//Für Android abgewandelte Form des TouchEffect Nuget
			else if(Device.RuntimePlatform == Device.Android) {
				if(bindable is View view) {
					SetColor(bindable, Color.Default);
					EffectsConfig.SetChildrenInputTransparent(view, true);
					view.Effects.Add(new VisualTouchFeedbackRoutingEffect());
				}
			}
			else {
				throw new NotSupportedException($"{Device.RuntimePlatform} is not supported!");
			}
		}
	}
}

AlleSchonWeg avatar Aug 10 '21 09:08 AlleSchonWeg

@AlleSchonWeg Hmm, I can't get it to work. Have you tried it on CollectionView? My collection view starts like this:

<CollectionView x:Name="HistoryView" ItemsSource="{Binding HistoryItems}" SelectionMode="None" Margin="0" IsGrouped="False"> <CollectionView.ItemTemplate> <DataTemplate> <StackLayout Padding="0" Margin="0" > <Grid Padding="10,15,10,15" Margin="0" x:DataType="model:HistoryItem"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="3*"></ColumnDefinition> </Grid.ColumnDefinitions>

isness avatar Aug 10 '21 15:08 isness

@isness I use the Sharpnado HorizontalListView. Not sure, if XF CollectionView works. Do you attach the effect like this: effects:VisualTouchFeedbackEffect.Color="color.red" to your view ?

AlleSchonWeg avatar Aug 10 '21 19:08 AlleSchonWeg

Yes, I attached it the way you described. Can't get it to work on collectionview. Maybe it doesn't work on latest XF version, I have no idea. The same bug exists on XamarinCommunityToolkit, though. It would be great to have a workaround for it: https://github.com/xamarin/XamarinCommunityToolkit/issues/1261

isness avatar Aug 17 '21 14:08 isness