【笔记】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#代码如下:

【笔记】WPF实现类似安卓ViewPager引导界面效果及问题汇总
  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         #endregion
124 
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大于1
179             else if (x_offset < -100 && ActiveItemIndex > 0)
180             {
181                 --ActiveItemIndex;
182             }
183             else
184             {
185                 MoveAnimation(ActiveItemIndex);
186             }
187             e.Handled = true;
188         }
189         #endregion
190 
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         #endregion
207     }
208 }
C# Code

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

【笔记】WPF实现类似安卓ViewPager引导界面效果及问题汇总
1     <Grid>
2         <control:IndicatorControl ItemsListSource="{Binding ImageList}" TotalItemsCount="{Binding ImageList.Count}"></control:IndicatorControl>
3     </Grid>
Xaml Code

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

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

上一篇:android开发之——混淆编译


下一篇:Android动画的实现原理 .