本文經原作者授權以原創方式二次分享,歡迎轉載、分享。
原文作者:普通的地球人
原文地址:https://www.cnblogs.com/tsliwei/p/6282183.html
Github地址:https://github.com/WPFDevelopersOrg/WPFDevelopers
效果
前陣子看到ay的蜘蛛網效果和知乎的登錄頁背景,覺得效果很酷.自己也想寫一個.于是寫著寫著就變成這樣了.少女夢幻的趕腳有木有.我這有著一顆少女心的摳腳大漢;
實現思路分為兩個部分:
1)星星無休止的漫游;
2)星星之間的連線;
星星和連線非別放到兩個容器里,以便分開操作;
星星
把星星的運動分解為
X
軸和Y
軸兩個不相干的運動,分別操作.操作就是隨機生成一個速度,隨機生成一個時間.運動完之后再隨機生成一個速度,隨機生成一個時間......無限循環;星星的旋轉也是同樣的道理;
連線
首先解釋下連線的規則.兩個星星之間連線,每個星星都有一個連線的勢力范圍,就是寬度乘以連線倍率,這個連線倍率可以在窗體設置.當兩個勢力范圍有交集的時候,就連線;
例:
星1寬度5
,星2寬度10
,連線倍率是3
,那么這兩個星星的距離小于5*3+10*3=45
時就連線,大于45
時斷開.如果連線倍率設置為4
,則兩個星星減的距離小于5*4+10*4=60
時連線,大于60
時斷開;
實現與資源占有率
星星運動的實現有兩種:
1)基于Grid
和TranslateTransform
用DoubleAnimation
動畫控制星星的位移.
2)基于Canvas
通過幀動畫控制Canvas
的X,Y
.
連線的實現也有兩種:
1)簡單粗暴.在每一幀都清空連線容器.然后雙層循環星星,重新連接所有星星(符合連線規則的).
2)在每一幀循環連線,判斷連線規則.符合就改變此連線的X1,Y1,X2,Y2
而不去重新new
連線.不符合規則的就移除.然后依然是雙層循環星星,看符合規則的兩個星星間有沒有連線,沒有的就new一個.
眾所周知,WPF
做這種動畫資源占有率還是比較高的,寫了這么多實現,也是因為這個.
大體上還是基于Canvas
的實現占用資源稍低.但也有個問題,如果給星星再加一個模糊效果的話,基于Canvas
實現的資源占有率不會飆升,而是幀數明顯降低.(也可能是我電腦環境的原因)
并不能說那種實現好與壞,可能具體運行環境不一樣,參數設置不一樣,每種實現都有不同的表現.
然后關于資源占有率問題,以我目前的水平,就只能到這了.博友們自己取舍吧.
源碼如下
1)StarrySky.cs
代碼如下;
using?System;
using?System.Collections.Generic;
using?System.Windows;
using?System.Windows.Controls;
using?System.Windows.Media;
using?System.Windows.Media.Animation;
using?System.Windows.Shapes;namespace?WPFDevelopers.Controls
{[TemplatePart(Name?=?GridTemplateName,?Type?=?typeof(Grid))][TemplatePart(Name?=?CanvasTemplateName,?Type?=?typeof(Canvas))]public?class?StarrySky?:?Control{private?const?string?GridTemplateName?=?"PART_GridLineContainer";private?const?string?CanvasTemplateName?=?"PART_CanvasStarContainer";private?Grid?_grid;private?Canvas?_canvas;public?static?readonly?DependencyProperty?StarCountProperty?=DependencyProperty.Register("StarCount",?typeof(int),?typeof(StarrySky),?new?UIPropertyMetadata(10));public?static?readonly?DependencyProperty?StarSizeMinProperty?=DependencyProperty.Register("StarSizeMin",?typeof(int),?typeof(StarrySky),?new?UIPropertyMetadata(5));public?static?readonly?DependencyProperty?StarSizeMaxProperty?=DependencyProperty.Register("StarSizeMax",?typeof(int),?typeof(StarrySky),?new?UIPropertyMetadata(20));public?static?readonly?DependencyProperty?StarVMinProperty?=DependencyProperty.Register("StarVMin",?typeof(int),?typeof(StarrySky),?new?UIPropertyMetadata(10));public?static?readonly?DependencyProperty?StarVMaxProperty?=DependencyProperty.Register("StarVMax",?typeof(int),?typeof(StarrySky),?new?UIPropertyMetadata(20));public?static?readonly?DependencyProperty?StarRVMinProperty?=DependencyProperty.Register("StarRVMin",?typeof(int),?typeof(StarrySky),?new?UIPropertyMetadata(90));public?static?readonly?DependencyProperty?StarRVMaxProperty?=DependencyProperty.Register("StarRVMax",?typeof(int),?typeof(StarrySky),?new?UIPropertyMetadata(360));public?static?readonly?DependencyProperty?LineRateProperty?=DependencyProperty.Register("LineRate",?typeof(int),?typeof(StarrySky),?new?UIPropertyMetadata(3));private?readonly?Random?_random?=?new?Random();private?StarInfo[]?_stars;static?StarrySky(){DefaultStyleKeyProperty.OverrideMetadata(typeof(StarrySky),new?FrameworkPropertyMetadata(typeof(StarrySky)));}public?StarrySky(){Loaded?+=?delegate{CompositionTarget.Rendering?+=?delegate{StarRoamAnimation();AddOrRemoveStarLine();MoveStarLine();};};}public?override?void?OnApplyTemplate(){base.OnApplyTemplate();_grid?=?GetTemplateChild(GridTemplateName)?as?Grid;_canvas?=?GetTemplateChild(CanvasTemplateName)?as?Canvas;}public?int?StarCount{get?=>?(int)GetValue(StarCountProperty);set?=>?SetValue(StarCountProperty,?value);}public?int?StarSizeMin{get?=>?(int)GetValue(StarSizeMinProperty);set?=>?SetValue(StarSizeMinProperty,?value);}public?int?StarSizeMax{get?=>?(int)GetValue(StarSizeMaxProperty);set?=>?SetValue(StarSizeMaxProperty,?value);}public?int?StarVMin{get?=>?(int)GetValue(StarVMinProperty);set?=>?SetValue(StarVMinProperty,?value);}public?int?StarVMax{get?=>?(int)GetValue(StarVMaxProperty);set?=>?SetValue(StarVMaxProperty,?value);}public?int?StarRVMin{get?=>?(int)GetValue(StarRVMinProperty);set?=>?SetValue(StarRVMinProperty,?value);}public?int?StarRVMax{get?=>?(int)GetValue(StarRVMaxProperty);set?=>?SetValue(StarRVMaxProperty,?value);}public?int?LineRate{get?=>?(int)GetValue(LineRateProperty);set?=>?SetValue(LineRateProperty,?value);}public?void?InitStar(){//清空星星容器_stars?=?new?StarInfo[StarCount];_canvas.Children.Clear();_grid.Children.Clear();//生成星星for?(var?i?=?0;?i?<?StarCount;?i++){double?size?=?_random.Next(StarSizeMin,?StarSizeMax?+?1);?//星星尺寸var?starInfo?=?new?StarInfo{X?=?_random.Next(0,?(int)_canvas.ActualWidth),XV?=?(double)_random.Next(-StarVMax,?StarVMax)?/?60,XT?=?_random.Next(6,?301),?//幀Y?=?_random.Next(0,?(int)_canvas.ActualHeight),YV?=?(double)_random.Next(-StarVMax,?StarVMax)?/?60,YT?=?_random.Next(6,?301),?//幀StarLines?=?new?Dictionary<StarInfo,?Line>()};var?star?=?new?Path{Data?=?Application.Current.Resources["PathStarrySky"]?as?Geometry,Width?=?size,Height?=?size,Stretch?=?Stretch.Fill,Fill?=?GetRandomColorBursh(),RenderTransformOrigin?=?new?Point(0.5,?0.5),RenderTransform?=?new?RotateTransform?{?Angle?=?0?}};Canvas.SetLeft(star,?starInfo.X);Canvas.SetTop(star,?starInfo.Y);starInfo.StarRef?=?star;//設置星星旋轉動畫SetStarRotateAnimation(star);//添加到容器_stars[i]?=?starInfo;_canvas.Children.Add(star);}}private?void?SetStarRotateAnimation(Path?star){double?v?=?_random.Next(StarRVMin,?StarRVMax?+?1);?//速度double?a?=?_random.Next(0,?360?*?5);?//角度var?t?=?a?/?v;?//時間var?dur?=?new?Duration(new?TimeSpan(0,?0,?0,?0,?(int)(t?*?1000)));var?sb?=?new?Storyboard{Duration?=?dur};//動畫完成事件?再次設置此動畫sb.Completed?+=?(S,?E)?=>?{?SetStarRotateAnimation(star);?};var?da?=?new?DoubleAnimation{To?=?a,Duration?=?dur};Storyboard.SetTarget(da,?star);Storyboard.SetTargetProperty(da,?new?PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));sb.Children.Add(da);sb.Begin(this);}private?SolidColorBrush?GetRandomColorBursh(){var?r?=?(byte)_random.Next(128,?256);var?g?=?(byte)_random.Next(128,?256);var?b?=?(byte)_random.Next(128,?256);return?new?SolidColorBrush(Color.FromRgb(r,?g,?b));}///?<summary>///?????星星漫游動畫///?</summary>private?void?StarRoamAnimation(){if?(_stars?==?null)return;foreach?(var?starInfo?in?_stars){//X軸運動if?(starInfo.XT?>?0){//運動時間大于0,繼續運動if?(starInfo.X?>=?_canvas.ActualWidth?||?starInfo.X?<=?0)//碰到邊緣,速度取反向starInfo.XV?=?-starInfo.XV;//位移加,時間減starInfo.X?+=?starInfo.XV;starInfo.XT--;Canvas.SetLeft(starInfo.StarRef,?starInfo.X);}else{//運動時間小于0,重新設置速度和時間starInfo.XV?=?(double)_random.Next(-StarVMax,?StarVMax)?/?60;starInfo.XT?=?_random.Next(100,?1001);}//Y軸運動if?(starInfo.YT?>?0){//運動時間大于0,繼續運動if?(starInfo.Y?>=?_canvas.ActualHeight?||?starInfo.Y?<=?0)//碰到邊緣,速度取反向starInfo.YV?=?-starInfo.YV;//位移加,時間減starInfo.Y?+=?starInfo.YV;starInfo.YT--;Canvas.SetTop(starInfo.StarRef,?starInfo.Y);}else{//運動時間小于0,重新設置速度和時間starInfo.YV?=?(double)_random.Next(-StarVMax,?StarVMax)?/?60;starInfo.YT?=?_random.Next(100,?1001);}}}///?<summary>///?????添加或者移除星星之間的連線///?</summary>private?void?AddOrRemoveStarLine(){//沒有星星?直接返回if?(_stars?==?null?||?StarCount?!=?_stars.Length)return;//生成星星間的連線for?(var?i?=?0;?i?<?StarCount?-?1;?i++)for?(var?j?=?i?+?1;?j?<?StarCount;?j++){var?star1?=?_stars[i];var?x1?=?star1.X?+?star1.StarRef.Width?/?2;var?y1?=?star1.Y?+?star1.StarRef.Height?/?2;var?star2?=?_stars[j];var?x2?=?star2.X?+?star2.StarRef.Width?/?2;var?y2?=?star2.Y?+?star2.StarRef.Height?/?2;var?s?=?Math.Sqrt((y2?-?y1)?*?(y2?-?y1)?+?(x2?-?x1)?*?(x2?-?x1));?//兩個星星間的距離var?threshold?=?star1.StarRef.Width?*?LineRate?+?star2.StarRef.Width?*?LineRate;if?(s?<=?threshold){if?(!star1.StarLines.ContainsKey(star2)){var?line?=?new?Line{X1?=?x1,Y1?=?y1,X2?=?x2,Y2?=?y2,Stroke?=?GetStarLineBrush(star1.StarRef,?star2.StarRef)};star1.StarLines.Add(star2,?line);_grid.Children.Add(line);}}else{if?(star1.StarLines.ContainsKey(star2)){_grid.Children.Remove(star1.StarLines[star2]);star1.StarLines.Remove(star2);}}}}///?<summary>///?????移動星星之間的連線///?</summary>private?void?MoveStarLine(){//沒有星星?直接返回if?(_stars?==?null)return;foreach?(var?star?in?_stars)foreach?(var?starLine?in?star.StarLines){var?line?=?starLine.Value;line.X1?=?star.X?+?star.StarRef.Width?/?2;line.Y1?=?star.Y?+?star.StarRef.Height?/?2;line.X2?=?starLine.Key.X?+?starLine.Key.StarRef.Width?/?2;line.Y2?=?starLine.Key.Y?+?starLine.Key.StarRef.Height?/?2;}}///?<summary>///?????獲取星星連線顏色畫刷///?</summary>///?<param?name="star0">起始星星</param>///?<param?name="star1">終點星星</param>///?<returns>LinearGradientBrush</returns>private?LinearGradientBrush?GetStarLineBrush(Path?star0,?Path?star1){return?new?LinearGradientBrush{GradientStops?=?new?GradientStopCollection{new?GradientStop?{?Offset?=?0,?Color?=?(star0.Fill?as?SolidColorBrush).Color?},new?GradientStop?{?Offset?=?1,?Color?=?(star1.Fill?as?SolidColorBrush).Color?}}};}}///?<summary>///?????星星///?</summary>internal?class?StarInfo{///?<summary>///?????X坐標///?</summary>public?double?X?{?get;?set;?}///?<summary>///?????X軸速度(單位距離/幀)///?</summary>public?double?XV?{?get;?set;?}///?<summary>///?????X坐標以X軸速度運行的時間(幀)///?</summary>public?int?XT?{?get;?set;?}///?<summary>///?????Y坐標///?</summary>public?double?Y?{?get;?set;?}///?<summary>///?????Y軸速度(單位距離/幀)///?</summary>public?double?YV?{?get;?set;?}///?<summary>///?????Y坐標以Y軸速度運行的時間(幀)///?</summary>public?int?YT?{?get;?set;?}///?<summary>///?????對星星的引用///?</summary>public?Path?StarRef?{?get;?set;?}public?Dictionary<StarInfo,?Line>?StarLines?{?get;?set;?}}
}
2)StarrySky.xaml
代碼如下;
<ResourceDictionary?xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:controls="clr-namespace:WPFDevelopers.Controls"><ResourceDictionary.MergedDictionaries><ResourceDictionary?Source="Basic/ControlBasic.xaml"/></ResourceDictionary.MergedDictionaries><RadialGradientBrush?x:Key="StarrySkyRadialGradientBrush"?GradientOrigin="0.5,0"?Center="0.5,0.3"?RadiusX="0.7"><GradientStop?Color="#FF04040E"?Offset="0"/><GradientStop?Color="#FF24315D"?Offset="1"/></RadialGradientBrush><Style?TargetType="{x:Type?controls:StarrySky}"?BasedOn="{StaticResource?ControlBasicStyle}"><Setter?Property="Background"?Value="{StaticResource?StarrySkyRadialGradientBrush}"></Setter><Setter?Property="Template"><Setter.Value><ControlTemplate?TargetType="{x:Type?controls:StarrySky}"><Grid?Background="{TemplateBinding?Background}"><Grid?x:Name="PART_GridLineContainer"/><Canvas?x:Name="PART_CanvasStarContainer"/></Grid></ControlTemplate></Setter.Value></Setter></Style>
</ResourceDictionary>
3)StarrySkyExample.xaml
代碼如下;
<UserControl?x:Class="WPFDevelopers.Samples.ExampleViews.StarrySkyExample"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"?xmlns:d="http://schemas.microsoft.com/expression/blend/2008"?xmlns:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"xmlns:ws="https://github.com/WPFDevelopersOrg.WPFDevelopers.Minimal"xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews"mc:Ignorable="d"?d:DesignHeight="450"?d:DesignWidth="800"><UserControl.Resources><Style?TargetType="{x:Type?TextBlock}"><Setter?Property="Foreground"?Value="White"></Setter><Setter?Property="FontSize"?Value="14"></Setter><Setter?Property="VerticalAlignment"?Value="Center"></Setter><Setter?Property="Margin"?Value="2"></Setter></Style><Style?TargetType="{x:Type?StackPanel}"><Setter?Property="Margin"?Value="2"></Setter></Style><Style?TargetType="{x:Type?TextBox}"?BasedOn="{StaticResource?{x:Type?TextBox}}"><Setter?Property="ws:ElementHelper.Watermark"?Value="輸入內容"></Setter><Setter?Property="Margin"?Value="-30,0"></Setter><Setter?Property="Width"?Value="100"></Setter></Style></UserControl.Resources><Grid><!--<wpfdev:StarrySky?StarCount="{Binding?ElementName=tbx_starCount,Path=Text}"StarSizeMin="{Binding?ElementName=tbx_starSizeMin,Path=Text}"StarSizeMax="{Binding?ElementName=tbx_starSizeMax,Path=Text}"StarVMin="{Binding?ElementName=tbx_starVMin,Path=Text}"StarVMax="{Binding?ElementName=tbx_starVMax,Path=Text}"StarRVMin="{Binding?ElementName=tbx_starRVMin,Path=Text}"StarRVMax="{Binding?ElementName=tbx_starRVMax,Path=Text}"LineRate="{Binding?ElementName=tbx_lineRate,Path=Text}"Name="myStarrySky"></wpfdev:StarrySky>--><wpfdev:StarrySky?Name="myStarrySky"></wpfdev:StarrySky><StackPanel?HorizontalAlignment="Left"?VerticalAlignment="Top"><StackPanel?Orientation="Horizontal"><TextBlock?Text="星星個數:"></TextBlock><TextBox?x:Name="tbx_starCount"?Text="{Binding?ElementName=myStarrySky,Path=StarCount}"/></StackPanel><StackPanel?Orientation="Horizontal"><TextBlock?Text="最小尺寸:"></TextBlock><TextBox?Name="tbx_starSizeMin"??Text="{Binding?ElementName=myStarrySky,Path=StarSizeMin}"></TextBox></StackPanel><StackPanel?Orientation="Horizontal"><TextBlock?Text="最大尺寸:"></TextBlock><TextBox?Name="tbx_starSizeMax"?Text="{Binding?ElementName=myStarrySky,Path=StarSizeMax}"></TextBox></StackPanel><StackPanel?Orientation="Horizontal"><TextBlock?Text="最小速度:"></TextBlock><TextBox?Name="tbx_starVMin"?Text="{Binding?ElementName=myStarrySky,Path=StarVMin}"></TextBox></StackPanel><StackPanel?Orientation="Horizontal"><TextBlock?Text="最大速度:"></TextBlock><TextBox?Name="tbx_starVMax"?Text="{Binding?ElementName=myStarrySky,Path=StarVMax}"></TextBox></StackPanel><StackPanel?Orientation="Horizontal"><TextBlock?Text="最小轉速:"></TextBlock><TextBox?Name="tbx_starRVMin"?Text="{Binding?ElementName=myStarrySky,Path=StarRVMin}"></TextBox></StackPanel><StackPanel?Orientation="Horizontal"><TextBlock?Text="最大轉速:"></TextBlock><TextBox?Name="tbx_starRVMax"?Text="{Binding?ElementName=myStarrySky,Path=StarRVMax}"></TextBox></StackPanel><StackPanel?Orientation="Horizontal"><TextBlock?Text="連線倍率:"></TextBlock><TextBox?Name="tbx_lineRate"?Text="{Binding?ElementName=myStarrySky,Path=LineRate}"></TextBox></StackPanel><Button?Name="btn_render"?Content="生成"?Click="btn_render_Click"/></StackPanel></Grid>
</UserControl>
4)StarrySkyExample.xaml.cs
代碼如下;
using?System.Windows.Controls;namespace?WPFDevelopers.Samples.ExampleViews
{///?<summary>///?StarrySkyExample.xaml?的交互邏輯///?</summary>public?partial?class?StarrySkyExample?:?UserControl{public?StarrySkyExample(){InitializeComponent();}private?void?btn_render_Click(object?sender,?System.Windows.RoutedEventArgs?e){myStarrySky.InitStar();}}
}
源碼1[1]Gtihub[2]Gitee[3]
參考資料
[1]
源碼: https://files.cnblogs.com/files/tsliwei/StarrySkyBasedOnCanvasYOUHUA.zip
[2]Gtihub: https://github.com/WPFDevelopersOrg/WPFDevelopers
[3]gitee: https://gitee.com/WPFDevelopersOrg/WPFDevelopers