一直想写点东西,直到今天才真正动笔,唯一的原因就是太懒,太懒...
大家肯定熟悉安卓各种客户端首页联动图片广告(比如淘宝),可以自动滚动,可以手动滑动,当然是循环的。但是在wp上我看到的做的最好的最早的当属爱壁纸了。
写在wp8.1的filpview马上来临之际。上代码吧。
class="brush:csharp;collapse:true;;gutter:true;"> [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;
}
}
如果有4张图片 应该在最前面加上最后一张 再在最后面加上第一张 这样当到最前面或者最后面的时候 直接修改 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; }
这个判断确保如果是图片 就阻止枢轴滑动 如果页面还有其他图片 也可以用坐标 等等...