首页 > 代码库 > 【笔记】WPF实现类似安卓ViewPager引导界面效果及问题汇总

【笔记】WPF实现类似安卓ViewPager引导界面效果及问题汇总

最近在开发项目的首次使用引导界面时,遇到了问题,引导界面类似于安卓手机ViewPager那样的效果,希望通过左右滑动手指来实现切换不同页面,其间伴随动画。

实现思路:

1、界面布局:新建一个UserControl,最外层为Grid,两行一列,内嵌一个Canvas和StackPanel。Canvas中放一个StackPanel用于存放大图列表,外层的StackPanel用于存放RadioButton组,Xaml代码如下:

 1     <Grid x:Name="grid"> 2         <Grid.RowDefinitions> 3             <RowDefinition Height="7*"></RowDefinition> 4             <RowDefinition></RowDefinition> 5         </Grid.RowDefinitions> 6         <Canvas x:Name="canvas" Grid.Row="0" Grid.RowSpan="2" Background="#eaede6"> 7             <StackPanel x:Name="imageStack"  Orientation="Horizontal"></StackPanel> 8         </Canvas> 9         <StackPanel x:Name="buttonStack" Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Center" >10             <RadioButton></RadioButton>11         </StackPanel>12     </Grid>

2、后台代码:定义三个依赖属性,分别为ActiveItemIndex,TotalItemsCount,ItemsListSource,分别表示当前处于活动状态的条目ID,总的条目数量,及条目源,这里的后台代码我的ItemsListSource的数据类型是IEnumerable<BitmapImage>,为了方便看效果我直接把每个页面作为一张图片,到项目集成的时候应该用Page或其他控件,当ActiveItemIndex改变时,执行相应的动画,C#代码如下:

  1 namespace UserInterface.UserControls  2 {  3     /// <summary>  4     /// IndicatorControl.xaml 的交互逻辑  5     /// </summary>  6     public partial class IndicatorControl : UserControl  7     {  8         #region 字段及属性  9         /// <summary> 10         /// 单张图片宽度 11         /// </summary> 12         private Double _width = 1300; 13         /// <summary> 14         /// 触摸起始点 15         /// </summary> 16         private TouchPoint _startTouchPoint; 17         /// <summary> 18         /// 触摸结束点 19         /// </summary> 20         private TouchPoint _endTouchPoint; 21  22         // Using a DependencyProperty as the backing store for ActiveButtonIndex.  This enables animation, styling, binding, etc... 23         public static readonly DependencyProperty ActiveItemIndexProperty = 24             DependencyProperty.Register("ActiveItemIndex", typeof(Int32), typeof(IndicatorControl), new UIPropertyMetadata(-1, new PropertyChangedCallback((sender, e) => 25             { 26                 IndicatorControl control = sender as IndicatorControl; 27                 control.SetActiveItem(); 28             }))); 29  30         // Using a DependencyProperty as the backing store for TotalButtonCount.  This enables animation, styling, binding, etc... 31         public static readonly DependencyProperty TotalItemsCountProperty = 32             DependencyProperty.Register("TotalItemsCount", typeof(Int32), typeof(IndicatorControl), new UIPropertyMetadata(-1, new PropertyChangedCallback((sender, e) => 33             { 34                 IndicatorControl control = sender as IndicatorControl; 35                 control.SetItemsByTotalCount(); 36             }))); 37  38         // Using a DependencyProperty as the backing store for ImageListProperty.  This enables animation, styling, binding, etc... 39         public static readonly DependencyProperty ItemsListSourceProperty = 40             DependencyProperty.Register("ItemsListSource", typeof(IEnumerable<BitmapImage>), typeof(IndicatorControl), new UIPropertyMetadata(null, new PropertyChangedCallback((sender, e) => 41             { 42                 IndicatorControl control = sender as IndicatorControl; 43                 control.SetItemsList(); 44             }))); 45         /// <summary> 46         /// 当前处于激活状态的条目索引 47         /// </summary> 48         public Int32 ActiveItemIndex 49         { 50             get { return (Int32)GetValue(ActiveItemIndexProperty); } 51             set { SetValue(ActiveItemIndexProperty, value); } 52         } 53         /// <summary> 54         /// 总条目数量 55         /// </summary> 56         public Int32 TotalItemsCount 57         { 58             get { return (Int32)GetValue(TotalItemsCountProperty); } 59             set { SetValue(TotalItemsCountProperty, value); } 60         } 61         /// <summary> 62         /// 条目数据源 63         /// </summary> 64         public IEnumerable<BitmapImage> ItemsListSource 65         { 66             get { return (IEnumerable<BitmapImage>)GetValue(ItemsListSourceProperty); } 67             set { SetValue(ItemsListSourceProperty, value); } 68         } 69         #endregion 70  71         #region 构造函数 72         public IndicatorControl() 73         { 74             InitializeComponent(); 75         } 76         #endregion 77  78         #region 方法 79         /// <summary> 80         /// 设置当前活动的Item项 81         /// </summary> 82         public void SetActiveItem() 83         { 84             for (int i = 0; i < this.TotalItemsCount; i++) 85             { 86                 if (i.Equals(this.ActiveItemIndex)) 87                 { 88                     (this.buttonStack.Children[i] as RadioButton).IsChecked = true; 89                 } 90             } 91             MoveAnimation(ActiveItemIndex); 92         } 93         /// <summary> 94         /// 设置Item的总数 95         /// </summary> 96         public void SetItemsByTotalCount() 97         { 98             this.buttonStack.Children.Clear(); 99             for (Int32 i = 0; i < this.TotalItemsCount; i++)100             {101                 RadioButton r = new RadioButton();102                 r.IsEnabled = false;103                 r.GroupName = "Index";104                 r.Margin = new Thickness(10);105                 this.buttonStack.Children.Add(r);106             }107         }108         /// <summary>109         /// 设置Items数据源110         /// </summary>111         public void SetItemsList()112         {113             this.imageStack.Children.Clear();114             for (Int32 i = 0; i < ItemsListSource.Count(); i++)115             {116                 Image image = new Image();117                 image.Source = ItemsListSource.ElementAt(i);118                 image.Width = _width;119                 image.Stretch = Stretch.Fill;120                 this.imageStack.Children.Add(image);121             }122         }123         #endregion124 125         #region 事件126         /// <summary>127         /// 控件加载时执行一些操作128         /// </summary>129         /// <param name="sender"></param>130         /// <param name="e"></param>131         private void UserControl_Loaded(object sender, RoutedEventArgs e)132         {133             this.ActiveItemIndex = 0;134             this.imageStack.Width = _width * TotalItemsCount;135         }136         /// <summary>137         /// 触摸按下138         /// </summary>139         /// <param name="sender"></param>140         /// <param name="e"></param>141         private void imageStack_TouchDown(object sender, TouchEventArgs e)142         {143             _startTouchPoint = e.GetTouchPoint(App.Current.MainWindow);144             e.Handled = true;145         }146         /// <summary>147         /// 长按并移动148         /// </summary>149         /// <param name="sender"></param>150         /// <param name="e"></param>151         private void imageStack_TouchMove(object sender, TouchEventArgs e)152         {153             TouchPoint tempPoint = e.GetTouchPoint(App.Current.MainWindow);154             //得到前后两点X的平移距离155             double distance = _startTouchPoint.Position.X - tempPoint.Position.X;156             //计算相偏移量157             Double offset = this._width * ActiveItemIndex + distance;158             //释放属性,使其可以被设置159             this.imageStack.BeginAnimation(Canvas.LeftProperty, null);160 161             Canvas.SetLeft(this.imageStack, -offset);162             e.Handled = true;163         }164         /// <summary>165         /// 触摸释放166         /// </summary>167         /// <param name="sender"></param>168         /// <param name="e"></param>169         private void imageStack_TouchUp(object sender, TouchEventArgs e)170         {171             _endTouchPoint = e.GetTouchPoint(App.Current.MainWindow);172             double x_offset = _startTouchPoint.Position.X - _endTouchPoint.Position.X;173             //当X轴偏移量向右大于100且当前Index小于页总数174             if (x_offset > 100 && ActiveItemIndex < TotalItemsCount - 1)175             {176                 ++ActiveItemIndex;177             }178             //当X轴偏移量向左偏移量大于100且当前Index大于1179             else if (x_offset < -100 && ActiveItemIndex > 0)180             {181                 --ActiveItemIndex;182             }183             else184             {185                 MoveAnimation(ActiveItemIndex);186             }187             e.Handled = true;188         }189         #endregion190 191         #region 动画192         /// <summary>193         /// 动画194         /// </summary>195         /// <param name="index"></param>196         private void MoveAnimation(Int32 index)197         {198             DoubleAnimation da = new DoubleAnimation();199             da.Duration = new Duration(TimeSpan.FromMilliseconds(300));200             da.DecelerationRatio = 0.2;201             da.AccelerationRatio = 0.2;202             da.From = Canvas.GetLeft(this.imageStack);203             da.To = -(index * _width);204             this.imageStack.BeginAnimation(Canvas.LeftProperty, da);205         }206         #endregion207     }208 }
C# Code

3、数据绑定:有了依赖属性,在客户端的任何一个窗口中调用该控件,都可以进行数据绑定了,以下是调用该控件的窗口XAML:

1     <Grid>2         <control:IndicatorControl ItemsListSource="{Binding ImageList}" TotalItemsCount="{Binding ImageList.Count}"></control:IndicatorControl>3     </Grid>
Xaml Code

如果控件的当前激活条目需要绑定到其他地方,那么这里也可以进行设置,但这里暂时不需要设置了。

【笔记】WPF实现类似安卓ViewPager引导界面效果及问题汇总