如何在Xamarin中停止并启动定时进度条?

时间:2017-09-23 18:08:25

标签: xamarin xamarin.forms

答案中有一个问题,显示如何创建运行指定时间段的进度条。以下是该问题的链接:

How can I create a bar area that slowly fills from left to right over 5, 10 or ?? seconds?

我已经对此进行了测试,效果很好。但是,我想知道如何扩展它,以便可以在完成之前取消/停止进度条,然后再次重新启动。

问题和答案非常受欢迎,所以看起来这可能会让很多人受益。

我很感激有关可能的方法的任何想法和反馈。

更新1:

我尝试实施解决方案,但我收到错误,并希望得到一些建议。我正在使用你所有的新代码,我在这里改变了新的代码:

timerView.StartTimerCommand
     .Execute(TimeSpan.FromSeconds(App.pti.Val()));

三个问题

首先 - 我注意到你将timerView分成两个文件。属性文件似乎以某种方式链接到主文件。从图形上看,属性文件从timerView缩进。你如何在Visual Studio中进行此链接?我刚创建了两个文件,这有什么不同。

第二次 - 当我尝试编译代码时,我收到此错误:

/ Users // Documents / Phone app / Japanese7 / Japanese / Views / Phrases / PhrasesFrame.xaml(10,10):错误:位置117:10。缺少附加属性“Japanese.TimerView.ProgressBarProperty”(日语)的公共静态GetProgressBar或公共实例属性getter

你有什么想法可能导致这个吗?一切都和以前一样。

第三次 - 我注意到你使用的是BoxView而我使用的是Frame。代码是否可以使用?

更新2:

在我的后端C#代码中,我使用以下命令启动计时器:

timerView.StopTimerCommand.Execute(); // Give syntax error

我试图用一些类似的语法来停止计时器,但是有一些问题。您是否可以告诉我如何在与C#后端使用计时器而不是解决方案中的MVVM时停止计时器:

{{1}}

1 个答案:

答案 0 :(得分:3)

第1步:将取消方法添加到ViewExtensions

public static class ViewExtensions
{
    static string WIDTH_ANIMATION_NAME = "WidthTo";
    public static Task<bool> WidthTo(this VisualElement self, double toWidth, uint length = 250, Easing easing = null)
    {
         ...
    }

    public static void CancelWidthToAnimation(this VisualElement self)
    {
        if(self.AnimationIsRunning(WIDTH_ANIMATION_NAME))
            self.AbortAnimation(WIDTH_ANIMATION_NAME);
    }
}

第2步:为'pause'和'stop'/'cancel'命令添加可绑定属性;以及跟踪计时器是否正在运行的属性。

public static readonly BindableProperty PauseTimerCommandProperty =
    BindableProperty.Create(
        "PauseTimerCommand", typeof(ICommand), typeof(TimerView),
        defaultBindingMode: BindingMode.OneWayToSource,
        defaultValue: default(ICommand));

public ICommand PauseTimerCommand
{
    get { return (ICommand)GetValue(PauseTimerCommandProperty); }
    set { SetValue(PauseTimerCommandProperty, value); }
}

public static readonly BindableProperty StopTimerCommandProperty =
    BindableProperty.Create(
        "StopTimerCommand", typeof(ICommand), typeof(TimerView),
        defaultBindingMode: BindingMode.OneWayToSource,
        defaultValue: default(ICommand));

public ICommand StopTimerCommand
{
    get { return (ICommand)GetValue(StopTimerCommandProperty); }
        set { SetValue(StopTimerCommandProperty, value); }
}

public static readonly BindableProperty IsTimerRunningProperty =
    BindableProperty.Create(
        "IsTimerRunning", typeof(bool), typeof(TimerView),
        defaultBindingMode: BindingMode.OneWayToSource,
        defaultValue: default(bool), propertyChanged: OnIsTimerRunningChanged);

public bool IsTimerRunning
{
    get { return (bool)GetValue(IsTimerRunningProperty); }
    set { SetValue(IsTimerRunningProperty, value); }
}

private static void OnIsTimerRunningChanged(BindableObject bindable, object oldValue, object newValue)
{
    ((TimerView)bindable).OnIsTimerRunningChangedImpl((bool)oldValue, (bool)newValue);
}

第3步:更新TimerView,如下所示,使用StopWatch来跟踪时间,暂停和取消。

public partial class TimerView : AbsoluteLayout
{
    readonly Stopwatch _stopWatch = new Stopwatch();
    public TimerView()
    {
        ...
    }

    async void HandleStartTimerCommand(object param = null)
    {
        if (IsTimerRunning)
            return;

        ParseForTime(param);
        if (InitRemainingTime())
            _stopWatch.Reset();

        SetProgressBarWidth();

        IsTimerRunning = true;

        //Start animation
        await ProgressBar.WidthTo(0, Convert.ToUInt32(RemainingTime.TotalMilliseconds));

        //reset state
        IsTimerRunning = false;
    }

    void HandlePauseTimerCommand(object unused)
    {
        if (!IsTimerRunning)
            return;

        ProgressBar.CancelWidthToAnimation(); //abort animation
    }

    void HandleStopTimerCommand(object unused)
    {
        if (!IsTimerRunning)
            return;

        ProgressBar.CancelWidthToAnimation(); //abort animation

        ResetTimer(); //and reset timer
    }

    protected virtual void OnIsTimerRunningChangedImpl(bool oldValue, bool newValue)
    {
        if (IsTimerRunning)
        {
            _stopWatch.Start();
            StartIntervalTimer(); //to update RemainingTime
        }    
        else
            _stopWatch.Stop();

        ((Command)StartTimerCommand).ChangeCanExecute();
        ((Command)PauseTimerCommand).ChangeCanExecute();
        ((Command)StopTimerCommand).ChangeCanExecute();
    }

    bool _intervalTimer;
    void StartIntervalTimer()
    {
        if (_intervalTimer)
            return;

        Device.StartTimer(TimeSpan.FromMilliseconds(100), () =>
        {
            if(IsTimerRunning)
            {
                var remainingTime = Time.TotalMilliseconds - _stopWatch.Elapsed.TotalMilliseconds;
                if (remainingTime <= 100)
                {
                    _intervalTimer = false;
                    ResetTimer();
                }
                else
                    RemainingTime = TimeSpan.FromMilliseconds(remainingTime);
            }

            return _intervalTimer = IsTimerRunning; //stop device-timer if timer was stopped
        });
    }

    private void ResetTimer()
    {
        ProgressBar.CancelWidthToAnimation();
        RemainingTime = default(TimeSpan); //reset timer
        SetProgressBarWidth(); //reset width
    }

    void SetProgressBarWidth()
    {
        if (RemainingTime == Time)
            SetLayoutBounds(ProgressBar, new Rectangle(0, 0, Width, Height));
        else
        {
            var progress = ((double)RemainingTime.Seconds / Time.Seconds);
            SetLayoutBounds(ProgressBar, new Rectangle(0, 0, Width * progress, Height));
        }
    }
    ...
}

样本用法

<controls:TimerView x:Name="timerView">
    <controls:TimerView.ProgressBar>
        <BoxView BackgroundColor="Maroon" />
    </controls:TimerView.ProgressBar>
    <controls:TimerView.TrackBar>
        <BoxView BackgroundColor="Gray" />
    </controls:TimerView.TrackBar>
</controls:TimerView>

<Label Text="{Binding Path=RemainingTime, StringFormat='{0:%s}:{0:%f}', Source={x:Reference timerView}}" /> 

<Button Command="{Binding StartTimerCommand, Source={x:Reference timerView}}" Text="Start Timer">
    <Button.CommandParameter>
        <x:TimeSpan>0:0:20</x:TimeSpan>
    </Button.CommandParameter>
</Button>

<Button Command="{Binding PauseTimerCommand, Source={x:Reference timerView}}" Text="Pause Timer" />

<Button Command="{Binding StopTimerCommand, Source={x:Reference timerView}}" Text="Stop Timer" />

enter image description here

TimerBarSample

上传的工作示例

编辑1

首先 - 它确实没有什么区别 - 您甚至可以将所有代码合并到一个文件中。可以使用<DependentOn />标记实现缩进链接 - 类似于用于XAML文件的代码隐藏cs的标记。

第二次 - 我已将protected访问修饰符添加到可绑定属性的getter或setter中。但是当应用XAMLC时看起来它会失败。我已经更新了github示例中的代码。

第三次 - 是的,可以使用从View(可能是BoxViewFrame)继承的任何控件

编辑2

由于这些命令(可绑定属性)的类型为ICommand,因此要Execute - 您需要传入一个参数。如果命令不需要参数 - 您可以使用null

推荐用法:

 if(timerView.StopTimerCommand.CanExecute(null))
      timerView.StopTimerCommand.Execute(null);