How can I interact with a xamarin forms image in Android? - xamarin

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;
}

Related

Xamarin Forms Webview Custom Renderer Zoom not working

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>

How can I share the value of a field between back-end C# and a renderer?

My C# looks like this:
public App()
{
InitializeComponent();
MainPage = new Japanese.MainPage();
}
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
var phrasesPage = new NavigationPage(new PhrasesPage())
{
Title = "Play",
Icon = "ionicons-2-0-1-ios-play-outline-25.png"
};
public partial class PhrasesPage : ContentPage
{
public PhrasesFrame phrasesFrame;
public PhrasesPage()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
App.phrasesPage = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
App.dataChange = true;
phrasesFrame = new PhrasesFrame(this);
phrasesStackLayout.Children.Add(phrasesFrame);
}
public partial class PhrasesFrame : Frame
{
private async Task ShowCard()
{
if (pauseCard == false)
..
and I have an iOS renderer for a tab page
public class TabbedPageRenderer : TabbedRenderer
{
private MainPage _page;
private void OnTabBarReselected(object sender, UITabBarSelectionEventArgs e)
{
...
pauseCard = false;
...
My problem is there is no connection between the two and I would like to know how I can make it so that pauseCard could be set in one place and read in another.
Here is a simple custom Entry example using a bindable bool property that gets changed from the renderer every time the text changes in the entry.
Entry subclass w/ a bindable property called OnOff (bool)
public class CustomPropertyEntry : Entry
{
public static readonly BindableProperty OnOffProperty = BindableProperty.Create(
propertyName: "OnOff",
returnType: typeof(bool),
declaringType: typeof(CustomPropertyEntry),
defaultValue: false);
public bool OnOff
{
get { return (bool)GetValue(OnOffProperty); }
set { SetValue(OnOffProperty, value); }
}
}
iOS Renderer
Note: I keep a reference to the instance of the CustomPropertyEntry passed into OnElementChanged so later I can set its custom property when needed.
public class CustomPropertyEntryRenderer : ViewRenderer<CustomPropertyEntry, UITextField>
{
UITextField textField;
CustomPropertyEntry entry;
protected override void OnElementChanged(ElementChangedEventArgs<CustomPropertyEntry> e)
{
base.OnElementChanged(e);
if (Control == null)
{
textField = new UITextField();
SetNativeControl(textField);
}
if (e.OldElement != null)
{
textField.RemoveTarget(EditChangedHandler, UIControlEvent.EditingChanged);
entry = null;
}
if (e.NewElement != null)
{
textField.AddTarget(EditChangedHandler, UIControlEvent.EditingChanged);
entry = e.NewElement;
}
}
void EditChangedHandler(object sender, EventArgs e)
{
entry.OnOff = !entry.OnOff;
}
}
XAML Example:
<local:CustomPropertyEntry x:Name="customEntry" Text="" />
<Switch BindingContext="{x:Reference customEntry}" IsToggled="{Binding OnOff}" />

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;
};
}
}
}
}

How to create an Entry renderer in Xamarin?

I want to create a renderer for an entry field using Xamarin. What is the best way to get it? I have no idea how to get it, any help will be appreciate.
This is my entry field and I want to create font size renderer for that
<Entry x:Name="txtTest"/>
This is sample of Entry that set padding to 0 and can set TextSize property.
namespace projectname
{
public class BareEntry : Entry
{
public BareEntry ()
{
}
public static readonly BindableProperty TextSizeProperty =
BindableProperty.Create<BareEntry,int> (X => X.TextSize, 10);
public int TextSize
{
get {return (int)base.GetValue (TextSizeProperty);}
set {base.SetValue (TextSizeProperty, value);}
}
}
}
This part is on android platform
using Xamarin.Forms;
using projectname;
using projectname.Droid;
using Xamarin.Forms.Platform.Android;
using MocMSearch.Droid;
using Android.Support.V4.Content;
[assembly: ExportRenderer (typeof (BareEntry), typeof (BareEntryRenderer))]
namespace MocMSearch.Droid
{
public class BareEntryRenderer : EntryRenderer
{
public BareEntryRenderer ()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Entry> e)
{
base.OnElementChanged(e);
Recreate ();
}
void Recreate()
{
if (this.Control == null) return;
Control.SetPadding (0,0,0,0);
Control.SetTextSize (Android.Util.ComplexUnitType.Mm,(Element as BareEntry).TextSize);
}
protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged (sender, e);
if (e.PropertyName == "TextSize")
{
Recreate ();
this.Invalidate ();
}
}
}
}

Resources