做商城类APP时经常会遇到抢购倒计时的功能,之前做小区宝iOS的时候也有类似的功能,想着参考iOS做的思路,自定义一个Cell,在Cell中每秒刷新一下控件的文本值,但使用xamarin.forms实现时,自定义cell的方式并不可行,小伙伴上周给发了一个倒计时功能的demo:https://github.com/jsuarezruiz/MyTripCountdown,demo是如下图实现的是一个时间的倒计时效果,需要将一个倒计时的功能放在列表中,实现多个倒计时的效果, 看了源码也一直没思路,昨天也是没思路报着试一试的心态动手操作了下,没想到成功了,还是非常有成就感的。
一、定义计时器
xamarin.forms提供了Device.StartTimer来实现定时任务,每隔一秒需要触发事件改变剩余时间。这里定义了两个Action,Completed是在倒计时结束时触发,Ticked是每秒触发一次。RemainTime是剩余时间timespan,EndDate为结束时间。
using System;using System.Collections.Generic;using System.Text;using Xamarin.Forms;namespace TimeCountDown
{public class CountDown : BindableObject
{
TimeSpan _remainTime;public event Action Completed;public event Action Ticked;public DateTime EndDate { get; set; }public TimeSpan RemainTime
{get { return _remainTime; }private set
{
_remainTime = value;
OnPropertyChanged();
}
}public void Start(int seconds = 1)
{
Device.StartTimer(TimeSpan.FromSeconds(seconds), () =>
{
RemainTime = (EndDate - DateTime.Now);var ticked = RemainTime.TotalSeconds > 1;if (ticked)
{
Ticked?.Invoke();
}else
{
Completed?.Invoke();
}return ticked;
});
}
}
}
二、设置BaseViewModel
这里创建了一个BaseViewModel,并有2个方法,LoadAsync()、UnloadAsync(),而且继承了ExtendedBindableObject。
using System;using System.Collections.Generic;using System.Text;using System.Threading.Tasks;namespace TimeCountDown
{public abstract class BaseViewModel : ExtendedBindableObject
{public virtual Task LoadAsync()
{return Task.CompletedTask;
}public virtual Task UnloadAsync()
{return Task.CompletedTask;
}
}
}
在ExtendedBindableObject中扩展了BindableObject,增加了SetProperty方法,SetProperty方法使用ref引用改变属性的值。
using System;using System.Collections.Generic;using System.Runtime.CompilerServices;using System.Text;using Xamarin.Forms;namespace TimeCountDown
{public class ExtendedBindableObject : BindableObject
{protected bool SetProperty(ref T backingStore, T value, [CallerMemberName]string propertyName = "")
{if (EqualityComparer.Default.Equals(backingStore, value))
{return false;
}
backingStore = value;
OnPropertyChanged(propertyName);return true;
}
}
}
三、设置ViewModel
新建继承BaseViewModel的类CountDownViewModel,在CountDownViewModel中定义了倒计时类CountDown,当CountDownViewModel调用构造函数时实例化倒计时CountDown,EndDate通过时间戳获得,之后调用LoadAsync()方法,启动计时器,并为计时器绑定具体Actio,在Ticked的Action中每秒定时刷新绑定到界面的数值。
using System;using System.Collections.Generic;using System.Text;using System.Threading.Tasks;namespace TimeCountDown
{public class CountDownViewModel : BaseViewModel
{public long Tick { get; set; }private string _countDownTitle;public string CountDownTitle
{get => _countDownTitle;set => SetProperty(ref _countDownTitle, value);
}private CountDown _countDown;public CountDownViewModel(long ticks)
{
Tick = ticks;
_countDown = new CountDown() { EndDate = DateTime.Now.Add(new TimeSpan(ticks)) };
LoadAsync();
}public override Task LoadAsync()
{
_countDown.Start();
_countDown.Ticked += OnCountdownTicked;
_countDown.Completed += OnCountdownCompleted;return base.LoadAsync();
}public override Task UnloadAsync()
{
_countDown.Ticked -= OnCountdownTicked;
_countDown.Completed -= OnCountdownCompleted;return base.UnloadAsync();
}void OnCountdownTicked()
{
CountDownTitle = string.Format("{0}:{1}:{2}后开抢", _countDown.RemainTime.Hours, _countDown.RemainTime.Minutes, _countDown.RemainTime.Seconds);
}void OnCountdownCompleted()
{
CountDownTitle = "抢购进行中";
UnloadAsync();
}
}
}
四、测试
在MainPage中设置了一个ListView,ViewCell模板中设置了一个Label,Text值绑定了CountDownTitle。在MainPage的构造方法中设置listview的ItemsSource。
<?xml version="1.0" encoding="utf-8" ?>"http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TimeCountDown"
x:Class="TimeCountDown.MainPage">"listView" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">"{Binding CountDownTitle}" FontSize="14" TextColor="Black" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
public MainPage()
{
InitializeComponent();
List countDownVMs = new List() {new CountDownViewModel(11111111111),new CountDownViewModel(2222222222),new CountDownViewModel(3333333333333),new CountDownViewModel(444444444444),
};
listView.ItemsSource = countDownVMs;
}
效果图如下: