zoukankan      html  css  js  c++  java
  • 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>


     

  • 相关阅读:
    字典树入门
    Cyclic Nacklace HDU 3746 KMP 循环节
    KMP字符串匹配 模板 洛谷 P3375
    Phone List POJ-3630 字典树 or 暴力
    stringstream istringstream ostringstream 三者的区别
    单词数 HDU 2072 字符串输入控制
    逆序单词 HIhoCoder 1366 字典树
    input框中修改placeholder的样式
    如何使用$.each()与$().each()以及他们的区别
    css解决input的阴影
  • 原文地址:https://www.cnblogs.com/westmount/p/3550656.html
Copyright © 2011-2022 走看看