65.9K
CodeProject 正在变化。 阅读更多。
Home

使用自定义圆形 ListBox 控件的 Silverlight 奖品轮盘动画

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (5投票s)

2010年5月25日

CPOL

5分钟阅读

viewsIcon

76491

downloadIcon

1891

本文介绍了一个使用自定义圆形 ListBox 控件的 Silverlight 轮盘动画。

引言

本文介绍了一个 Silverlight 自定义控件 ListBox,其中包含一个 RadialPanel 作为奖品轮盘。该轮盘可以加载文本、数字或图像数据。使用旋转按钮,我们可以启动轮盘动画。轮盘会旋转并选择一个随机项。第二个动画会显示选定的中奖项。该项目展示了如何使用代码隐藏和 XAML 在 Silverlight 中设计自定义控件和动画。

背景

Silverlight 自定义控件非常灵活。使用模板,我们可以设计出与常规矩形控件外观不同的自定义控件。本项目使用了一个自定义的圆形列表框和一个 RadialPanel 作为奖品轮盘。该设计灵感来源于 Jeff Prosise、Scott Guthrie 和 Jit Ghosh 关于构建自定义控件的出色博文。这些内容将在本文末尾的参考文献中列出。

输出

页面外观如下图所示。您可以在此网站找到实时演示:奖品轮盘实时演示

Prizewheel.jpg

页面加载时,奖品轮盘(默认直径为 300)会加载文本数据并显示。可以通过数字增量计数器更改轮盘的直径。按下“加载半径”按钮后,将使用新直径修改轮盘。可以通过选择数字、文本或图像三个选项之一,然后按下“加载数据”按钮来更改轮盘的数据内容。

图片轮盘如下图所示

Prizewheel_images.jpg

数字轮盘如下图所示

Prizewheel_Numbers.jpg

按下旋转按钮时,会选择一个随机数字框,然后开始轮盘旋转。右上角的计数器会加载轮盘动画的步数。当轮盘旋转时,此计数器会倒计时归零。轮盘会旋转到随机选择的项并停止。然后开始第二个动画。选定的项会被放大、平移和翻转(使用平面投影动画),并在轮盘中心显示。选定的项编号和角度会显示在右上角的列表框中。此列表框和计数器用于调试目的,如果需要,可以移除(或折叠)。

获胜者选中的图像如下所示

Prizewheel_Winner.jpg

代码描述

奖品轮盘控件模板

Silverlight 最强大的功能之一是能够完全自定义控件的外观和感觉。我已将这些功能用于设计奖品轮盘作为本项目的主要元素自定义控件。它是一个具有圆形形状和 RadialPanel 的列表框。请参阅 Scott Guthrie(参考文献 2)和 Jit Ghose(参考文献 3)关于自定义控件的出色文章。列表框的形状由 App.Xaml 文件资源中编码的模板决定。在此,我们将 Ellipse 声明为控件形状,并带有渐变填充。通过此技巧,我们将控件的视觉树替换为一个椭圆,其尺寸可以在运行时定义。通过使椭圆的宽度和高度相等,我们可以创建圆形作为控件的形状。

<Style TargetType="ListBox" x:Key="RoundListBox_Style" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Grid>
<Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.5,0.5" 
Center="0.5,0.5" >
<GradientStop Color="SteelBlue" Offset="0.1" />
<GradientStop Color="RoyalBlue" Offset="0.25" />
<GradientStop Color="LightSkyBlue" Offset="0.50" />
<GradientStop Color="LightBlue" Offset="0.75" />
<GradientStop Color="BurlyWood" Offset="1.0" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<ItemsPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

ItemsPresenter:模板中的下一个有趣之处是 ItemsPresenter。这允许我们保留多项内容模型,我们将使用另一个技巧将 RadialPanel 用作项呈现器。

自定义 RadialPanel

RadialPanel 利用了 Jeff Prosise(参考文献 1)的出色工作,该代码位于 RadialPanel.Cs 文件中。在此类中,MeasureOverrideArrangeOverride 负责将列表框项以径向布局排列。XAML 中的奖品轮盘代码如下所示

<ListBox
   Style="{StaticResource RoundListBox_Style}"
   x:Name="Prize_ListWheel">
<ListBox.RenderTransform>
<RotateTransform x:Name="Prize_ListWheel_Rotate"
   Angle="0" >
</RotateTransform>
</ListBox.RenderTransform>
<ListBox.ItemsPanel>
<ItemsPanelTemplate x:Name="RPIPT1">
<custom:RadialPanel x:Name="RadialPanel1" 
   Loaded="RadialPanel1_Loaded"
   ItemAlignment="Center"
   ItemOrientation="Rotated" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

RadialPanel1_Loaded 事件处理程序获取 RadialPanel 的半径,并调用 ModifyWheelParameters() 来修改轮盘参数。

private void RadialPanel1_Loaded(object sender, RoutedEventArgs e)
{
    radialpanelxaml =(RadialPanel) sender;
    ModifyWheelParameters();
    Load_Wheel_Data();
}

ModifyWheelParameters 从增量计数器计算半径值,并使用因子 wheelradiusfactor(为 2.4)调整 Prize_ListWheel 椭圆的宽度和高度。此因子将 RadialPanel 拟合到列表框圆形形状内。

private void ModifyWheelParameters()
{
    Prize_ListWheel.ItemsSource = "";
    radialpanelradius = Convert.ToInt16(Counter_Radius.Value);
    radialpanelxaml.Radius = radialpanelradius;
    Prize_ListWheel.Height = radialpanelradius * wheelradiusfactor;
    Prize_ListWheel.Width = radialpanelradius * wheelradiusfactor;
    Prize_ListWheel_Rotate.CenterX = Prize_ListWheel.Width/2;
    Prize_ListWheel_Rotate.CenterY = Prize_ListWheel.Height/2;
    Canvas.SetLeft(Prize_ListWheel, ListWheelLeft);
    Canvas.SetTop(Prize_ListWheel, ListWheelTop);
    RectMaskCreate();
}

动画代码

本项目中有两个动画。主要动画是奖品轮盘的旋转。它非常简单直接。Main.Xaml 中的这段代码决定了旋转变换

<ListBox.RenderTransform>
<RotateTransform x:Name="Prize_ListWheel_Rotate"
      Angle="0" >
</RotateTransform>
</ListBox.RenderTransform>

当按下 Spin 按钮时,会引发 SpinCode_Start_Click 事件,该事件会选择一个随机数并计算需要旋转的角度。变量 numberofrotations 设置为两次旋转(因此轮盘将旋转两次)+ 随机列表框项的角度。变量 anglefactor 设置为 4,以增加旋转步数,从而产生平滑的旋转轮盘运动。此事件还会启动 myDispatchTimer,这是一个在 MainPage() 中设置的 25 毫秒计时器。

private void SpinCode_Start_Click(object sender, RoutedEventArgs e)
{
    //Bring box to original position from previous win
    Normal_Shift_Box(oldrandomnumber);
    Random random = new Random();
    if (Pictures_Button.IsChecked==false)
    {
    randomnumber = random.Next(0, itextmaxcount);
    animanglerot =360.0 / Convert.ToDouble(itextmaxcount);
    anglesteps = Convert.ToInt64((itextmaxcount*numberofrotations
    - randomnumber+oldrandomnumber) * anglefactor);
    }
    else
    {
    randomnumber = random.Next(0, iimagemaxcount);
    animanglerot = 360.0 / Convert.ToDouble(iimagemaxcount);
    anglesteps = Convert.ToInt64((iimagemaxcount *
    numberofrotations - randomnumber + oldrandomnumber) * anglefactor);
}
    anglesteps--;
    Prize_ListWheel_Rotate.Angle += animanglerot / anglefactor;
    myDispatcherTimer.Start();
}

每隔 25 毫秒,就会引发 Each_Tick 事件。此事件会使轮盘旋转一步,直到 anglesteps 计数器归零。

public void Each_Tick(object o, EventArgs sender)
{
    if (anglesteps > 0)
    {
        Prize_ListWheel_Rotate.Angle += animanglerot / anglefactor;
        anglesteps--;
    }
    else
    {
        ListBoxWinners.Items.Add(randomnumber.ToString()+", "    + 
                                 Prize_ListWheel_Rotate.Angle.ToString());
        myDispatcherTimer.Stop();
        Winner_Animation();
        oldrandomnumber = randomnumber;
    }
    TextBox_Counter.Text = anglesteps.ToString();
}

anglesteps 归零时,会启动第二个动画和 myDispatcherTimer2

此动画使用 Scale_Shift_Box,它结合使用 PlaneProjectionScaleTransformTranslateTransform 来创建第二个动画,将获胜项带到奖品轮盘的顶部中心。

private void Winner_Animation()
{
    double Scale=1;
    double translatefactor=-0.5;
    int selectedbox = randomnumber;
    TextWinner.Visibility = Visibility.Visible;
    transform_animation_method(Scale, translatefactor, selectedbox);
}

private void Scale_Shift_Box(int boxnumber)
{
    TransformGroup tg = new TransformGroup();
    ScaleTransform st = new ScaleTransform();
    PlaneProjection pp = new PlaneProjection();
    st.ScaleX = 1 + Scale10 / step10;
    st.ScaleY = 1 + Scale10 / step10;
    TranslateTransform tt = new TranslateTransform();
    tt.X = xincrement * (stepmax - step10);
    tt.Y = yincrement * (stepmax - step10);
    tg.Children.Add(st);
    tg.Children.Add(tt);
    if (Pictures_Button.IsChecked == true)
    {
        imagelist[boxnumber].RenderTransform = tg;
        imagelist[boxnumber].Projection = pp;
        pp.RotationY = 15 * step10;
    }
    else
    {
        namelist[boxnumber].RenderTransform = tg;
        namelist[boxnumber].Projection = pp;
        pp.RotationY = 15 * step10;
    }
}

关注点

自定义控件是 Sivlerlight 中非常有趣的一部分。Jeff Prosise、Scott Guthrie、Jit Ghosh 的文章将为您提供关于自定义控件的全面概述。您还可以使用 Lester 的博客(参考文献 4)提供的 Xamlpadx-v2,并结合 Jit Ghosh 博客中的信息来理解 Silverlight 控件中的视觉树。

参考文献

  1. Silverlight 中的径向布局,Jeff Prosise 关于 RadialPanel 控件的博文。
  2. Silverlight 教程第 7 部分:使用控件模板自定义控件的外观和感觉,Scott Guthrie 关于自定义控件的博文。
  3. WPF 控件模板 - 概述,Jit Ghosh 关于自定义控件的博文。
  4. Xamlpadx-v2,Lester 的 XML 博客中关于 Xamlpadx-v2 的链接,这是一个理解自定义控件的出色工具。
  5. 奖品轮盘实时演示:此项目的实时演示。

作者的其他文章

历史

这是本文的第一个版本。

© . All rights reserved.