让你的控件舞动起来

最近有个需求是需要窗体上要有很炫的效果,比如动画划过的panel,跟随鼠标移动的动画,动画扩大缩小的panel,诸如此类,如果单独写这些效果一是要被烦死,二是窗体内的代码会一团糟,所以弄了个指挥其他控件移动变形的控件。

使用方法很简单,首先把这个控件拖到窗体

image

然后在需要移动其他控件的时候,比如我想在窗体上让一个按钮移动到鼠标当前点击的位置,那么只需要在窗体的mouse_click事件里这么写:

 private void Form1_MouseClick(object sender, MouseEventArgs e)
{
trans.MoveTo(button1.Location, e.Location, button1,
10);
}

这样当你在窗体上点击鼠标左键的时候,按钮就会跟着你的鼠标跑啦。

效果如下,由于屏幕录像的原因效果很粗糙

 

 

当然不止能让控件移动,改变大小的动画也行,这些动画都是缓动的,也就是开始很快,然后慢慢变慢直到停止。接下来我们来看看是如何实现这些效果的。首先动画的原理大家都知道是视觉暂留原理,由于GDI+的绘图很繁重,暂时无法实现24帧每秒的完美动画,所以我们暂定为20帧每秒。而驱动动画的肯定是Timmer控件了,所以这里Timmer的间隔时间为50ms。

所以第一步我们是新建一个自动控件。并拖入一个Timmer控件,注意,是winform的Timmer,只有这个Timmer的事件里操作窗体控件不用处理线程的问题。

image image

有的时候可能需要移动和变形的是多个控件,我们将控件的移动行为抽象为一个行为的对象:

image

移动和变形的数据都是两个int的元组,所以用一个类Data来抽象

 

 class Data
{
public int X { get; set; }
public int Y { get; set; }
public Data(int x, int y)
{
X
= x;
Y
= y;
}
}

由于数据不知道是变形还是位移,所以用一个Action来把为控件赋值的操作独立出来,为了减少在Timmer的事件里的计算,加快速度,所以在移动和变形前就计算好每一个步骤的值,这些值放在Steps中,好了,接下来为Item增加计算所有步骤的方法:

 

 public void Compute(int Step)
{
int XDirection = To.X.CompareTo(From.X);
int YDirection = To.Y.CompareTo(From.Y);
Steps.Add(From);
int x = From.X;
int y = From.Y;
double itemX = ((double)Math.Abs(From.X - To.X)) / StepToItem(Step);
double itemY = ((double)Math.Abs(From.Y - To.Y)) / StepToItem(Step);
for (int i = Step; i > 0; i--)
{
double offsetX = (i * 2 - 1) * itemX;
double offsetY = (i * 2 - 1) * itemY;
if (XDirection > 0)
{
x
= (int)(x + offsetX);
}
if (XDirection < 0)
{
x
= (int)(x - offsetX);
}
if (YDirection > 0)
{
y
= (int)(y + offsetY);
}
if (YDirection < 0)
{
y
= (int)(y - offsetY);
}
Steps.Add(
new Data(x, y));
}
Steps.Add(To);
}

参数Steps是设定要计算多少个中间步骤,数值越大移动越慢,数值越小移动越快。

接下来在控件中增加一个list来存储需要移动的行为,并初始化

image

然后在Timmer的事件里驱动移动的步骤:

 

 private void ProcessItems(object sender, EventArgs e)
{
List
<Item> removelist = new List<Item>();
foreach (Item item in Items)
{
item.MoveStep
++;
if (item.MoveStep < item.Steps.Count)
{
item.CurrentStep
= item.Steps[item.MoveStep];
item.Moving();
}
else { removelist.Add(item); } } if (removelist.Count > 0)
{
foreach (Item item in removelist)
{
Items.Remove(item);
}
}
if (Items.Count < 1 )
{
ProcessTimer.Stop();
}
}

移动完成的就把项目从列表里移除。如果没有项目可以移动了就关闭timmer节约资源

 

最后实现两个方法来启动移动和形变的过程:

 

 public void MoveTo(Point From, Point To, Control Target, int Step)
{
Item item
= new Item()
{
CurrentStep
= new Data(From.X,From.Y),
From
= new Data(From.X,From.Y),
To
= new Data(To.X,To.Y),
MoveStep
= 0,
Steps
= new List<Data>(),
Target
= Target
};
item.SetValue
= (d, c) => { c.Location = new Point(d.X, d.Y);
};
item.Compute(Step);
Items.Add(item);
if (!ProcessTimer.Enabled)
{
ProcessTimer.Start();
}
}

public void ScaleTo(Size From,Size To,Control Target,int Step)
{
Item item
= new Item()
{
CurrentStep
= new Data(From.Width, From.Height),
From
= new Data(From.Width, From.Height),
To
= new Data(To.Width, To.Height),
MoveStep
= 0,
Steps
= new List<Data>(),
Target
= Target
};
item.SetValue
= (d, c) => { c.Size = new Size(d.X, d.Y);
};
item.Compute(Step);
Items.Add(item);
if (!ProcessTimer.Enabled)
{
ProcessTimer.Start();
}
}

如果你跟着本文的步骤一起做了,那么现在就可以来尝试玩一玩这个东西了。

最后强调一下,一定要把窗体和控件的双缓冲都打开,不然就是眼睛被闪了,置于如何开双缓冲就不在本文范围内了

posted on 2009-11-20 16:49  亚历山大同志  阅读(2851)  评论(8编辑  收藏  举报

导航