WP8中自定义Lrc歌词同步显示控件(二)

  在上一篇文章 WP8中自定义Lrc歌词同步显示控件(一)介绍了我在WP中实现自定义Lrc歌词显示控件的思路,在这一篇文章中主要来介绍具体的Lrc歌词同步显示控件的具体实现。

      首先,回顾一下上一篇中所提及的大体思路:在控件的布局中用3个TextBlock来显示当前正在播放的那句歌词,以及它的前面部分歌词和后面部门歌词,在布局的外层用一个ScrollViewer来控制滚动,使用户能够通过滑动控件来查看整体歌词,控件随播放进度来调整ScrollViewer的竖直滚动偏移量,使正在播放的那句歌词适中居中显示。

      在具体实现前,我们来定义下歌词相关的一些数据结构,因为的我们的控件依赖它来工作。我们把每一句歌词成为一个歌词片段,数据结构为LrcFragment,它包含了该句歌词的开始时间,结束时间,歌词内容等(在这里时间单位为毫秒),定义如下:

    public class LrcFragment
    {
        public long StartTime { get; set; }  //开始时间
        public long EndTime { get; set; }  //结束时间
        public long Duration { get; private set; } //持续时间
        public string LrcText { get; set; } //歌词内容

        public LrcFragment(string text, long start, long duration)
        {
            StartTime = start;
            Duration = duration;
            LrcText = text;
        }

        public LrcFragment(string text, long start)
        {
            StartTime = start;
            Duration = 0;
            LrcText = text;
        }
    }

那么一首歌的歌词就是由许多歌词片段构成的一个集合,我们把整首歌歌词的数据结构定义为Lyric,它包含了一个歌词片段集合属性,以及歌词解析相关的方法。定义如下:

    public class Lyric
    {
        public List<LrcFragment> Fragments { get; private set; }

        public Lyric(string lrcText)
        {
            Fragments = new List<LrcFragment>();
            ParseLyricText(lrcText);
        }

//解析整首歌词内容
public void ParseLyricText(string lrcText) { StringReader reader = new StringReader(lrcText); string line = null; //一行一行的解析
while ((line = reader.ReadLine()) != null) { ParseLine(line.Trim()); } //对fragments按开始时间进行排序 Fragments.Sort(new LrcFragmentComparer()); //对Fragments集合中的每个Fragment计算结束时间 ComputeEndTime(); } // 根据播放进度来获得当前进度下所应播放的歌词片段index // progress为播放进度,成功返回true public bool GetFragmentIndex(long progress, out int index) { .............. .............. } .............. .............. }

  现在我们根据上文提到的思路来具体实现Lrc歌词同步显示控件。因为是自定义的控件,我们使它继承自System.Windows.Controls.Control

 public class LrcDisplayControl : Control

在项目中添加Themes文件夹,并在其中添加名为Generic.xaml的文件,我们在其中定义LrcDisplayControl的外观布局:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:controls="clr-namespace:ControlsLib.Controls"
    >
    <!-- LrcDisplayControl-->
    <Style TargetType="controls:LrcDisplayControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:LrcDisplayControl">
                    <ScrollViewer x:Name="RootScrollViewer" 
                     Background
="{TemplateBinding Background}" > <Grid x:Name="RootGrid" > <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock x:Name="TopRowText" Grid.Row="0"
                        TextWrapping="Wrap" VerticalAlignment="Bottom"
                        FontSize
="{TemplateBinding FontSize}"
                       TextAlignment="Center"
                        Foreground
="{TemplateBinding Foreground}"
                       FontFamily
="{TemplateBinding FontFamily}"
                        LineHeight
="{TemplateBinding LineHeight}"/> <TextBlock x:Name="MidRowText" Grid.Row="1" TextWrapping="Wrap"
                       FontSize="{TemplateBinding FontSize}"
                       Foreground
="{TemplateBinding EmphasisBrush}" TextAlignment="Center"
                       FontFamily="{TemplateBinding FontFamily}"
                       LineHeight
="{TemplateBinding LineHeight}"/> <TextBlock x:Name="BottomRowText" Grid.Row="2" TextWrapping="Wrap"
                        FontSize
="{TemplateBinding FontSize}"
                        VerticalAlignment="Top"
                        TextAlignment="Center"
                         Foreground="{TemplateBinding Foreground}"
                        FontFamily
="{TemplateBinding FontFamily}" LineHeight="{TemplateBinding LineHeight}" /> </Grid> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>

 在LrcDisplayControl中重写OnApplyTemplate方法,并查找,实例化相关子控件

 1         public override void OnApplyTemplate()
 2         {
 3             base.OnApplyTemplate();
 4 
 5             RootScrollViewer = GetTemplateChild("RootScrollViewer") as ScrollViewer;
 6             RootGrid = GetTemplateChild("RootGrid") as Grid;
 7             TopTextBlock = GetTemplateChild("TopRowText") as TextBlock;
 8             MidTextBlock = GetTemplateChild("MidRowText") as TextBlock;
 9             BottomTextBlock = GetTemplateChild("BottomRowText") as TextBlock;
10         }

为控件定义依赖性属性LrcText ,使用户能够将歌曲的整个Lrc歌词绑定到控件

 1         public string LrcText
 2         {
 3             get { return (string)GetValue(LrcTextProperty); }
 4             set { SetValue(LrcTextProperty, value); }
 5         }
 6 
 7         public static readonly DependencyProperty LrcTextProperty =
 8             DependencyProperty.Register("LrcText", typeof(string), typeof(LrcDisplayControl), new PropertyMetadata(new PropertyChangedCallback(OnLrcTextPropertyChanged)));
 9 
10         private static void OnLrcTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
11         {
12             LrcDisplayControl lrcControl = sender as LrcDisplayControl;
13             if (lrcControl != null)
14             {
15                 lrcControl.UpdateLrcText();
16             }
17         }

从上面可以看出,当LrcText属性变化时,我们调用了控件的UpdateLrcText方法,因为歌词已经改变了,所以需要对控件进行更新,比如重新解析歌词,初始化各TextBlock的Text属性值等

        private void UpdateLrcText()
        {
            if (LrcText == null)
            {
                //歌词为空,则清空相关数据,歌词TextBlock.Text也清空
Lrc
= null; if (TopTextBlock != null) { TopTextBlock.Text = MidTextBlock.Text = BottomTextBlock.Text = null; } return; }
//歌词变化了,则重新解析歌词
Lrc
= new Lyric(LrcText); //并把让BottomTextBlock显示第一句歌词,TopTextBlock,MidTextBlock显示空 if (TopTextBlock != null) { TopTextBlock.Text = MidTextBlock.Text = null; if (Lrc.Fragments != null) { BottomTextBlock.Text = ComposeLrcFraments(0); } else { BottomTextBlock.Text = null; } } }

 为了让控件能随播放进度调整显示状态,我们在定义一个进度依赖项属性,使外界能将播放进度绑定到该属性;

        public long Progress
        {
            get { return (long)GetValue(ProgressProperty); }
            set { SetValue(ProgressProperty, value); }
        }

        public static readonly DependencyProperty ProgressProperty =
            DependencyProperty.Register("Progress", typeof(long), typeof(LrcDisplayControl), new PropertyMetadata(new PropertyChangedCallback(OnProgressPropertyChanged)));

        private static void OnProgressPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            LrcDisplayControl lrcControl = sender as LrcDisplayControl;
            if (lrcControl != null)
            {
          //进度变化了,调用控件相应方法,通知其更新
lrcControl.OnProgressChanged(); } }

当播放进度变化时,调整控件使其显示正确的歌词

        
//播放进度变化时调用,调整控件
private void OnProgressChanged() { if (Lrc == null || Lrc.Fragments.Count == 0) { return; }
int curFragIndex; //查找当前播放进度下应显示的歌词片段index,如果和当前显示的index不同,则更新
if (Lrc.GetFragmentIndex(Progress, out curFragIndex) && curFragIndex != CurFramentIndex) { TopTextBlock.Text = ComposeLrcFraments(0, curFragIndex - 1); MidTextBlock.Text = Lrc.Fragments[curFragIndex].LrcText; BottomTextBlock.Text = ComposeLrcFraments(curFragIndex + 1); CurFramentIndex = curFragIndex; UpdateVerticalScroll(); } }

至此控件功能上已经完成,为了控件更具可定制性,再定义一些依赖性属性,比如高亮的颜色,歌词行高度等

        /// <summary>
        /// 歌词高亮Brush
        /// </summary>
        public Brush EmphasisBrush
        {
            get { return (Brush)GetValue(EmphasisBrushProperty); }
            set { SetValue(EmphasisBrushProperty, value); }
        }

        public static readonly DependencyProperty EmphasisBrushProperty =
            DependencyProperty.Register("EmphasisBrush", typeof(Brush), typeof(LrcDisplayControl), null);
        
/// <summary> /// 歌词行高度 /// </summary> public double LineHeight { get { return (double)GetValue(LineHeightProperty); } set { SetValue(LineHeightProperty, value); } } public static readonly DependencyProperty LineHeightProperty = DependencyProperty.Register("LineHeight", typeof(double), typeof(LrcDisplayControl), null);

最后在我们的布局中就可以直接使用这个控件了

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" >
               
                <controls:LrcDisplayControl Grid.Row="1"
                                            Name="LrcControl" 
                                            FontSize="24"
                                            EmphasisBrush="Yellow"
                                            Progress="{Binding Progress}"
                                            LrcText="{Binding Lrc}"
                                            />      
        </Grid>

 

 

 


 

 

 

 

 

 

 

 

 

 

 

posted on 2014-03-08 19:40  西山雨  阅读(767)  评论(0编辑  收藏  举报

导航