一直想寫點東西,直到今天才真正動筆,唯一的原因就是太懶,太懶...
大家肯定熟悉安卓各種客戶端首頁聯動圖片廣告(比如淘寶),可以自動滾動,可以手動滑動,當然是循環的。但是在wp上我看到的做的最好的最早的當屬愛壁紙了。
寫在wp8.1的filpview馬上來臨之際。上代碼吧。
[TemplatePart(Name = InnerBorderName, Type = typeof(Border))] [TemplatePart(Name = InnerItemsPresenterName, Type = typeof(ItemsPresenter))] [TemplatePart(Name = listMaskerName, Type = typeof(ListBox))] public class SlideView : ItemsControl, INotifyPropertyChanged { private const string InnerBorderName = "InnerBorder"; private const string InnerItemsPresenterName = "InnerItemsPresenter"; private const string listMaskerName = "listMasker"; //視覺樹根元素 Border border = null; //滑動元素 ItemsPresenter itemsPresenter = null; ListBox listBox = null; //是否正在滑動 private bool isBusy = false; //滑動故事版 Storyboard sb = null; //滑動動畫 DoubleAnimation da = null; //緩動函數 CircleEase ease = null; // 計數器 private DispatcherTimer timer = null; //標記索引 private int index = 1; private int selectIndex = 0; private double borderWidth = 0; // MarkSource public static readonly DependencyProperty MarkSourceProperty = DependencyProperty.Register("MarkSource", typeof(IEnumerable), typeof(SlideView), new PropertyMetadata(null)); /// <summary> /// 當前位置索引 /// </summary> public int SelectIndex { get { return selectIndex; } set { selectIndex = value; OnPropertyChanged("SelectIndex"); } } /// <summary> /// 頁碼數據源 /// </summary> public IEnumerable MarkSource { get { return (IEnumerable)GetValue(MarkSourceProperty); } set { SetValue(MarkSourceProperty, value); } } /// <summary> /// Border寬度 /// </summary> public double BorderWidth { get { return borderWidth; } set { borderWidth = value; OnPropertyChanged("BorderWidth"); } } /// <summary> /// 構造函數 /// </summary> public SlideView() { this.DefaultStyleKey = typeof(SlideView); this.Loaded += SlideView_Loaded; this.Unloaded += SlideView_Unloaded; } protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { BorderWidth = this.Width * this.Items.Count; if (this.Items.Count >= 4) { InitParams(); if (itemsPresenter == null) { itemsPresenter = this.GetTemplateChild(InnerItemsPresenterName) as ItemsPresenter; } (itemsPresenter.RenderTransform as CompositeTransform).TranslateX = this.Width * (-1); StartMove(); } base.OnItemsChanged(e); } public override void OnApplyTemplate() { base.OnApplyTemplate(); border = this.GetTemplateChild(InnerBorderName) as Border; if (listBox != null) { listBox.LayoutUpdated -= listBox_LayoutUpdated; } listBox = this.GetTemplateChild(listMaskerName) as ListBox; if (listBox != null) { listBox.LayoutUpdated += listBox_LayoutUpdated; } if (border != null) { Binding bind = new Binding(); bind.Path = new PropertyPath("BorderWidth"); bind.Source = this; border.SetBinding(WidthProperty, bind); } if (itemsPresenter != null) { itemsPresenter.ManipulationStarted -= itemsPresenter_ManipulationStarted; itemsPresenter.ManipulationDelta -= items_ManipulationDelta; itemsPresenter.ManipulationCompleted -= items_ManipulationCompleted; } itemsPresenter = this.GetTemplateChild(InnerItemsPresenterName) as ItemsPresenter; if (itemsPresenter != null) { itemsPresenter.ManipulationStarted += itemsPresenter_ManipulationStarted; itemsPresenter.ManipulationDelta += items_ManipulationDelta; itemsPresenter.ManipulationCompleted += items_ManipulationCompleted; } } void listBox_LayoutUpdated(object sender, EventArgs e) { if (listBox.Items.Count > 0) { List<Ellipse> ellipseList = FindChildOfType<Ellipse>(listBox); if (ellipseList.Count > 0) { foreach (var item in ellipseList) { if (item.GetValue(Shape.FillProperty) != null) { continue; } Binding bind = new Binding(); bind.Path = new PropertyPath("SelectIndex"); bind.Source = this; bind.Converter = new SlideViewMarkColorConverter(); bind.ConverterParameter = item.Tag; item.SetBinding(Shape.FillProperty, bind); } } } } private void SlideView_Loaded(object sender, System.Windows.RoutedEventArgs e) { if (itemsPresenter != null) { InitParams(); da.Duration = TimeSpan.FromMilliseconds(500); ease.EasingMode = EasingMode.EaseOut; da.EasingFunction = ease; Storyboard.SetTarget(da, itemsPresenter); Storyboard.SetTargetProperty(da, new PropertyPath("(UIElement.RenderTransform).(CompositeTransform.TranslateX)")); sb.Children.Add(da); sb.Completed += sb_Completed; timer.Interval = TimeSpan.FromMilliseconds(3000); timer.Tick += timer_Tick; StartMove(); } } private void SlideView_Unloaded(object sender, RoutedEventArgs e) { StopMove(); timer = null; if (sb != null) { sb.Stop(); sb = null; } if (da != null) { da = null; } if (ease != null) { ease = null; } } /// <summary> /// 初始化參數 /// </summary> private void InitParams() { index = 1; SelectIndex = 0; isBusy = false; if (sb == null) { sb = new Storyboard(); } if (da == null) { da = new DoubleAnimation(); } if (ease == null) { ease = new CircleEase(); } if (timer == null) { timer = new DispatcherTimer(); } } /// <summary> /// 計數器事件--自動滾動 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer_Tick(object sender, EventArgs e) { timer.Stop(); MoveToNext(); } /// <summary> /// 滑動開始事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void itemsPresenter_ManipulationStarted(object sender, System.Windows.Input.ManipulationStartedEventArgs e) { //如果還在滑動過程中或者items的數量小於2 則禁止滑動 if (isBusy || this.Items.Count < 4) { e.Complete(); e.Handled = true; return; } } /// <summary> /// 滑動過程事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void items_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e) { //如果還在滑動過程中 則禁止滑動 if (isBusy || this.Items.Count < 4) { e.Complete(); e.Handled = true; return; } StopMove(); ItemsPresenter b = sender as ItemsPresenter; (b.RenderTransform as CompositeTransform).TranslateX += e.DeltaManipulation.Translation.X; } /// <summary> /// 滑動結束事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void items_ManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e) { //如果還在滑動過程中 則禁止滑動 if (isBusy || this.Items.Count < 4) { e.Handled = true; return; } if (e.TotalManipulation.Translation.X < 0) { if (!e.IsInertial && Math.Abs(e.TotalManipulation.Translation.X) < this.Width / 3) { MoveToCurrent(); } else { MoveToNext(); } } else if (e.TotalManipulation.Translation.X > 0) { if (!e.IsInertial && Math.Abs(e.TotalManipulation.Translation.X) < this.Width / 3) { MoveToCurrent(); } else { MoveToPre(); } } } /// <summary> /// 動畫完成事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void sb_Completed(object sender, EventArgs e) { if (index == 0) { index = this.Items.Count - 1 - 1; (itemsPresenter.RenderTransform as CompositeTransform).TranslateX = index * (-1) * this.Width; } else if (index == this.Items.Count - 1) { index = 1; (itemsPresenter.RenderTransform as CompositeTransform).TranslateX = (-1) * this.Width; } CalcIndex(); isBusy = false; StartMove(); } /// <summary> /// 切換到下一頁 /// </summary> public void MoveToNext() { if (index + 1 > this.Items.Count) { return; } index++; da.To = index * (-1) * this.Width; isBusy = true; sb.Begin(); } /// <summary> /// 切換到上一頁 /// </summary> public void MoveToPre() { if (index - 1 < 0) { return; } index--; da.To = index * (-1) * this.Width; isBusy = true; sb.Begin(); } /// <summary> /// 切換到當前頁 /// </summary> private void MoveToCurrent() { da.To = index * (-1) * this.Width; isBusy = true; sb.Begin(); } /// <summary> /// 重置標記位 /// </summary> private void CalcIndex() { if (index == 0) { SelectIndex = this.Items.Count - 2 - 1; } else if (index == this.Items.Count - 1) { SelectIndex = 0; } else { SelectIndex = index - 1; } } /// <summary> /// 開始自動翻動 /// </summary> public void StartMove() { if (timer != null && !timer.IsEnabled) { timer.Start(); } } /// <summary> /// 停止自動翻動 /// </summary> public void StopMove() { if (timer != null && timer.IsEnabled) { timer.Stop(); } } /// <summary> /// 針對屬性更改通知的多播事件。 /// </summary> public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// 向偵聽器通知已更改了某個屬性值。 /// </summary> /// <param name="propertyName">用於通知偵聽器的屬性的名稱。此 /// 值是可選的,可以在從支持 /// <see cref="CallerMemberNameAttribute"/> 的編譯器調用時自動提供。</param> protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { var eventHandler = this.PropertyChanged; if (eventHandler != null) { eventHandler(this, new PropertyChangedEventArgs(propertyName)); } } static List<T> FindChildOfType<T>(DependencyObject root) where T : class { List<T> retList = new List<T>(); var queue = new Queue<DependencyObject>(); queue.Enqueue(root); while (queue.Count > 0) { DependencyObject current = queue.Dequeue(); for (int i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--) { var child = VisualTreeHelper.GetChild(current, i); var typedChild = child as T; if (typedChild != null) { retList.Add(typedChild); } queue.Enqueue(child); } } return retList; } }
如果圖片多於2張 應該在最前面加上最后一張 再在最后面加上第一張 這樣當到最前面或者最后面的時候 直接修改 TranslateX 達到循環的效果。
樣式:
<Style TargetType="snControls:SlideView"> <Setter Property="Background" Value="{x:Null}" /> <Setter Property="BorderThickness" Value="0" /> <Setter Property="TabNavigation" Value="Once" /> <Setter Property="IsTabStop" Value="False" /> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="snControls:SlideView"> <Grid Background="{TemplateBinding Background}" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}"> <Border Height="{TemplateBinding Height}" BorderThickness="{TemplateBinding BorderThickness}" x:Name="InnerBorder"> <ItemsPresenter x:Name="InnerItemsPresenter"> <ItemsPresenter.RenderTransform> <CompositeTransform/> </ItemsPresenter.RenderTransform> </ItemsPresenter> </Border> <ListBox VerticalAlignment="Bottom" HorizontalAlignment="Center" x:Name="listMasker" ItemsSource="{TemplateBinding MarkSource}"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <Ellipse Margin="0 0 4 12" Width="8" Height="8" Tag="{Binding MarkIndex}"> </Ellipse> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
顏色轉換器:
public class SlideViewMarkColorConverter : IValueConverter { private SolidColorBrush brush = new SolidColorBrush(); public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if ((int)value == (int)parameter) { brush.Opacity = 1; brush.Color = Colors.White; } else { brush.Opacity = 0.4; brush.Color = Colors.Black; } return brush; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
MarkSource綁定實體,其中MarkIndex修改為實體的sn,如果實體數量大於1 ItemsSource前insert最后一個 ItemsSource后add第一個。
最后如果將控件放在樞軸里面 是無法滑動的。
在頁面后置代碼loadevent里面加上
slideview.UseOptimizedManipulationRouting = false; slideview.AddHandler(PivotItem.ManipulationStartedEvent, new EventHandler<ManipulationStartedEventArgs>(myPivotItem_ManipulationStarted), true); slideview.AddHandler(PivotItem.ManipulationDeltaEvent, new EventHandler<ManipulationDeltaEventArgs>(myPivotItem_ManipulationDelta), true); slideview.AddHandler(PivotItem.ManipulationCompletedEvent, new EventHandler<ManipulationCompletedEventArgs>(myPivotItem_ManipulationCompleted), true);
在
myPivotItem_ManipulationStarted
myPivotItem_ManipulationDelta
myPivotItem_ManipulationCompleted 事件里面判斷
if (e.OriginalSource.GetType() == typeof(Image)) { e.Handled = true; }
這個判斷確保如果是圖片 就阻止樞軸滑動 如果頁面還有其他圖片 也可以用坐標 等等...