Xamarin.Android ButtonRenderer longclick and touch - xamarin

I'm trying to implement the functionality of the android button through a ButtonRender. The problem occurs when I try to join ".LongClick" and ".touch", since it does not launch the long click event.
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
this.SetBackgroundResource(Resource.Drawable.button);
base.OnElementChanged(e);
Android.Widget.Button thisButton = Control as Android.Widget.Button;
thisButton.LongClickable = true;
thisButton.LongClick += delegate
{
string s = "";
};
thisButton.Touch += (object sender, Android.Views.View.TouchEventArgs e2) =>
{
if (e2.Event.Action == MotionEventActions.Down)
{
.
.
.
}
else if (e2.Event.Action == MotionEventActions.Up)
{
.
.
.
}
else if (e2.Event.Action == MotionEventActions.HoverExit || e2.Event.Action == MotionEventActions.Cancel)
{
.
.
.
}
else if (e2.Event.Action == MotionEventActions.Move)
{
.
.
.
}
};
}

To invoke the event of native control, we need to create event handlers in our custom control, and together an interface which inherits from IViewController in order to set values from renderers.
Here is my demo, first of all, create a custom button:
public class MyButton : Xamarin.Forms.Button, IMyButtonController
{
public event EventHandler Touched;
void IMyButtonController.SendTouched()
{
Touched?.Invoke(this, EventArgs.Empty);
}
public event EventHandler LongClicked;
void IMyButtonController.SendLongClicked()
{
LongClicked?.Invoke(this, EventArgs.Empty);
}
public event EventHandler Released;
void IMyButtonController.SendReleased()
{
Released?.Invoke(this, EventArgs.Empty);
}
}
The IMyButtonController inherits from IViewController like this:
public interface IMyButtonController : IViewController
{
void SendTouched();
void SendLongClicked();
void SendReleased();
}
Then in android project, implement the ButtonRenderer and IMyButtonController like this:
[assembly: ExportRenderer(typeof(MyButton), typeof(MyButtonRenderer))]
namespace ProjectNameSpace.Droid
{
public class MyButtonRenderer : Xamarin.Forms.Platform.Android.ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
if (Control != null)
{
Control.SetOnTouchListener(ButtonTouchListener.Instance.Value);
Control.LongClickable = true;
Control.SetOnLongClickListener(ButtonLongClickListener.Instance.Value);
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Control != null)
{
Control.SetOnTouchListener(null);
Control.SetOnLongClickListener(null);
}
}
base.Dispose(disposing);
}
private class ButtonTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
public static readonly Lazy<ButtonTouchListener> Instance = new Lazy<ButtonTouchListener>(() => new ButtonTouchListener());
public bool OnTouch(Android.Views.View v, Android.Views.MotionEvent e)
{
var renderer = v.Tag as ButtonRenderer;
if (renderer != null)
{
var buttonController = renderer.Element as IMyButtonController;
if (e.Action == Android.Views.MotionEventActions.Down)
{
buttonController?.SendTouched();
}
else if (e.Action == Android.Views.MotionEventActions.Up)
{
buttonController?.SendReleased();
}
}
return false;
}
}
private class ButtonLongClickListener : Java.Lang.Object, Android.Views.View.IOnLongClickListener
{
public static readonly Lazy<ButtonLongClickListener> Instance = new Lazy<ButtonLongClickListener>(() => new ButtonLongClickListener());
public bool OnLongClick(Android.Views.View v)
{
var renderer = v.Tag as ButtonRenderer;
((IMyButtonController)renderer?.Element)?.SendLongClicked();
return true;
}
}
}
}

Related

xamarin.forms how to select all text on focus with Entry?

I know the Xamarin.Android code, but how about Xamarin.IOS?
Xamarin.Android code:
public class CustomNumEntry : EntryRenderer
{
public CustomNumEntry(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (this.Control != null)
{
Control.SetPadding(10, 0, 0, 15);
Control.InputType = Android.Text.InputTypes.ClassNumber | Android.Text.InputTypes.NumberFlagSigned | Android.Text.InputTypes.NumberFlagDecimal;
Control.TextAlignment = Android.Views.TextAlignment.Center;
}
if (e.OldElement == null)
{
var nativeEditText = (global::Android.Widget.EditText)Control;
nativeEditText.SetSelectAllOnFocus(true);
}
}
}
for Xamarin iOS:
nativeTextField.EditingDidBegin += (object sender, EventArgs eIos) =>
{
nativeTextField.PerformSelector(new Selector("selectAll"), null, 0.0f);
};
for Xamarin.Forms using custom renderer for iOS(which you are looking for):
public class CustomNumEntry : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var nativeTextField = (UITextField)Control;
nativeTextField.EditingDidBegin += (object sender, EventArgs eIos) =>
{
nativeTextField.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
};
}
}
}
Solution:
You can implement it in method EditingStarted.Refer the following code
namespace xxx.iOS
{
public class MyEntryRenderer:EntryRenderer,IUITextFieldDelegate
{
public MyEntryRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
Control.WeakDelegate = this;
}
}
[Export("textFieldDidBeginEditing:")]
public void EditingStarted(UITextField textField)
{
textField.PerformSelector(new Selector("selectAll:"), null, 0.0f);
}
}
}
Here is the solution via this post:
private void Entry_Focused(object sender, FocusEventArgs e)
{
myEntry.CursorPosition = 0;
myEntry.SelectionLength = entryItem.Text.Length;
}
Example shows selection of all text on focused but you can do the same on demand too.

Xamarin.Forms Webview OnDetachedFromWindow crashed

When open renderer page many times, when back to last page, it crashed and log this exception:
System.NotSupportedException: Unable to activate instance of type
AppZPMC.Droid.Renderers.CustomVideoWebViewRenderer from native handle
0xff9919cc (key_handle 0xf2ff0de).
Here is my code:
public class CustomVideoWebViewRenderer : WebViewRenderer
{
string url;
Activity activity;
WebView webView;
View customView;
CustomVideoWebView element;
FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
FrameLayout fullscreenContainer;
WebChromeClient.ICustomViewCallback customViewCallback;
public CustomVideoWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
webView = this.Control;
activity = CrossCurrentActivity.Current.Activity;
if (e.NewElement != null)
{
element = e.NewElement as CustomVideoWebView;
url = element.VideoUrl;
}
if (webView != null)
{
InitWebView();
}
}
protected override void OnDetachedFromWindow()
{
if (Element == null)
{
return;
}
base.OnDetachedFromWindow();
if (Control != null)
{
Control.StopLoading();
Control.LoadUrl("");
Control.Reload();
Control.Destroy();
}
}
public void FullScreen(Activity pActivity)
{
element.IsFullScreen = true;
WindowManagerLayoutParams attrs = pActivity.Window.Attributes;
attrs.Flags |= WindowManagerFlags.Fullscreen;
attrs.Flags |= WindowManagerFlags.KeepScreenOn;
pActivity.Window.Attributes = attrs;
pActivity.RequestedOrientation = Android.Content.PM.ScreenOrientation.Landscape;
}
public void SmallScreen(Activity pActivity)
{
element.IsFullScreen = false;
WindowManagerLayoutParams attrs = pActivity.Window.Attributes;
attrs.Flags &= ~WindowManagerFlags.Fullscreen;
attrs.Flags &= ~WindowManagerFlags.KeepScreenOn;
pActivity.Window.Attributes = attrs;
pActivity.RequestedOrientation = Android.Content.PM.ScreenOrientation.UserPortrait;
}
private void InitWebView()
{
WebChromeClient wvcc = new WebChromeClient();
WebSettings webSettings = webView.Settings;
webSettings.JavaScriptEnabled = true;
webSettings.SetSupportZoom(false);
webSettings.CacheMode = CacheModes.NoCache;
webView.SetWebViewClient(new VideoWebViewClient());
FrameLayout frameLayout = new FrameLayout(activity.ApplicationContext);
frameLayout.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
webView.SetWebChromeClient(new VideoWebChromeClient(frameLayout, ShowCustomView, HideCustomView));
webView.LoadUrl(url);
}
private class VideoWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
{
view.LoadUrl(request.Url.ToString());
return base.ShouldOverrideUrlLoading(view, request);
}
}
private class VideoWebChromeClient : WebChromeClient
{
FrameLayout frameLayout;
Action HideCustomView;
Action<View, ICustomViewCallback> ShowCustomView;
public VideoWebChromeClient(FrameLayout frameLayout, Action<View, ICustomViewCallback> showCustomView, Action hideCustomView)
{
this.frameLayout = frameLayout;
ShowCustomView = showCustomView;
HideCustomView = hideCustomView;
}
public override View VideoLoadingProgressView => frameLayout;
public override void OnShowCustomView(View view, ICustomViewCallback callback)
{
ShowCustomView(view, callback);
base.OnShowCustomView(view, callback);
}
public override void OnHideCustomView()
{
HideCustomView();
base.OnHideCustomView();
}
}
private void ShowCustomView(View view, WebChromeClient.ICustomViewCallback callback)
{
if (customView != null)
{
callback.OnCustomViewHidden();
return;
}
FrameLayout decor = (FrameLayout)activity.Window.DecorView;
fullscreenContainer = new FullscreenHolder(activity.ApplicationContext);
fullscreenContainer.AddView(view, COVER_SCREEN_PARAMS);
decor.AddView(fullscreenContainer, COVER_SCREEN_PARAMS);
customView = view;
customViewCallback = callback;
FullScreen(activity);
}
private void HideCustomView()
{
if (customView == null)
{
return;
}
FrameLayout decor = (FrameLayout)activity.Window.DecorView;
decor.RemoveView(fullscreenContainer);
fullscreenContainer = null;
customView = null;
customViewCallback.OnCustomViewHidden();
webView.Visibility = ViewStates.Visible;
SmallScreen(activity);
}
private class FullscreenHolder : FrameLayout
{
public FullscreenHolder(Context ctx) : base(ctx)
{
SetBackgroundColor(Color.Black);
}
public override bool OnTouchEvent(MotionEvent e)
{
return true;
}
}
}
This is full crash log:
Java.Lang.Error: Exception of type 'Java.Lang.Error' was thrown.
java.lang.Error: Java callstack:
md5ff1c77b81adfd2c553f48967fc623ae5.CustomVideoWebViewRenderer.n_onDetachedFromWindow(Native Method)
md5ff1c77b81adfd2c553f48967fc623ae5.CustomVideoWebViewRenderer.onDetachedFromWindow(CustomVideoWebViewRenderer.java:45)
android.view.View.dispatchDetachedFromWindow(View.java:14595)
android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3074)
android.view.ViewGroup.removeAllViewsInLayout(ViewGroup.java:4792)
android.view.ViewGroup.removeAllViews(ViewGroup.java:4738)
md5270abb39e60627f0f200893b490a1ade.FragmentContainer.n_onDestroyView(Native Method)
md5270abb39e60627f0f200893b490a1ade.FragmentContainer.onDestroyView(FragmentContainer.java:59)
android.support.v4.app.Fragment.performDestroyView(Fragment.java:2480)
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1422)
android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1569)
android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:753)
android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2415)
android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2201)
android.support.v4.app.FragmentManagerImpl.optimizeAndExecuteOps(FragmentManager.java:2155)
android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2064)
android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:718)
android.os.Handler.handleCallback(Handler.java:742)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:157)
android.app.ActivityThread.main(ActivityThread.java:5551)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:742)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:632)
Its common problem (bug) in Xamarin.Forms, take a look at: https://github.com/xamarin/Xamarin.Forms/issues/1646, no fix yet :(

SwitchRender.SwitchRender()' is obsolete with Xamarin.Forms renderer

I am implementing code for SwitchRenderer in Android but it appears that there is an issue with the code as I get a warning:
'SwitchRender.SwitchRender()' is obsolete; 'This constuctor is
obsolets as of version 2.5. Please use SwitchRender(Context) instead.'
Does anyone have any ideas how I can resolve this problem:
using Japanese.Android;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Japanese;
[assembly: ExportRenderer(typeof(ExtSwitch), typeof(ExtSwitchRenderer))]
namespace Japanese.Android
{
public class ExtSwitchRenderer : SwitchRenderer
{
private ExtSwitch view;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Switch> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || e.NewElement == null)
return;
view = (ExtSwitch)Element;
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.JellyBean)
{
if (this.Control != null)
{
if (this.Control.Checked)
{
this.Control.TrackDrawable.SetColorFilter(view.SwitchOnColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
}
else
{
this.Control.TrackDrawable.SetColorFilter(view.SwitchOffColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
}
this.Control.CheckedChange += this.OnCheckedChange;
UpdateSwitchThumbImage(view);
}
//Control.TrackDrawable.SetColorFilter(view.SwitchBGColor.ToAndroid(), PorterDuff.Mode.Multiply);
}
}
private void UpdateSwitchThumbImage(CustomSwitch view)
{
if (!string.IsNullOrEmpty(view.SwitchThumbImage))
{
view.SwitchThumbImage = view.SwitchThumbImage.Replace(".jpg", "").Replace(".png", "");
int imgid = (int)typeof(Resource.Drawable).GetField(view.SwitchThumbImage).GetValue(null);
Control.SetThumbResource(Resource.Drawable.icon);
}
else
{
Control.ThumbDrawable.SetColorFilter(view.SwitchThumbColor.ToAndroid(), PorterDuff.Mode.Multiply);
// Control.SetTrackResource(Resource.Drawable.track);
}
}
private void OnCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
if (this.Control.Checked)
{
this.Control.TrackDrawable.SetColorFilter(view.SwitchOnColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
}
else
{
this.Control.TrackDrawable.SetColorFilter(view.SwitchOffColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
}
}
protected override void Dispose(bool disposing)
{
this.Control.CheckedChange -= this.OnCheckedChange;
base.Dispose(disposing);
}
}
}
Check out the answer here. The new way to do Android custom renderers would be this:
public class ExtSwitchRenderer : SwitchRenderer
{
private ExtSwitch view;
public ExtSwitchRenderer(Context context) : base(context) { }
....
}
Then if you actually need to use the Android context in your custom renderer, you would use the one passed in through the constructor, instead of Forms.Context for example.

Is there a way to recognize a short or long press on a screen with Xamarin.Forms?

I have an application that responds to a short tap on the screen. I do this by adding a gesture recognizer.
Is there a way that I can make it respond to either a short or a long press and have these call different methods?
You will have implement renderers for that. In case of iOS you can use UILongPressGestureRecognizer to detect a long-press action, while in case of Android, you can use GestureDetector to do the same.
Forms control
public class CustomView : ContentView
{
public event EventHandler<EventArgs> LongPressEvent;
public void RaiseLongPressEvent()
{
if (IsEnabled)
LongPressEvent?.Invoke(this, EventArgs.Empty);
}
}
iOS renderer
[assembly: ExportRenderer(typeof(CustomView), typeof(CustomViewRenderer))]
namespace AppNamespace.iOS
{
public class CustomViewRenderer : ViewRenderer<CustomView, UIView>
{
UILongPressGestureRecognizer longPressGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<CustomView> e)
{
longPressGestureRecognizer = longPressGestureRecognizer ??
new UILongPressGestureRecognizer(() =>
{
Element.RaiseLongPressEvent();
});
if (longPressGestureRecognizer != null)
{
if (e.NewElement == null)
{
this.RemoveGestureRecognizer(longPressGestureRecognizer);
}
else if (e.OldElement == null)
{
this.AddGestureRecognizer(longPressGestureRecognizer);
}
}
}
}
}
Android renderer
[assembly: ExportRenderer(typeof(CustomView), typeof(CustomViewRenderer))]
namespace AppNamespace.Droid
{
public class CustomViewRenderer : ViewRenderer<CustomView, Android.Views.View>
{
private CustomViewListener _listener;
private GestureDetector _detector;
public CustomViewListener Listener
{
get
{
return _listener;
}
}
public GestureDetector Detector
{
get
{
return _detector;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<CustomView> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
GenericMotion += HandleGenericMotion;
Touch += HandleTouch;
_listener = new CustomViewListener(Element);
_detector = new GestureDetector(_listener);
}
}
protected override void Dispose(bool disposing)
{
GenericMotion -= HandleGenericMotion;
Touch -= HandleTouch;
_listener = null;
_detector?.Dispose();
_detector = null;
base.Dispose(disposing);
}
void HandleTouch(object sender, TouchEventArgs e)
{
_detector.OnTouchEvent(e.Event);
}
void HandleGenericMotion(object sender, GenericMotionEventArgs e)
{
_detector.OnTouchEvent(e.Event);
}
}
public class CustomViewListener : GestureDetector.SimpleOnGestureListener
{
readonly CustomView _target;
public CustomViewListener(CustomView s)
{
_target = s;
}
public override void OnLongPress(MotionEvent e)
{
_target.RaiseLongPressEvent();
base.OnLongPress(e);
}
}
}
Sample Usage
<local:CustomView LongPressEvent="Handle_LongPress" />
Code-behind
void Handle_LongPressEvent(object sender, System.EventArgs e)
{
//handle long press event here
}
You can also customize above to add a command to make it more MVVM friendly.
You can refer this link for more details regarding gesture recognizers.
http://arteksoftware.com/gesture-recognizers-with-xamarin-forms/
You will have implement renderers for that. In case ios and android
best way for do that!

Image - long tap

Is there a way to detect a long tap on an image control in Xamarin Forms?
I'm using the carousel view to display images and would like to give the option to delete them by selecting with a long tap.
Based on your suggestions in the comments this is what I did:
(The purpose of the control is to be able to select an Image with a LongTap)
I defined my own Image control in the PCL:
IsSelected BindableProperty.
LongTap event.
public class MyImage:Image
{
private BindableProperty IsSelectedProperty = BindableProperty.Create("IsSelected", typeof(bool), typeof(MyImage), false);
public bool IsSelected {
get {
return (bool)GetValue(IsSelectedProperty);
}
set {
SetValue(IsSelectedProperty, value);
}
}
public event EventHandler LongClick;
public void OnLongClick()
{
IsSelected = !IsSelected;
if(IsSelected)
{
Opacity = 0.5;
}
else
{
Opacity = 1;
}
if (LongClick != null)
{
LongClick(this, EventArgs.Empty);
}
}
}
And this is my custom renderer: (Defined in the Android project)
[assembly: ExportRenderer(typeof(MyImage), typeof(MyImageRenderer))]
namespace PRISMCarouselView.Droid.Renderes
{
public class MyImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (Control != null)
{
ImageView androidSource = Control as ImageView;
MyImage myImage = e.NewElement as MyImage;
androidSource.LongClick += (object sender, LongClickEventArgs ee) =>
{
myImage.OnLongClick();
};
}
}
}
}
Edit 1:
Here's a slightly updated version, I use the BindingPropertyChangedDelegate to change the opacity of the image:
public class SelectableImage : Image
{
public SelectableImage()
{
}
private static BindableProperty IsSelectedProperty = BindableProperty.Create("IsSelected",
typeof(bool),
typeof(SelectableImage),
false, BindingMode.Default, null, (sender, o1, o2) => {
SelectableImage imageControl = sender as SelectableImage;
if(imageControl != null)
{
if(imageControl.IsSelected)
{
imageControl.Opacity = 0.5;
}else
{
imageControl.Opacity = 1;
}
}
});
public bool IsSelected {
get {
return (bool)GetValue(IsSelectedProperty);
}
set {
SetValue(IsSelectedProperty, value);
}
}
}
And the renderer:
[assembly: ExportRenderer(typeof(SelectableImage), typeof(SelectableImageRenderer))]
namespace Muserma.Apps.Droid.Renderer
{
public class SelectableImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (Control != null)
{
ImageView androidSource = Control as ImageView;
SelectableImage selectableImage = e.NewElement as SelectableImage;
androidSource.LongClick += (object sender, LongClickEventArgs ee) =>
{
selectableImage.IsSelected = !selectableImage.IsSelected;
};
}
}
}
}

Resources