Timer Speed is increasing on picker selection change - xamarin.forms

I am creating a timer which is working on a picker selection changed.Timer is working good until the times end to the "00:00:00".If i change the selection changed in the mid of time then the speed of timer is increased and if i again change selected index than timer run much fast. My problem is that timer should run on 1 sec dealy every time.
I am sharing my code below. Thanks in advance for help.
//picker selection change
private async void GName_SelectedIndexChanged(object sender, EventArgs e)
{
lblEndTime.Text = "00:00:10";
StartTimmer();
}
public void StartTimmer()
{
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
Device.BeginInvokeOnMainThread(() =>
{
TimeSpan timeSpan = TimeSpan.Parse(lblEndTime.Text);
timeSpan -= TimeSpan.FromSeconds(1);
if (lblEndTime.Text == "00:00:00")
{
returnValue = false;
}
else
{
lblEndTime.Text = Convert.ToString(timeSpan);
}
});
return returnValue;
});
}

Use a System.Timer and stop the timer every time you change the selected index instead of creating a new one.
public partial class MainPage : ContentPage
{
Timer Timer1 { get; set; }
public MainPage()
{
InitializeComponent();
}
public void StartTimmer()
{
if (Timer1 != null)
{
Timer1.Stop();
Timer1.Dispose();
}
Timer1 = new Timer();
Timer1.Interval = 1000;
Timer1.Enabled = true;
Timer1.Start();
Timer1.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
{
Device.BeginInvokeOnMainThread(() =>
{
TimeSpan timeSpan = TimeSpan.Parse(lblEndTime.Text);
timeSpan -= TimeSpan.FromSeconds(1);
if (lblEndTime.Text == "00:00:00")
{
Timer1.Stop();
Timer1.Dispose();
}
else
{
lblEndTime.Text = Convert.ToString(timeSpan);
}
});
};
}
private void Button_Clicked(object sender, EventArgs e)
{
lblEndTime.Text = "00:00:10";
StartTimmer();
}
}
And in xaml:
<StackLayout>
<!-- Place new controls here -->
<Label x:Name="lblEndTime" Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Button Text="Click Me" Clicked="Button_Clicked"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>

Related

Xamarin Boolean Bindable Property

I am trying to make clickable Icon which will be using for Wish List, for this I have created boolean property which will return Image.
This is my code, but it does not support onClick event, Please advise to figure out this problem.
public class WishIconImg : Image, IDisposable
{
static FontImageSource unselected_source = new FontImageSource();
static FontImageSource selected_source = new FontImageSource();
public WishIconImg()
{
unselected_source.FontFamily = "FA-S";
unselected_source.Glyph = "\U000f02d5";
unselected_source.Color = Color.DarkOrange;
selected_source.FontFamily = "FA-S";
selected_source.Glyph = "\U000f02d1";
selected_source.Color = Color.DarkOrange;
OnClick += Checkbox_OnClick;
}
public static BindableProperty IsCheckedProperty = BindableProperty.Create(
nameof(IsChecked), typeof(bool), typeof(WishIconImg), defaultBindingMode: BindingMode.TwoWay,
propertyChanged: IsCheckedChanged);
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
private static void IsCheckedChanged(BindableObject bindable, object oldValue, object newValue)
{
var cb = (WishIconImg)bindable;
if (cb == null)
return;
if ((bool)newValue)
{
cb.Source = selected_source;
}
else
{
cb.Source=unselected_source ;
}
}
void Checkbox_OnClick(object sender, EventArgs e)
{
IsChecked = !IsChecked;
}
public void Dispose()
{
OnClick -= Checkbox_OnClick;
}
}
}
Xaml
<controls:WishIconImg x:Name="HeartChk" IsChecked="{Binding AddWish, Mode=TwoWay}" HeightRequest="35" WidthRequest="35" HorizontalOptions="End"/>
Even I have tried with Label property but it doesnt work
You could modify the class like following
public WishIconImg()
{
unselected_source.FontFamily = "FA-S";
unselected_source.Glyph = "\U000f02d5";
unselected_source.Color = Color.DarkOrange;
selected_source.FontFamily = "FA-S";
selected_source.Glyph = "\U000f02d1";
selected_source.Color = Color.DarkOrange;
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (s, e) => {
// handle the tap
IsChecked = !IsChecked;
};
this.GestureRecognizers.Add(tapGestureRecognizer);
}
Try adding TapGestureRecognizer for an click event
Do something like this
<StackLayout HeightRequest="35" WidthRequest="35" HorizontalOptions="End">
<controls:WishIconImg x:Name="HeartChk" IsChecked="{Binding AddWish, Mode=TwoWay}" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Checkbox_OnClick}" />
</StackLayout.GestureRecognizers>
</StackLayout>
In your ViewModel Bind the command for it
public System.Windows.Input.ICommand Checkbox_OnClick => new Xamarin.Forms.Command(Checkbox_OnClickTapped);
Checkbox_OnClickTapped will be your method called when your view will be clicked

Xamarin forms: How to add SwipeGestureRecognizer for StackLayout?

I have 15 options on my home page. Initially, I will show 9 options in UI. To view the remaining 6 icons, the user slides to the right and back to the left to see previous. I try to implement the swiping feature like below, but it is not working.
XAML
<StackLayout x:Name="firstLlayout">
<Grid>
//3 icons in horizontal
</Grid>
<Grid>
//3 icons in horizontal
</Grid>
<Grid>
//3 icons in horizontal
</Grid>
<StackLayout.GestureRecognizers>
<SwipeGestureRecognizer Direction="Right" Swiped="RightSwipe"/>
</StackLayout.GestureRecognizers>
</StackLayout>
<StackLayout IsVisible="False" x:Name="secondLayout">
<Grid>
//3 icons in horizontal
</Grid>
<Grid>
//3 icons in horizontal
</Grid>
<StackLayout.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Swiped="LeftSwipe"/>
</StackLayout.GestureRecognizers>
</StackLayout>
XAML.CS
public void RightSwipe(object sender, EventArgs e)
{
firstLlayout.IsVisible = false;
secondLayout.IsVisible = true;
}
public void LeftSwipe(object sender, EventArgs e)
{
secondLayout.IsVisible = false;
firstLlayout.IsVisible = true;
}
When try left and right swipe nothing is happening in UI, and code execution not coming to event functions. What I am missing here?
Cause1:
Swipe action will conflict with scroll action if you put the stacklayout in a ScrollView .
Solution:
Remove the ScrollView from Root StackLayout, then the swiping will work.
Cause2: It is necessary to add a child control(like Image or Label)to StackLayout , otherwise the swipe action will never been called .
Solution: If you do want to let the content of StackLayout shows nothing in default, you can check the following code .
in code behind
using System;
using Xamarin.Forms;
namespace xxx
{
public class GestureScrollView : ScrollView
{
public event EventHandler SwipeLeft;
public event EventHandler SwipeRight;
public void OnSwipeLeft() =>
SwipeLeft?.Invoke(this, null);
public void OnSwipeRight() =>
SwipeRight?.Invoke(this, null);
}
}
in Android Project
using System;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using xxx;
using xxx.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(GestureScrollView), typeof(GestureScrollViewRenderer))]
namespace xxx.Droid
{
public class GestureScrollViewRenderer : ScrollViewRenderer
{
readonly CustomGestureListener _listener;
readonly GestureDetector _detector;
public GestureScrollViewRenderer(Context context) : base(context)
{
_listener = new CustomGestureListener();
_detector = new GestureDetector(context, _listener);
}
public override bool DispatchTouchEvent(MotionEvent e)
{
if (_detector != null)
{
_detector.OnTouchEvent(e);
base.DispatchTouchEvent(e);
return true;
}
return base.DispatchTouchEvent(e);
}
public override bool OnTouchEvent(MotionEvent ev)
{
base.OnTouchEvent(ev);
if (_detector != null)
return _detector.OnTouchEvent(ev);
return false;
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
{
_listener.OnSwipeLeft -= HandleOnSwipeLeft;
_listener.OnSwipeRight -= HandleOnSwipeRight;
}
if (e.OldElement == null)
{
_listener.OnSwipeLeft += HandleOnSwipeLeft;
_listener.OnSwipeRight += HandleOnSwipeRight;
}
}
void HandleOnSwipeLeft(object sender, EventArgs e) =>
((GestureScrollView)Element).OnSwipeLeft();
void HandleOnSwipeRight(object sender, EventArgs e) =>
((GestureScrollView)Element).OnSwipeRight();
}
public class CustomGestureListener : GestureDetector.SimpleOnGestureListener
{
static readonly int SWIPE_THRESHOLD = 100;
static readonly int SWIPE_VELOCITY_THRESHOLD = 100;
MotionEvent mLastOnDownEvent;
public event EventHandler OnSwipeLeft;
public event EventHandler OnSwipeRight;
public override bool OnDown(MotionEvent e)
{
mLastOnDownEvent = e;
return true;
}
public override bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
if (e1 == null)
e1 = mLastOnDownEvent;
float diffY = e2.GetY() - e1.GetY();
float diffX = e2.GetX() - e1.GetX();
if (Math.Abs(diffX) > Math.Abs(diffY))
{
if (Math.Abs(diffX) > SWIPE_THRESHOLD && Math.Abs(velocityX) > SWIPE_VELOCITY_THRESHOLD)
{
if (diffX > 0)
OnSwipeRight?.Invoke(this, null);
else
OnSwipeLeft?.Invoke(this, null);
}
}
return base.OnFling(e1, e2, velocityX, velocityY);
}
}
}
And in Xaml
Put the StackLayout in the ScrollView
<local:GestureScrollView SwipeRight="RightSwipe">
<StackLayout x:Name="firstLlayout" >
//...
</StackLayout>
</local:GestureScrollView>
I like and used Lucas Zhang's answer answer-59190549 to this question however, the GestureScrollView can be changed to utilize the same GestureRecognizer's that iOS will use like so:
public class GestureScrollView : ScrollView
{
private bool isInitialized = false;
private List<SwipeGestureRecognizer> LeftSwipeRecognizers { get; } = new();
private List<SwipeGestureRecognizer> RightSwipeRecognizers { get; } = new();
public GestureScrollView() : base()
{
}
protected override void LayoutChildren(double x, double y, double width, double height)
{
base.LayoutChildren(x, y, width, height);
//Not sure if this is the best place, but the ctor wasn't getting called.
if (!isInitialized)
{
isInitialized = true;
foreach (SwipeGestureRecognizer swipeGestureRecognizer in GestureRecognizers.Where(x => x is SwipeGestureRecognizer))
{
if (swipeGestureRecognizer.Direction.HasFlag(SwipeDirection.Left))
{
LeftSwipeRecognizers.Add(swipeGestureRecognizer);
}
if (swipeGestureRecognizer.Direction.HasFlag(SwipeDirection.Right))
{
RightSwipeRecognizers.Add(swipeGestureRecognizer);
}
}
}
}
private void ExecuteGestureCommands(List<SwipeGestureRecognizer> swipeRecognizers, SwipedEventArgs e)
{
foreach (var gesture in swipeRecognizers)
{
gesture.SendSwiped(this, e.Direction);
}
}
public void OnSwipeLeft(object sender, SwipedEventArgs e)
{
ExecuteGestureCommands(LeftSwipeRecognizers, e);
}
public void OnSwipeRight(object sender, SwipedEventArgs e)
{
ExecuteGestureCommands(RightSwipeRecognizers, e);
}
}
And the Renderer would need changes similar to these (Basically change all "EventArgs" references to "SwipedEventArgs" and pass in a new SwipedEventArgs on the event calls):
...
void HandleOnSwipeLeft(object sender, SwipedEventArgs e) => ((GestureScrollView)Element).OnSwipeLeft(sender, e);
void HandleOnSwipeRight(object sender, SwipedEventArgs e) => ((GestureScrollView)Element).OnSwipeRight(sender, e);
...
public event EventHandler<SwipedEventArgs> OnSwipeLeft;
public event EventHandler<SwipedEventArgs> OnSwipeRight;
...
if (diffX > 0)
{
OnSwipeRight?.Invoke(this, new SwipedEventArgs(null, SwipeDirection.Right));
}
else
{
OnSwipeLeft?.Invoke(this, new SwipedEventArgs(null, SwipeDirection.Left));
}
...
It's not perfect as it ignores the Up/Down directions in Android, but so does the other implementation.

Timer start is not allowing my other pages to load when using Navigate.Push in Xamarin Forms

I used the System.Timers.Timer(); code to do the count down Timer on page load and then I used the Navigate.Push to go into another page.
Timer Code on page load:
public Index()
{
InitializeComponent();
StartCountDownTimer();
}
DateTime endTime = new DateTime(2019, 08, 25, 14, 00, 0);
public void StartCountDownTimer()
{
try
{
timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.Elapsed += t_Tick;
TimeSpan ts = endTime - DateTime.Now;
lblCountDown.Text = ts.ToString("d' Days 'h' Hours 'm' Minutes 's' Seconds'");
timer.Start();
}
catch (Exception ex)
{
string Error = ex.Message;
}
}
System.Timers.Timer timer;
void t_Tick(object sender, EventArgs e)
{
try
{
TimeSpan ts = endTime - DateTime.Now;
string NewTimer = ts.ToString("d' Days 'h' Hours 'm' Minutes 's' Seconds'");
//txtCountDown.Text = NewTimer;
lblCountDown.Text = NewTimer;
if ((ts.TotalMilliseconds < 0) || (ts.TotalMilliseconds < 1000))
{
timer.Stop();
lblCountDown.Text = "The day has arrived";
}
}
catch (Exception ex)
{
string Error = ex.Message;
}
}
Navigate Code using a button click on the same page:
private void ClickAboutTab(object sender, EventArgs e)
{
await Navigation.PushAsync(new Page());
}
Code of page I am navigating to:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AppName.Pages.Page">
<ContentPage.Content>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
Solutions I already tried before navigating are:
timer.Stop();
timer.Dispose();
timer = null;
The timer callback is not invoked from the UI thread.
You need to invoke all ui related stuff from the ui thread:
void t_Tick(object sender, EventArgs e)
{
try
{
TimeSpan ts = endTime - DateTime.Now;
string countDownValue = ts.ToString("'d' Days 'h' Hours 'm' Minutes 's' Seconds");
if ((ts.TotalMilliseconds < 0) || (ts.TotalMilliseconds < 1000))
{
timer.Stop();
countDownValue = "The day has arrived";
}
Device.BeginInvokeOnMainThread(async () =>
{
lblCountDown.Text = countDownValue;
}
}
catch (Exception ex)
{
string Error = ex.Message;
}
}

How to properly redirect a Webview that contains a pdf to Google Drive

On an Xamarian.Forms app, which is trying to redirect links that contain a PDF in from a WebView to a Google Drive URL. My code works as expected on iOS but just spins on an Android.
public partial class Balance : ContentPage
{
public Balance()
{
InitializeComponent();
website.Navigated += WebView_Navigated;
website.Navigating += WebView_Navigating;
UrlWebViewSource source = new UrlWebViewSource();
source.Url = "https://www.google.com";
website.Source = source;
}
private void WebView_Navigating(object sender, WebNavigatingEventArgs e)
{
progress.IsVisible = true;
website.IsVisible = false;
}
public void WebView_Navigated(object sender, WebNavigatedEventArgs e)
{
progress.IsVisible = false;
website.IsVisible = true;
if (e.Url.Contains("pdf") && !e.Url.Contains("drive.google.com"))
{
var webview = (WebView)sender;
website.Source = "https://drive.google.com/viewerng/viewer?embedded=true&url=" + e.Url;
}
}
}
On Android the site never loads and the activity indicator just sits and spins.
According to your description, I use your code and redirect a webview, here is my code, there is no issue on android, nospins on android :
<ContentPage.Content>
<StackLayout>
<WebView
x:Name="website"
HeightRequest="1000"
Navigated="Website_Navigated"
Navigating="Website_Navigating"
WidthRequest="1000" />
</StackLayout>
</ContentPage.Content>
public partial class Page19 : ContentPage
{
public Page19 ()
{
InitializeComponent ();
UrlWebViewSource source = new UrlWebViewSource();
//source.Url = "https://www.google.com";
source.Url = "http://www.pdf995.com/samples/pdf.pdf";
website.Source = source;
}
private void Website_Navigated(object sender, WebNavigatedEventArgs e)
{
website.IsVisible = false;
}
private void Website_Navigating(object sender, WebNavigatingEventArgs e)
{
website.IsVisible = true;
if (e.Url.Contains("pdf") && !e.Url.Contains("drive.google.com"))
{
var webview = (WebView)sender;
website.Source = "https://drive.google.com/viewerng/viewer?embedded=true&url=" + e.Url;
}
}
}

Xamarin.Forms Slider.Clicked event?

I'm making an mp3 player in Xamarin.Forms, in which the slider should both show time expired and allow jumping to locations in the track.
As far as I can tell, only the ValueChanged event is available, with the unfortunate sideeffect that every time my timer updates the slider value, I also trigger my player.SeekTo method, causing broken playback.
Is there any way to specifically pick up touch events on a slider?
If not, does anyone have any suggestions on how to make this work?
In case it's relevant, here's my code for the MainPage:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace MuZor
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
PlayButton.Clicked += PlayButton_Clicked;
PlaylistsButton.Clicked += PlaylistsButton_Clicked;
RandomButton.Clicked += RandomButton_Clicked;
RepeatButton.Clicked += RepeatButton_Clicked;
ChoiceButton.Clicked += ChoiceButton_Clicked;
PreviousButton.Clicked += PreviousButton_Clicked;
NextButton.Clicked += NextButton_Clicked;
TimeSlider.ValueChanged += TimeSlider_ValueChanged;
MessagingCenter.Subscribe<Interfaces.IAudioPlayer>(App.player, "PlayerPrepared", (args) =>
{
System.Diagnostics.Debug.WriteLine("Message received");
PlayerPrepared();
});
}
private void TimeSlider_ValueChanged(object sender, ValueChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Slider value changed, value = " + e.NewValue + "(int = " + (int)e.NewValue);
//App.player.SeekTo((int)e.NewValue);
}
private void NextButton_Clicked(object sender, EventArgs e)
{
if (!App.settings.RandomOn && App.settings.CurrentTrack < App.playlist.Count - 1)
{
Play((int)App.settings.CurrentTrack + 1);
}
}
private void PreviousButton_Clicked(object sender, EventArgs e)
{
if (!App.settings.RandomOn && App.settings.CurrentTrack > 0)
{
Play((int)App.settings.CurrentTrack - 1);
}
}
private void ChoiceButton_Clicked(object sender, EventArgs e)
{
}
private void RepeatButton_Clicked(object sender, EventArgs e)
{
}
private void RandomButton_Clicked(object sender, EventArgs e)
{
}
private void PlaylistsButton_Clicked(object sender, EventArgs e)
{
}
private void PlayButton_Clicked(object sender, EventArgs e)
{
if (App.settings.IsPaused || App.player.IsPlaying())
{
App.player.PauseResume();
if (App.settings.IsPaused)
{
UnPause();
}
else
{
Pause();
}
}
else
{
int trackToplay = App.settings.CurrentTrack != null ? (int)App.settings.CurrentTrack : 0;
Play(trackToplay);
}
}
private void Play(int currentTrack)
{
HelperClasses.SettingsHelper.SaveCurrentTrack(currentTrack);
App.player.LoadAndPlay(App.playlist[currentTrack].Path);
}
private void Pause()
{
HelperClasses.SettingsHelper.SavePausedState(true);
Device.BeginInvokeOnMainThread(() =>
{
PlayButton.Text = "Play";
});
}
private void UnPause()
{
HelperClasses.SettingsHelper.SavePausedState(false);
Device.BeginInvokeOnMainThread(() =>
{
PlayButton.Text = "Pause";
});
StartTimer();
}
private void StartTimer()
{
double position;
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
position = App.player.GetCurrentPosition();
TimeSpan runTime = TimeSpan.FromMilliseconds(position);
Device.BeginInvokeOnMainThread(() =>
{
TimeLabel.Text = runTime.ToString(#"mm\:ss");
TimeSlider.Value = position;
});
if (App.player.IsPlaying())
return true;
else
return false;
});
}
private void PlayerPrepared()
{
var totalDurationInMS = App.player.GetDuration();
TimeSlider.Maximum = totalDurationInMS;
TimeSlider.Minimum = 0;
TimeSpan totalDuration = TimeSpan.FromMilliseconds(totalDurationInMS);
Device.BeginInvokeOnMainThread(() =>
{
RemainingTimeLabel.Text = totalDuration.ToString(#"mm\:ss");
TimeLabel.Text = "00:00";
});
UnPause();
}
}
}
I think I've found a workaround. Since the valuechanged event contains both old and new values, I'll only fire SeekTo if the difference is negative or bigger than 2.

Resources