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;
};
}
}
}
}
Related
Ok, so I've been trying to get the custom renderer working but I have a problem.
The Xamarin Forums discussion is here: https://forums.xamarin.com/discussion/comment/376813#Comment_376813
The problem is tht when I zoom out the webview stays in a part of the screen and doesn't fit the whole screen.
I add the code of the project:
MyWebView.cs
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace CofarLE_Ejemplo_5
{
public class MyWebView : WebView
{
public int ZoomInLevel
{
get { return (int)GetValue(ZoomInLevelProperty); }
set { SetValue(ZoomInLevelProperty, value); }
}
public bool EnableZoomControl
{
get { return (bool)GetValue(EnableZoomControlProperty); }
set { SetValue(EnableZoomControlProperty, value); }
}
public static readonly BindableProperty ZoomInLevelProperty = BindableProperty.Create(propertyName: "ZoomInLevel", returnType: typeof(int), declaringType: typeof(MyWebView), defaultValue: 100, propertyChanged: OnZoomInLevelPropertyChanged);
public static readonly BindableProperty EnableZoomControlProperty = BindableProperty.Create(propertyName: "EnableZoomControl", returnType: typeof(bool), declaringType: typeof(MyWebView), defaultValue: false, propertyChanged: OnEnableZoomChanged);
private static void OnZoomInLevelPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control1 = (MyWebView)bindable;
control1.ZoomInLevel = (int)newValue;
}
private static void OnEnableZoomChanged(BindableObject bindable, object oldValue, object newValue)
{
var control1 = (MyWebView)bindable;
control1.EnableZoomControl = (bool)newValue;
}
}
}
MyWebViewRendereriOS.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using CofarLE_Ejemplo_5;
using CofarLE_Ejemplo_5.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(MyWebView), typeof(MyWebViewRendereriOS))]
namespace CofarLE_Ejemplo_5.iOS
{
public class MyDelegate : UIScrollViewDelegate
{
public UIView myView;
public UIView ViewForZoom;
public MyDelegate(UIView view)
{
myView = view;
ViewForZoom = view;
}
public override UIView ViewForZoomingInScrollView(UIScrollView scrollView)
{
MessagingCenter.Subscribe<object, bool>(this, "zoom", (sender, arg) => {
if (arg == true)
{
myView.ContentMode = UIViewContentMode.ScaleToFill;
ViewForZoom = myView;
}
else
{
ViewForZoom = null;
}
});
return ViewForZoom;
}
}
public class MyWebViewRendereriOS : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (NativeView != null && e.NewElement != null)
{
var control1 = NativeView as UIWebView;
if (e.OldElement != null)
{
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
}
if (e.NewElement != null)
{
e.NewElement.PropertyChanged += OnElementPropertyChanged;
}
control1.ScalesPageToFit = true;
control1.ScrollView.Delegate = new MyDelegate(control1);
control1.ContentMode = UIViewContentMode.ScaleToFill;
control1.ScrollView.SizeToFit();
}
}
private void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var control1 = NativeView as UIWebView;
if (control1 == null)
{
return;
}
control1.ScalesPageToFit = true;
ScrollView.MaximumZoomScale = 2;
ScrollView.MinimumZoomScale = nfloat.Parse("0.5");
control1.ScrollView.MaximumZoomScale = 2;
control1.ScrollView.SizeToFit();
control1.ScrollView.MinimumZoomScale = nfloat.Parse("0.5");
}
}
}
you could try this,is it the effect you need(here is for Android)
1.create a custom view ZoomWebView:
public class ZoomWebView : View
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(ZoomWebView),
defaultValue: default(string));
public string Uri {
get { return (string)GetValue (UriProperty); }
set { SetValue (UriProperty, value); }
}
}
2. custom renderer ZoomWebViewRenderer
public class ZoomWebViewRenderer : ViewRenderer<ZoomWebView, Android.Webkit.WebView>
{
Context _context;
public ZoomWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<ZoomWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var webView = new Android.Webkit.WebView(_context);
webView.Settings.UseWideViewPort = true;
webView.Settings.LoadWithOverviewMode = true;
webView.Settings.SetSupportZoom(true);
webView.Settings.BuiltInZoomControls = true;
webView.Settings.DisplayZoomControls = false;
webView.SetWebViewClient(new ZoomWebViewClient());
SetNativeControl(webView);
Control.LoadUrl(Element.Uri);
}
}
class ZoomWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
{
view.LoadUrl(request.Url.ToString());
return true;
}
}
}
3.call in the page xaml:
<ContentPage.Content>
<local:ZoomWebView Uri="https://www.microsoft.com" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</ContentPage.Content>
i have created one custom entry field with floating label using custom renderer like from this uri https://github.com/AlejandroRuiz/FloatingTextEntry
i need to change entry underline color as well placeholder color on button click .
Custom Entry Code :
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Xamarin.Forms;
namespace BloodTrace.CustomControls
{
public class XfxEntry : Entry
{
public static readonly BindableProperty ErrorTextProperty = BindableProperty.Create(nameof(ErrorText),
typeof(string),
typeof(XfxEntry),
default(string), propertyChanged: OnErrorTextChangedInternal);
public static readonly BindableProperty FloatingHintEnabledProperty = BindableProperty.Create(nameof(FloatingHintEnabled),
typeof(bool),
typeof(XfxEntry),
true);
public static readonly BindableProperty ActivePlaceholderColorProperty = BindableProperty.Create(nameof(ActivePlaceholderColor),
typeof(Color),
typeof(XfxEntry),
Color.Accent);
/// <summary>
/// ActivePlaceholderColor summary. This is a bindable property.
/// </summary>
public Color ActivePlaceholderColor
{
get { return (Color)GetValue(ActivePlaceholderColorProperty); }
set { SetValue(ActivePlaceholderColorProperty, value); }
}
public Color ErrorPlaceholderColor
{
get { return (Color)GetValue(ErrorPlaceholderColorProperty); }
set { SetValue(ErrorPlaceholderColorProperty, value); }
}
public static readonly BindableProperty ErrorPlaceholderColorProperty = BindableProperty.Create(nameof(ErrorPlaceholderColor),
typeof(Color),
typeof(XfxEntry),
Color.Green);
/// <summary>
/// <c>true</c> to float the hint into a label, otherwise <c>false</c>. This is a bindable property.
/// </summary>
public bool FloatingHintEnabled
{
get { return (bool)GetValue(FloatingHintEnabledProperty); }
set { SetValue(FloatingHintEnabledProperty, value); }
}
/// <summary>
/// Gets or Sets whether or not the Error Style is 'Underline' or 'None'
/// </summary>
public ErrorDisplay ErrorDisplay { get; set; } = ErrorDisplay.None;
public bool IsErrorPlaceholderColorChange { get; set; } = false;
/// <summary>
/// Error text for the entry. An empty string removes the error. This is a bindable property.
/// </summary>
public string ErrorText
{
get { return (string)GetValue(ErrorTextProperty); }
set { SetValue(ErrorTextProperty, value); }
}
/// <summary>
/// Raised when the value of the error text changes
/// </summary>
public event EventHandler<TextChangedEventArgs> ErrorTextChanged;
private static void OnErrorTextChangedInternal(BindableObject bindable, object oldvalue, object newvalue)
{
var materialEntry = (XfxEntry)bindable;
materialEntry.OnErrorTextChanged(bindable, oldvalue, newvalue);
materialEntry.ErrorTextChanged?.Invoke(materialEntry, new TextChangedEventArgs((string)oldvalue, (string)newvalue));
}
protected virtual void OnErrorTextChanged(BindableObject bindable, object oldvalue, object newvalue) { }
}
public enum ErrorDisplay
{
Underline,
None
}
}
Android Renderer : -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using BloodTrace;
using BloodTrace.Droid.Renderer;
using Application = Android.App.Application;
using Color = Xamarin.Forms.Color;
using AColor = Android.Graphics.Color;
using FormsAppCompat = Xamarin.Forms.Platform.Android.AppCompat;
using BloodTrace.CustomControls;
using Android.Support.Design.Widget;
using Android.Text;
using Xamarin.Forms;
using Android.Content.Res;
using Android.Views.InputMethods;
using Java.Lang;
using Android.Support.V7.Widget;
using Android.Util;
using Xamarin.Forms.Platform.Android;
using System.ComponentModel;
using Android.Text.Method;
using Android.Support.V4.View;
using BloodTrace.Droid.Extensions;
using BloodTraceSharedProject.Extensions;
[assembly: ExportRenderer(typeof(XfxEntry), typeof(XfxEntryRendererDroid))]
namespace BloodTrace.Droid.Renderer
{
public class XfxEntryRendererDroid : FormsAppCompat.ViewRenderer<XfxEntry, TextInputLayout>,
ITextWatcher,
TextView.IOnEditorActionListener
{
private bool _hasFocus;
private ColorStateList _defaultTextColor;
public XfxEntryRendererDroid(Context context) : base(context)
{
AutoPackage = false;
}
protected EditText EditText => Control.EditText;
public bool OnEditorAction(TextView v, ImeAction actionId, KeyEvent e)
{
if ((actionId == ImeAction.Done) || ((actionId == ImeAction.ImeNull) && (e.KeyCode == Keycode.Enter)))
{
Control.ClearFocus();
HideKeyboard();
((IEntryController)Element).SendCompleted();
}
return true;
}
public virtual void AfterTextChanged(IEditable s)
{
}
public virtual void BeforeTextChanged(ICharSequence s, int start, int count, int after)
{
}
public virtual void OnTextChanged(ICharSequence s, int start, int before, int count)
{
if (string.IsNullOrWhiteSpace(Element.Text) && (s.Length() == 0)) return;
((IElementController)Element).SetValueFromRenderer(Entry.TextProperty, s.ToString());
}
protected override TextInputLayout CreateNativeControl()
{
var textInputLayout = new TextInputLayout(Context);
var editText = new AppCompatEditText(Context)
{
SupportBackgroundTintList = ColorStateList.ValueOf(GetPlaceholderColor())
};
editText.SetTextSize(ComplexUnitType.Sp, (float)Element.FontSize);
textInputLayout.AddView(editText);
return textInputLayout;
}
protected override void OnElementChanged(ElementChangedEventArgs<XfxEntry> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
if (Control != null)
EditText.FocusChange -= ControlOnFocusChange;
if (e.NewElement != null)
{
var ctrl = CreateNativeControl();
SetNativeControl(ctrl);
if (!string.IsNullOrWhiteSpace(Element.AutomationId))
EditText.ContentDescription = Element.AutomationId;
_defaultTextColor = EditText.TextColors;
Focusable = true;
EditText.ShowSoftInputOnFocus = true;
// Subscribe
EditText.FocusChange += ControlOnFocusChange;
EditText.AddTextChangedListener(this);
EditText.SetOnEditorActionListener(this);
EditText.ImeOptions = ImeAction.Done;
SetText();
SetHintText();
SetErrorText();
SetFontAttributesSizeAndFamily();
SetInputType();
SetTextColor();
SetHorizontalTextAlignment();
SetFloatingHintEnabled();
SetIsEnabled();
SetErrorDisplay();
SetLabelAndUnderlineColor();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.PlaceholderProperty.PropertyName)
SetHintText();
else if (e.PropertyName == XfxEntry.ErrorTextProperty.PropertyName)
SetErrorText();
else if (e.PropertyName == Entry.IsPasswordProperty.PropertyName ||
e.PropertyName == InputView.KeyboardProperty.PropertyName)
SetInputType();
else if (e.PropertyName == Entry.TextProperty.PropertyName)
SetText();
else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName)
SetHorizontalTextAlignment();
else if (e.PropertyName == XfxEntry.FloatingHintEnabledProperty.PropertyName)
SetFloatingHintEnabled();
else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
SetIsEnabled();
else if ((e.PropertyName == Entry.FontAttributesProperty.PropertyName) ||
(e.PropertyName == Entry.FontFamilyProperty.PropertyName) ||
(e.PropertyName == Entry.FontSizeProperty.PropertyName))
SetFontAttributesSizeAndFamily();
else if (e.PropertyName == XfxEntry.ActivePlaceholderColorProperty.PropertyName ||
e.PropertyName == Entry.PlaceholderColorProperty.PropertyName)
SetLabelAndUnderlineColor();
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
SetTextColor();
}
private void ControlOnFocusChange(object sender, FocusChangeEventArgs args)
{
_hasFocus = args.HasFocus;
if (_hasFocus)
{
var manager = (InputMethodManager)Application.Context.GetSystemService(Context.InputMethodService);
EditText.PostDelayed(() =>
{
EditText.RequestFocus();
manager.ShowSoftInput(EditText, 0);
},
100);
}
var isFocusedPropertyKey = Element.GetInternalField<BindablePropertyKey>("IsFocusedPropertyKey");
((IElementController)Element).SetValueFromRenderer(isFocusedPropertyKey, _hasFocus);
//This will change floating label entry SetUnderlineColor >>>> SetUnderlineColor color basedon focus or non focus
SetUnderlineColor(_hasFocus ? GetActivePlaceholderColor() : GetPlaceholderColor());
}
protected AColor GetPlaceholderColor() => Element.PlaceholderColor.ToAndroid(Color.FromHex("#80000000"));
private AColor GetActivePlaceholderColor() => Element.ActivePlaceholderColor.ToAndroid(global::Android.Resource.Attribute.ColorAccent, Context);
protected virtual void SetLabelAndUnderlineColor()
{
var defaultColor = GetPlaceholderColor();
var activeColor = GetActivePlaceholderColor();
SetHintLabelDefaultColor(defaultColor);
SetHintLabelActiveColor(activeColor);
SetUnderlineColor(_hasFocus ? activeColor : defaultColor);
}
//This will change entry under line color
private void SetUnderlineColor(AColor color)
{
var element = (ITintableBackgroundView)EditText;
element.SupportBackgroundTintList = ColorStateList.ValueOf(color);
}
private void SetHintLabelActiveColor(AColor color)
{
var hintText = Control.Class.GetDeclaredField("mFocusedTextColor");
hintText.Accessible = true;
hintText.Set(Control, new ColorStateList(new int[][] { new[] { 0 } }, new int[] { color }));
}
private void SetHintLabelDefaultColor(AColor color)
{
var hint = Control.Class.GetDeclaredField("mDefaultTextColor");
hint.Accessible = true;
hint.Set(Control, new ColorStateList(new int[][] { new[] { 0 } }, new int[] { color }));
}
private void SetText()
{
if (EditText.Text != Element.Text)
{
EditText.Text = Element.Text;
if (EditText.IsFocused)
EditText.SetSelection(EditText.Text.Length);
}
}
private void SetHintText()
{
Control.Hint = Element.Placeholder;
}
private void SetTextColor()
{
if (Element.TextColor == Color.Default)
{
EditText.SetTextColor(_defaultTextColor);
}
else
{
EditText.SetTextColor(Element.TextColor.ToAndroid());
}
}
private void SetHorizontalTextAlignment()
{
switch (Element.HorizontalTextAlignment)
{
case Xamarin.Forms.TextAlignment.Center:
EditText.Gravity = GravityFlags.CenterHorizontal;
break;
case Xamarin.Forms.TextAlignment.End:
EditText.Gravity = GravityFlags.Right;
break;
default:
EditText.Gravity = GravityFlags.Left;
break;
}
}
public void SetFloatingHintEnabled()
{
Control.HintEnabled = Element.FloatingHintEnabled;
Control.HintAnimationEnabled = Element.FloatingHintEnabled;
SetFontAttributesSizeAndFamily();
}
public void SetErrorDisplay()
{
switch (Element.ErrorDisplay)
{
case ErrorDisplay.None:
Control.ErrorEnabled = false;
break;
case ErrorDisplay.Underline:
Control.ErrorEnabled = true;
break;
}
}
protected void HideKeyboard()
{
var manager = (InputMethodManager)Application.Context.GetSystemService(Context.InputMethodService);
manager.HideSoftInputFromWindow(EditText.WindowToken, 0);
}
//here we can set entry floating label >>> input text size
private void SetFontAttributesSizeAndFamily()
{
EditText.Typeface = Control.Typeface = Element.ToTypeface();
EditText.SetTextSize(ComplexUnitType.Sp, (float)Element.FontSize);
//var a = (float)Element.FontSize; //18
}
private void SetErrorText()
{
Control.Error = !string.IsNullOrEmpty(Element.ErrorText) ? Element.ErrorText : null;
}
private void SetIsEnabled()
{
EditText.Enabled = Element.IsEnabled;
}
private void SetInputType()
{
EditText.InputType = Element.Keyboard.ToInputType();
if (Element.IsPassword && (EditText.InputType & InputTypes.ClassText) == InputTypes.ClassText)
{
EditText.TransformationMethod = new PasswordTransformationMethod();
EditText.InputType = EditText.InputType | InputTypes.TextVariationPassword;
}
if (Element.IsPassword && (EditText.InputType & InputTypes.ClassNumber) == InputTypes.ClassNumber)
{
EditText.TransformationMethod = new PasswordTransformationMethod();
EditText.InputType = EditText.InputType | InputTypes.NumberVariationPassword;
}
}
}
}
XML : -
<xfx:XfxEntry x:Name="EntPassword" Placeholder="Password" IsPassword="True" Focused="EntPassword_Focused"></xfx:XfxEntry>
<Button Text="Sign Up" BackgroundColor="#C62729" TextColor="White" x:Name="BtnSignUp" Clicked="BtnSignUp_Clicked" ></Button>
Please help.
Thanks in advance
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 :(
I have an image in Xamarin Forms. I want to use the native android features to interact with this image. For example, when the image is tapped, I want to know the x,y coordinates of where the image was tapped. I can use Android ImageView but I'm not sure how to cast the Xamarin Forms image to Android ImageView
[assembly: ExportRenderer(typeof(Image), typeof(FloorplanImageRenderer))]
namespace EmployeeApp.Droid.Platform
{
public class FloorplanImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
if (Control == null)
{
var imageView = (ImageView)e.NewElement; // This is not right
}
base.OnElementChanged(e);
}
}
}
But the Control is null....
No, it shouldn't be null. Back to your question, I think first of all, you will need to attach a touch event to the image control in PCL and create a property to hold the coordinate when image get touched. And I think here in your code:
[assembly: ExportRenderer(typeof(Image), typeof(FloorplanImageRenderer))]
I think the Image here should be your custom image control which inherits from Image in PCL.
Create a interface for touch event:
public interface IFloorplanImageController
{
void SendTouched();
}
Create a custom control for image:
public class FloorplanImage : Image, IFloorplanImageController
{
public event EventHandler Touched;
public void SendTouched()
{
Touched?.Invoke(this, EventArgs.Empty);
}
public Tuple<float, float> TouchedCoordinate
{
get { return (Tuple<float, float>)GetValue(TouchedCoordinateProperty); }
set { SetValue(TouchedCoordinateProperty, value); }
}
public static readonly BindableProperty TouchedCoordinateProperty =
BindableProperty.Create(
propertyName: "TouchedCoordinate",
returnType: typeof(Tuple<float, float>),
declaringType: typeof(FloorplanImage),
defaultValue: new Tuple<float, float>(0, 0),
propertyChanged: OnPropertyChanged);
public static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
}
}
Implement the custom renderer:
[assembly: ExportRenderer(typeof(FloorplanImage), typeof(FloorplanImageRenderer))]
namespace EmployeeApp.Droid.Platform
{
public class FloorplanImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
if (Control != null)
{
Control.Clickable = true;
Control.SetOnTouchListener(ImageTouchListener.Instance.Value);
Control.SetTag(Control.Id, new JavaObjectWrapper<FloorplanImage> { Obj = Element as FloorplanImage });
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Control != null)
{
Control.SetOnTouchListener(null);
}
}
base.Dispose(disposing);
}
private class ImageTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
public static readonly Lazy<ImageTouchListener> Instance = new Lazy<ImageTouchListener>(
() => new ImageTouchListener());
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
var obj = v.GetTag(v.Id) as JavaObjectWrapper<FloorplanImage>;
var element = obj.Obj;
var controller = element as IFloorplanImageController;
if (e.Action == Android.Views.MotionEventActions.Down)
{
var x = e.GetX();
var y = e.GetY();
element.TouchedCoordinate = new Tuple<float, float>(x, y);
controller?.SendTouched();
}
else if (e.Action == Android.Views.MotionEventActions.Up)
{
}
return false;
}
}
}
public class JavaObjectWrapper<T> : Java.Lang.Object
{
public T Obj { get; set; }
}
}
Use this control like this:
<local:FloorplanImage HeightRequest="300" x:Name="image" WidthRequest="300"
Aspect="AspectFit" Touched="image_Touched" />
code behind:
private void image_Touched(object sender, EventArgs e)
{
var cor = image.TouchedCoordinate;
}
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;
}
}
}
}