最近知乎終於更新到了正式版,因此接下來的一段時間我會陸續分享一些知乎中用到的代碼片斷和控件,但願對UWP開發者有所幫助。git
今天先來分享一個Pivot吧!github
你確定想,Pivot還用你說啊!不要着急各位先看清楚情況!ide
在已知的UWP應用中只是看到了網易雲音樂中使用了相似的效果,接下來咱們一步步的實現這個控件。this
先來分析下這個控件,spa
好的,接下來咱們建立一個TemplateControl,取名爲ZhiHuPivot(夠俗的O(∩_∩)O~)並繼承自Pivotcode
public ZhiHuPivot() { this.DefaultStyleKey = typeof(ZhiHuPivot); if (!DesignMode.DesignModeEnabled) this.Loaded += ZhiHuPivot_Loaded; }
而後從generic.xaml中拿到Pivot的ControlTemplate,代碼過長我就不粘貼了,Pivot的Control其實並不複雜orm
而後把Margin和Padding都修改成0,代碼以下blog
<Style TargetType="controls:ZhiHuPivot"> <Setter Property="Margin" Value="0" /> <Setter Property="Padding" Value="0" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="IsTabStop" Value="False" /> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <Grid /> </ItemsPanelTemplate> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="controls:ZhiHuPivot"> </ControlTemplate> </Setter.Value> </Setter> </Style>
根據剛纔的思路,咱們先來喂ZhiHuPivot添加一些依賴屬性,以知足自定義的要求繼承
public double HeaderWidth { get { return (double)GetValue(HeaderWidthProperty); } set { SetValue(HeaderWidthProperty, value); } } // Using a DependencyProperty as the backing store for HeaderWidth. This enables animation, styling, binding, etc... public static readonly DependencyProperty HeaderWidthProperty = DependencyProperty.Register("HeaderWidth", typeof(double), typeof(ZhiHuPivot), new PropertyMetadata(80.0)); public double BackgroundLineStokeThickness { get { return (double)GetValue(BackgroundLineStokeThicknessProperty); } set { SetValue(BackgroundLineStokeThicknessProperty, value); } } // Using a DependencyProperty as the backing store for BackgroundLineStokeThickness. This enables animation, styling, binding, etc... public static readonly DependencyProperty BackgroundLineStokeThicknessProperty = DependencyProperty.Register("BackgroundLineStokeThickness", typeof(double), typeof(ZhiHuPivot), new PropertyMetadata(2.0)); public Brush BackgroundLineStoke { get { return (Brush)GetValue(BackgroundLineStokeProperty); } set { SetValue(BackgroundLineStokeProperty, value); } } // Using a DependencyProperty as the backing store for BackgroundLineStoke. This enables animation, styling, binding, etc... public static readonly DependencyProperty BackgroundLineStokeProperty = DependencyProperty.Register("BackgroundLineStoke", typeof(Brush), typeof(ZhiHuPivot), new PropertyMetadata(new SolidColorBrush(Colors.LightGray))); public double IndicatorLineStokeThickness { get { return (double)GetValue(IndicatorLineStokeThicknessProperty); } set { SetValue(IndicatorLineStokeThicknessProperty, value); } } // Using a DependencyProperty as the backing store for ForegroundLineStokeThickness. This enables animation, styling, binding, etc... public static readonly DependencyProperty IndicatorLineStokeThicknessProperty = DependencyProperty.Register(nameof(IndicatorLineStokeThickness), typeof(double), typeof(ZhiHuPivot), new PropertyMetadata(2.0)); public Brush IndicatorLineStroke { get { return (Brush)GetValue(IndicatorLineStrokeProperty); } set { SetValue(IndicatorLineStrokeProperty, value); } } // Using a DependencyProperty as the backing store for ForegroundLineStroke. This enables animation, styling, binding, etc... public static readonly DependencyProperty IndicatorLineStrokeProperty = DependencyProperty.Register(nameof(IndicatorLineStroke), typeof(Brush), typeof(ZhiHuPivot), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
分別是,PivotItemHeader的寬度,背景線的寬度和顏色,指示線的寬度和顏色事件
接下來咱們稍稍修改一個Pivot的ControlTemplate,先加一個背景線,再加一個指示線,而後指示線在背景線上左右移動就能夠了,代碼以下
<Line Grid.Column="1" Stroke="{TemplateBinding BackgroundLineStoke}" IsHitTestVisible="False" VerticalAlignment="Bottom" X2="10" Stretch="UniformToFill" StrokeThickness="{TemplateBinding BackgroundLineStokeThickness}"></Line> <Line x:Name="TipLine" Grid.Column="1" Stroke="{TemplateBinding IndicatorLineStroke}" IsHitTestVisible="False" VerticalAlignment="Bottom" StrokeThickness="{TemplateBinding IndicatorLineStokeThickness}"> <Line.RenderTransform> <TranslateTransform x:Name="TipLineTranslateTransform"></TranslateTransform> </Line.RenderTransform> </Line>
好的一切就緒,可是彷佛有一個重要的問題尚未解決,如何監測Pivot的左右滑動呢?
仔細觀察Pivot的ControlTemplate會發現全部的PivotItem其實在一個橫向滾動的ScrollViewer裏面而已,因此咱們能夠經過監測這個ScrollViewer來試試獲取偏移量,而後添加到指示器上就能夠了
首先呢咱們添加一些字段
private TranslateTransform _itemsPresenterTranslateTransform; private ScrollViewer _scrollViewer; private Line _tipLine; private TranslateTransform _tipLineTranslateTransform; private double _previsousOffset;
而後重載OnApplyTemplate方法,從中獲取模板中ScrollViewer
protected override void OnApplyTemplate() { base.OnApplyTemplate(); _itemsPresenterTranslateTransform = GetTemplateChild<TranslateTransform>("ItemsPresenterTranslateTransform"); if (_itemsPresenterTranslateTransform != null) { _itemsPresenterTranslateTransform.RegisterPropertyChangedCallback(TranslateTransform.XProperty, Callback); } _scrollViewer = GetTemplateChild<ScrollViewer>("ScrollViewer"); if (_scrollViewer != null) { _scrollViewer.RegisterPropertyChangedCallback(ScrollViewer.HorizontalOffsetProperty, HorizontalOffsetCallback); } _tipLine = GetTemplateChild<Line>("TipLine"); _tipLineTranslateTransform = GetTemplateChild<TranslateTransform>("TipLineTranslateTransform"); }
在回調事件中處理偏移量
private void HorizontalOffsetCallback(DependencyObject sender, DependencyProperty dp) { if (_previsousOffset != 0) { var x = (double)sender.GetValue(dp); var right = x > _previsousOffset; if (right) { // 非邊界 if (SelectedIndex + 1 != Items.Count) { var newX = (x - _previsousOffset) / Items.Count + (SelectedIndex * HeaderWidth); var max = (SelectedIndex + 1) * HeaderWidth; _tipLineTranslateTransform.X = newX < max ? newX : max; } else { _tipLineTranslateTransform.X = (SelectedIndex * HeaderWidth) - (x - _previsousOffset); } } else { // 非邊界 if (SelectedIndex != 0) { var newX = (x - _previsousOffset) / Items.Count + (SelectedIndex * HeaderWidth); var max = (SelectedIndex + 1) * HeaderWidth; _tipLineTranslateTransform.X = newX < max ? newX : max; } else { _tipLineTranslateTransform.X = _previsousOffset - x; } } } }
而後基本上就能夠說是大功告成了嗎?
等等別激動還有兩個問題,
第一個是計算寬度
private void ZhiHuPivot_Loaded(object sender, RoutedEventArgs e) { if (Items.Count > 1) { var res = ScreenSize().Width / Items.Count; if (IsMobile) HeaderWidth = res; _tipLine.X2 = HeaderWidth; } }
第二個是偏差修正
private void Callback(DependencyObject sender, DependencyProperty dp) { _previsousOffset = (double)sender.GetValue(dp); _tipLineTranslateTransform.X = (SelectedIndex * HeaderWidth); }
好滴如今基本上是沒有問題了,咱們來引用一下看看效果
<Grid Background="White"> <controls:ZhiHuPivot x:Name="MyPivot" IndicatorLineStroke="Red" IndicatorLineStokeThickness="2"> <Pivot.HeaderTemplate> <DataTemplate> <TextBlock Text="{Binding}" Margin="0,0,0,5" Foreground="Black" TextAlignment="Center" Width="{Binding ElementName=MyPivot, Path=HeaderWidth}" FontSize="16"></TextBlock> </DataTemplate> </Pivot.HeaderTemplate> <PivotItem Header="推薦" Margin="0" ></PivotItem> <PivotItem Header="熱門" Margin="0" ></PivotItem> <PivotItem Header="收藏" Margin="0" ></PivotItem> </controls:ZhiHuPivot> </Grid>
運行一下看看效果,會發現怎麼沒有對齊呢?
咱們還須要修改一下PivotHeaderItem的模板,比較長我就不粘貼了其實就是把Margin和Padding改爲了0而已,詳見源碼吧!
修改後的手機運行截圖
源代碼:Github
拋磚引玉,若是各位有更好的思路還望多多賜教!
追夢