How to create custom bindings for Windows Phone?
I need to do something like this (but this example for Android):
answer
Custom bindings in Android:
public class LongClickEventBinding
: MvxBaseAndroidTargetBinding
{
private readonly View _view;
private IMvxCommand _command;
public LongPressEventBinding(View view)
{
_view = view;
_view.LongClick += ViewOnLongClick;
}
private void ViewOnLongClick(object sender, View.LongClickEventArgs eventArgs)
{
if (_command != null)
{
_command.Execute();
}
}
public override void SetValue(object value)
{
_command = (IMvxCommand)value;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_view.Click -= ViewOnLongClick;
}
base.Dispose(isDisposing);
}
public override Type TargetType
{
get { return typeof(IMvxCommand); }
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
Excuse me for the improper question..
As far as I understood your question, you don't need to develop anything at all. Use Blend to apply & setup CallMethodAction built-in behavior, and implement public method in your VM class.
WP7 doesn't expose Tap and Hold as an event.
However, I believe you can access this sort og thing in Wp7 and Wp8 using Gestures - e.g.
http://blogs.claritycon.com/windowsphone7/2010/07/wp7-gesture-recognizer-and-behavior-triggers/
http://www.windowsphonegeek.com/articles/WP7-GestureService-in-depth--key-concepts-and-API
Related
I'm trying to create an extended version of the standard XF Map object:
public class RRMap: Map
{
public void DoSomethingOnMap() {
/* ... */
}
}
I also created an Android renderer (iOS will come later):
[assembly: ExportRenderer(typeof(RRMap), typeof(RRMapRendererAndroid))]
namespace MyApp.Droid.Renderers
{
public class RRMapRendererAndroid : MapRenderer
{
public RRMapRendererAndroid(Context context) : base(context) { }
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Control.GetMapAsync(this);
}
}
protected override MarkerOptions CreateMarker(Pin pin)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
marker.SetIcon(BitmapDescriptorFactory.DefaultMarker(210));
return marker;
}
}
}
Everything is working fine so far: the map is rendered and pins are created with a custom color.
Unfortunately, I'm stuck on the implementation of DoSomethingOnMap method: it should be a method in the shared code, but it should be implemented in different ways, depending on the platform.
In other circumstances, I would create an interface using DependencyService for implementation, but in this particular case I can't figure out how to proceed.
The first solution is you can use a messaging-center, this can communicate between shared project and iOS/Android project.
Publish a message in the doSomethingOnMap method and anywhere you subscribed to the message will be triggered.
The second is create an event in your shared project and subscribe to that event in the renderer, I wrote both two solutions below:
In your shared project:
public class CustomMap : Map
{
public List<CustomPin> CustomPins { get; set; }
public event EventHandler CallToNativeMethod;
public void doSomething()
{
if (CallToNativeMethod != null)
CallToNativeMethod(this, new EventArgs());
}
public void doSomething(CustomMap myMap) {
MessagingCenter.Send<CustomMap>(this, "Hi");
}
}
In the renderer:
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
}
if (e.NewElement != null)
{
MessagingCenter.Subscribe<CustomMap>(this, "Hi", (sender) =>
{
// Do something whenever the "Hi" message is received
Console.WriteLine("hi");
});
((CustomMap)e.NewElement).CallToNativeMethod += (sender, arg) =>
{
Console.WriteLine("native method");
};
}
}
At anywhere you want to call this method:
private void Button_Clicked(object sender, System.EventArgs e)
{
customMap.doSomething();
customMap.doSomething(customMap);
}
I'm making user changeable settings for my media player and I'm struggling to find an elegant solution to the problem.
One of my settings for example - pauses the video at it's last frame, if not checked it will either continue through the playlist or if it's only 1 file, reset it and pause it at the start.
This is how I've implemented it:
private void OnMediaEndedCommand()
{
if (GeneralSettings.PauseOnLastFrame)
{
MediaPlayer.SetMediaState(MediaPlayerStates.Pause);
return;
}
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
This is contained inside the ViewModel of the main window, where the media element is and GeneralSettings.PauseOnLastFrame is a boolean property.
This command is binded as follows:
<MediaElement ....>
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="MediaEnded">
<ia:InvokeCommandAction Command="{Binding MediaEndedCommand}"/>
</ia:EventTrigger>
</ia:Interaction.Triggers>
</MediaElement>
It works but it's awful, how should I go about implementing such setting system in an elegant way? Some settings might not be boolean, they might have multiple options, some might be applied only on startup and others, as the one illustrated above, event based.
Based on the information and sample code you provided, I would suggest
Approach - 1
A tightly couple ViewModel with System.Configuration.ApplicationSettingsBase and you can mention all you properties in ViewModel and map single of them with a separate application setting property. You can use your settings directly in biding afterwards e.g. : {x:Static Settings.Default.Whatevs}. Othe "Save" button click event or main window close event, you can save all you settings e.g. : Settings.Default.Save();
Approach - 2
A better approach, I would suggest / prefer (if I am developing this app) is to develop a wrapper class (e.g.: SettingProvider) that implement an inheritance (e.g: ISettingProvider) which uncovers all you settings as separate properties and also have a save method which saves all setting values. You can use this wrapper class into your ViewModel to handle all the commands and setting values in better way.
The benefit of this approach is the if you decide to change you setting to database , you need not to make change to you ViewModel as all job is done in SettingProvider class.
I am not sure but based on viewing your code, I assume that you used Approach-1. Please put you comments and any feedback to this answer. I would like to know what you think and may be you have got more simple and interesting way of achieving this.
UPDATE-1
Example
Enum for showing you demo
public enum MediaStatus
{
Playing = 0,
Stopped = 1,
Paused = 2
}
Interface
public interface ISettingProvider
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
Wrapper Class
public class SettingProvider : ISettingProvider
{
private double volumne;
public double Volumne // read-write instance property
{
get
{
return volumne;
}
set
{
volumne = value;
Settings.Default.Volumne = volumne;
}
}
private string lastMediaUrl;
public string LastMediaUrl // read-write instance property
{
get
{
return lastMediaUrl;
}
set
{
lastMediaUrl = value;
Settings.Default.LastMediaUrl = lastMediaUrl;
}
}
private MediaStatus playingMediaStatus;
public MediaStatus PlayingMediaStatus // read-write instance property
{
get
{
return playingMediaStatus;
}
set
{
playingMediaStatus = value;
Settings.Default.PlayingMediaStatus = (int)playingMediaStatus;
}
}
public void SaveSettings()
{
Settings.Default.Save();
}
//Constructor
public SettingProvider()
{
this.Volumne = Settings.Default.Volumne;
this.LastMediaUrl = Settings.Default.LastMediaUrl;
this.PlayingMediaStatus = (MediaStatus)Settings.Default.PlayingMediaStatus;
}
}
ViewModelBase Class
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
CommandHandler Class
public class CommandHandler : ICommand
{
public event EventHandler CanExecuteChanged { add { } remove { } }
private Action<object> action;
private bool canExecute;
public CommandHandler(Action<object> action, bool canExecute)
{
this.action = action;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute;
}
public void Execute(object parameter)
{
action(parameter);
}
}
ViewModel
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
public double Volumne
{
get
{
return objSettingProvider.Volumne;
}
set
{
objSettingProvider.Volumne = value;
OnPropertyChanged("Volumne");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand saveSettingButtonCommand;
public ICommand SaveSettingButtonCommand
{
get
{
return saveSettingButtonCommand ?? (saveSettingButtonCommand = new CommandHandler(param => saveSettings(param), true));
}
}
private void saveSettings()
{
objSettingProvider.SaveSettings();
}
}
UPDATE-2
public interface ISettingProvider
{
bool PauseOnLastFrame;
bool IsAutoPlay;
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
public class SettingProvider : ISettingProvider
{
private bool pauseOnLastFrame;
public bool PauseOnLastFrame // read-write instance property
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
Settings.Default.PauseOnLastFrame = volumne;
}
}
private bool isAutoPlay;
public bool IsAutoPlay // read-write instance property
{
get
{
return isAutoPlay;
}
set
{
isAutoPlay = value;
Settings.Default.IsAutoPlay = volumne;
}
}
}
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
MediaStatus PlayingMediaStatus
{
get
{
return objSettingProvider.PlayingMediaStatus;
}
set
{
if(value == MediaStatus.Paused)
MediaPlayer.Pause();
if(value == MediaStatus.Playing)
MediaPlayer.Play();
if(value == MediaStatus.Stopped)
MediaPlayer.Stop();
objSettingProvider.PlayingMediaStatus = (int)value;
OnPropertyChanged("PlayingMediaStatus");
}
}
private string currentMediaFile;
public string CurrentMediaFile
{
get
{
return currentMediaFile;
}
set
{
currentMediaFile = value;
MediaPlayer.Stop();
MediaPlayer.Current = currentMediaFile;
if(objSettingProvider.IsAutoPlay)
MediaPlayer.Play();
OnPropertyChanged("CurrentMediaFile");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand onMediaEndedCommand;
public ICommand OnMediaEndedCommand
{
get
{
return onMediaEndedCommand ?? (onMediaEndedCommand = new CommandHandler(param => onMediaEnded(param), true));
}
}
private void onMediaEnded()
{
if(objSettingProvider.PauseOnLastFrame)
{
PlayingMediaStatus = MediaStatus.Paused;
}
else if(PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
PlayingMediaStatus = MediaStatus.Stopped;
}
else
{
CurrentMediaFile = PlayListViewModel.FilesCollection.MoveNext();
}
}
}
NOTE: This is the detailed example I put here and also avoid some syntax error or naming error if I missed somewhere. Please correct it.
I am not aware which media player settings you are using. I took some sample properties. This is just an example of structure you can implement for you application. You may need to alter more code to implement this structure.
An elegant way to implement this IMHO would be to use a dependency injection container, this will provide great flexibility while allowing you to completely separate concerns (i.e. the settings implementation from your view models and custom controls).
There are many DI frameworks out there, for my example I will use simple injector because it is free (open source), simple and fast but you can apply the same principle to other frameworks (Unity, Ninject, etc..).
Start by creating an interface for your settings service, for example:
public interface ISettingsService
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
Then add your implementation for the service, the beauty of using DI is that you can change the implementation at anytime or completely replace it and your application will continue to work as usual.
Let's say you want to use application settings, here is your service:
public class SettingsServiceFromApplication : ISettingsService
{
public double Volume
{
get
{
return Properties.Settings.Volume;
}
}
[...]
}
Or let's say you want to use a database to store your settings:
public class SettingsServiceFromDb : ISettingsService
{
public double Volume
{
get
{
return MyDb.Volumen;
}
}
[...]
}
Then you can use a DI container to specify which implementation to use:
Start by installing the library using NuGet:
Install-Package SimpleInjector -Version 4.0.12
You need a way to share your container throughout the application, I usually just go with a static class that I initialize when starting the app:
using Container = SimpleInjector.Container;
namespace YourNamespace
{
public class Bootstrapper
{
internal static Container Container;
public static void Setup()
{
//Create container and register services
Container = new Container();
//Let's specify that we want to use SettingsServiceFromApplication
Container.Register<ISettingsService, SettingsServiceFromApplication>();
//You can use your bootstrapper class to initialize other stuff
}
}
You need to call Setup when starting the App, the best place is in the App constructor:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper.Setup();
}
}
So now you have an app wide depedency injection container that you can use to request "services" (specific implementations of an interface).
To get the settings implementation in your view models you could simply call the container as follows:
// This will return an instance of SettingsServiceFromApplication
ISettingsService settingsService = Bootstrapper.Container.GetInstance<ISettingsService>();
double volumen = settingsService.Volume;
To make it easier to work with, I usually create a base view model that will allow to get services more easyly, for example:
public abstract BaseViewModel
{
private ISettingsService _settings;
protected ISettingsService GeneralSettings
{
get
{
if (_settings == null)
_settings = Bootstrapper.Container.GetInstance<ISettingsService>();
return _settings;
}
}
}
Every view model inheriting from this class will have access to the settings:
public class YourViewModel : BaseViewModel
{
private void OnMediaEndedCommand()
{
if (GeneralSettings.PauseOnLastFrame)
{
MediaPlayer.SetMediaState(MediaPlayerStates.Pause);
return;
}
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
}
As you can see the code is the same as your code! But now the settings are coming from your container. Where is the elegance? Well, let's say that one year from now someone decides that you will store your settings in a database, what do you need to change in your code?
Container.Register<ISettingsService, SettingsServiceFromDb>();
A single line. Everything else should work as usual.
As well as view models, you could use this mechanism in your own controls:
public class MyMediaElement : UserControl //Or MediaElement and instead of commands you can override real events in the control code behind, this does not break the MVVM pattern at all, just make sure you use depedency properties if you need to exchange data with your view models
{
private void OnMediaEndedCommand()
{
//Get your settings from your container, do whatever you want to do depending on the settings
[...]
}
}
Then just use your control in your Views / ViewModels:
<local:MyMediaElement />
Yep, that's all you need because you handle everything in your User / Custom control, your view models doesn't need to care about how you handle settings in the control.
There are many options you can use to register containers, I recommend you take a look at the docs:
https://simpleinjector.org/index.html
https://simpleinjector.readthedocs.io/en/latest/index.html
I think maybe you are looking for an interface approach?
public interface IMediaEndedHandler
{
bool AlternateHandling(MediaPlayer player);
}
public class NullMediaEndedHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
return false;
}
}
public class PauseOnLastFrameHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
player.SetMediaState(MediaPlayerStates.Pause);
return true;
}
}
public class GeneralSettings
{
private bool pauseOnLastFrame;
private bool PauseOnLastFrame
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
MediaEndedHandler = value
? new PauseOnLastFrameHandler()
: new NullMediaEndedHandler();
}
}
public IMediaEndedHandler MediaEndedHandler = new NullMediaEndedHandler();
}
Then:
private void OnMediaEndedCommand()
{
if (GeneralSettings.MediaEndedHandler.AlternateHandling(MediaPlayer))
return;
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
This way, if your setting is, for example. an enum instead of a bool, you can specify a different implementation of the interface for each possible value.
I got sort of a typical music player window, music plays and seekbar point is moving while it plays.
I've done it using default mvvmcross binding to the property (which is changed through the EventHandler binding) like here:
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/playprogress"
local:MvxBind="Progress ValueOfTimer"
/>
So now I want the user to be able to move it forward and back.
I've been trying to bind it like this:
public class PlayWindowView : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
var set = this.CreateBindingSet<PlayWindowView, PlayWindowViewModel>();
SeekBar seek = FindViewById<SeekBar>(Resource.Id.playprogress);
set.Bind(seek).For("Max").To(viewModel => viewModel.MaxTimerValue);
set.Bind(seek).For("StopTrackingTouch").To(viewModel => viewModel.PlayProgressChanged);
set.Apply();
}
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.playwindow_view);
}
}
Viewmodel part looks like this:
public class PlayWindowViewModel : MvxViewModel<ListMenuItemDto>
{
private long _valueOfTimer;
public long ValueOfTimer
{
get { return _valueOfTimer; }
set
{
_valueOfTimer = value;
RaisePropertyChanged(() => ValueOfTimer);
}
}
//...
public MvxAsyncCommand<long> PlayProgressChanged
{
get { return new MvxAsyncCommand<long>(OnPlayProgressChange);}
}
private async Task OnPlayProgressChange(long progr)
{
await _playingService.SetTime((int) progr).ConfigureAwait(false);
}
}
But looks like it's not working.
I mean, it's not even getting into OnPlayProgressChange. But on view is appearing it goes into command PlayProgressChanged one time.
How can I bind this event (and such kind of events like StartTrackingTouch, StopTrackingTouch) to the function correctly?
P.S.
just FYI I using MvvmCross 5
UPD 28.11.2017
Tried custom binding and even Progress binding stoped working now.
So, xaml looks like this now:
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/playprogress"
local:MvxBind="Progress ValueOfTimer, OnStopTrackingTouch PlayProgressChanged"
/>
And binder code is here
public class SeekbarStopTrackingTouchEventBinding: MvxAndroidTargetBinding
{
private readonly SeekBar _seekbar;
private IMvxAsyncCommand _command;
public SeekbarStopTrackingTouchEventBinding(SeekBar seekbar) : base(seekbar)
{
_seekbar = seekbar;
_seekbar.StopTrackingTouch += ViewOnStopTrackingTouch;
}
private void ViewOnStopTrackingTouch(object sender, SeekBar.StopTrackingTouchEventArgs e)
{
if (_command != null)
{
_command.Execute(e);
}
}
public override Type TargetType
{
get { return typeof (IMvxAsyncCommand); }
}
protected override void SetValueImpl(object target, object value)
{
_command = (IMvxAsyncCommand)value;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_seekbar.StopTrackingTouch -= ViewOnStopTrackingTouch;
}
base.Dispose(isDisposing);
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
}
In Setup:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterFactory(new MvxCustomBindingFactory<SeekBar>("OnStopTrackingTouch", (seekbar) => new SeekbarStopTrackingTouchEventBinding(seekbar)));
}
UPD2
Changed binding like this: local:MvxBind="Progress ValueOfTimer; OnStopTrackingTouch PlayProgressChanged" (notice ; here) and event fires now, yes!
But now the thing is - in binder _command is stays null even after SetValueImpl fired and _command = (IMvxAsyncCommand)value; is fine, value points to setted ViewModel property.
How come?
UPD3
Found out it can't cast object to IMvxAsyncCommand.
I fixed that by changing
IMvxAsyncCommand _command to IMvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs> _command;
Will sum everything up in the answer.
But now I got the question - whats best practice in this case?
So, I don't know if it's a good way, but for now its working and I'm kinda happy with it.
Hope this would help somebody like me.
Custom Bindings approach is the key here. Really useful stuff is here:
In MvvmCross how do I do custom bind properties
MvvmCross Custom Event Binding Event Args
MVVMCross Bindings in Android
So, in my case, to make app listen to SeekBar OnStopTrackingTouch event I done this:
Created binding class:
public class SeekbarStopTrackingTouchEventBinding: MvxAndroidTargetBinding
{
private readonly SeekBar _seekbar;
private IMvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs> _command;
private string testString;
public SeekbarStopTrackingTouchEventBinding(SeekBar seekbar) : base(seekbar)
{
_seekbar = seekbar;
_seekbar.StopTrackingTouch += ViewOnStopTrackingTouch;
}
private void ViewOnStopTrackingTouch(object sender, SeekBar.StopTrackingTouchEventArgs e)
{
if (_command != null)
{
_command.Execute(e);
}
}
public override Type TargetType
{
get { return typeof (IMvxAsyncCommand); }
}
protected override void SetValueImpl(object target, object value)
{
try
{
_command = (IMvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs>)value;
}
catch (Exception e)
{
Log.Error("SOME BINDER FAIL\n\t" + e.Message + "\n", "SOME BINDER FAIL\n\t" + e.Message + "\n");
throw;
}
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_seekbar.StopTrackingTouch -= ViewOnStopTrackingTouch;
}
base.Dispose(isDisposing);
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
In Setup.cs placed this code:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterFactory(new MvxCustomBindingFactory<SeekBar>("OnStopTrackingTouch", (seekbar) => new SeekbarStopTrackingTouchEventBinding(seekbar)));
}
Prepared property in my ViewModel and command executing function:
public IMvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs> PlayProgressChanged
{
get
{
return new MvxAsyncCommand<SeekBar.StopTrackingTouchEventArgs>(OnPlayProgressChange);
}
}
private async Task OnPlayProgressChange(SeekBar.StopTrackingTouchEventArgs e)
{
var progr = e.SeekBar.Progress;
await _playingService.SetTime((int) progr).ConfigureAwait(false);
}
In view layout, inside local:MvxBind linked my ViewModel command with evend name, provided in registry.RegisterFactory in Setup.cs
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/playprogress"
local:MvxBind="Progress ValueOfTimer; OnStopTrackingTouch PlayProgressChanged"
/>
I'm porting to Xamarin.IOS a swift library that makes some material design animation with UIButton.
The swift counterpart is a subclass of UIButton and overrides layoutSublayersOfLayer.
Swift:
public override func layoutSublayersOfLayer(layer: CALayer) {
super.layoutSublayersOfLayer(layer)
if self.layer == layer {
layoutShape()
layoutVisualLayer()
}
}
On Xamarin side I noted that this method is called through CALayerDelegate, which is associated with CALayer through de property Delegate.
I tried to subclass CALayerDelegate and replace de Delegate property, but when I did that the button didn't rendered correctly and stopped respond to events.
Is there a way to override layoutSublayersOfLayer on Xamarin.Ios ? Is there another method that I can override to prepare stuff before a particular layer is drawn ?
public class MaterialButtonLayerDelegate : CALayerDelegate
{
private readonly MaterialButton _button;
public MaterialButtonLayerDelegate(MaterialButton button):base()
{
_button = button;
}
public override void LayoutSublayersOfLayer(CALayer layer)
{
if (_button.Layer == layer)
{
_button.LayoutShape();
_button.LayoutVisualLayer();
}
}
}
[Register("MaterialButton")]
public class MaterialButton : UIButton
{
public CAShapeLayer VisualLayer { get; private set; } = new CAShapeLayer();
public MaterialButton(CGRect frame):base(frame)
{
PrepareView();
}
protected void PrepareView()
{
Layer.Delegate = new MaterialButtonLayerDelegate(this);
ContentScaleFactor = MaterialDevice.Scale();
PrepareVisualLayer();
}
protected virtual void PrepareVisualLayer()
{
VisualLayer.ZPosition = 0;
VisualLayer.MasksToBounds = true;
Layer.AddSublayer(VisualLayer);
}
protected virtual void LayoutShape()
{
//...
}
protected virtual void LayoutVisualLayer()
{
//...
}
}
Thanks!
The only possible way to do it is extending CALayerDelegate class and setting it as your CAShapeLayer delegate. Be sure to call delegate's base members wherever possible as it performs required bindings behind the scenes.
public override void LayoutSublayersOfLayer(CALayer layer)
{
base.LayoutSublayersOfLayer(layer);
if (_button.Layer == layer)
{
_button.LayoutShape();
_button.LayoutVisualLayer();
}
}
How can I detect the double tap event for View in Xamarin.Android? I am failing with TouchListener.
There is a SO answer that says double tap is not an Android pattern.
For Native Android (Java), there is an answer, but I want to do that in Xamarin (C#).
It's too easy in c# to do this task. You are new to this world so I'm giving you the exact solution.
First of all you don't have to create a new custom view for this.
Create a class
private class GestureListener : GestureDetector.SimpleOnGestureListener
{
public override bool OnDown(MotionEvent e)
{
return true;
}
public override bool OnDoubleTap(MotionEvent e)
{
return true;
}
}
Now just write this code.
GestureDetector _gestureDetector = new GestureDetector (_context, new GestureListener ());
_gestureDetector.DoubleTap += (object sender, GestureDetector.DoubleTapEventArgs e) => {
//apply double tap code here
};
//apply touch to your view
View1.Touch += (object sender, View.TouchEventArgs e) => {
_gestureDetector.OnTouchEvent (e.Event);
};
I hope this help you.
The first link in your question already contains the answer. A pity it isn't the accepted answer, though. A C# port of the correct answer would be:
public class DoubleTappableView : View
{
private readonly GestureDetector _gestureDetector;
public DoubleTappableView(Context context, IAttributeSet attrs)
: base(context, attrs)
{
_gestureDetector = new GestureDetector(context, new GestureListener());
}
public override bool OnTouchEvent(MotionEvent e)
{
return _gestureDetector.OnTouchEvent(e);
}
private class GestureListener : GestureDetector.SimpleOnGestureListener
{
public override bool OnDown(MotionEvent e)
{
return true;
}
public override bool OnDoubleTap(MotionEvent e)
{
//TODO: Add double tap logic here
return true;
}
}
}