您的位置:首页 > 产品设计 > UI/UE

(3)FluidMoveBehavior 之模仿 Windows Phone 开始菜单的 Tile 长按后排序

2013-09-05 16:46 316 查看
这个工程和上一篇 (2)中介绍的排序大同小异,只是比上一篇交换复杂一点,不是通过单击进行交换,

而是拖动一个 Tile 到另一个 Tile 上时,判断两个 Tile 的中心距离是否符合条件来判断是否进行交换两个 Tile。

归根结底还是利用 FluidMoveBehavior 行为来使 Silverlight 的元素在重新定位时,产生动画效果。毕竟在

实际开发中,用户体验还是很重要的,生动的交互比生硬的交互会更让用户感到亲切。

当然项目中也用到了视觉状态管理相关的技术,因为不是重点,这里不会过多的介绍。

效果交互图:

public partial class TileControl : UserControl
{
public TileControl()
{
InitializeComponent();
this.DataContext = this;
}

#region TileBackground (依赖属性)
/// <summary>
/// Tile 的背景图片
/// </summary>
public string TileBackground
{
get
{
return (string)GetValue(TileBackgroundProperty);
}
set
{
SetValue(TileBackgroundProperty, value);
}
}
public static readonly DependencyProperty TileBackgroundProperty =
DependencyProperty.Register("TileBackground", typeof(string), typeof(TileControl),
new PropertyMetadata("/Assets/Tiles/FlipCycleTileMedium.png"));

#endregion

// 漂浮
static Random rand = new Random();
public void Float()
{
// 使 tile 进入不同的漂浮状态
switch (rand.Next(5))
{
case 1:
VisualStateManager.GoToState(this, "FloatVisualState1", false);
break;
case 2:
VisualStateManager.GoToState(this, "FloatVisualState2", false);
break;
case 3:
VisualStateManager.GoToState(this, "FloatVisualState3", false);
break;
case 4:
VisualStateManager.GoToState(this, "FloatVisualState4", false);
break;
default: VisualStateManager.GoToState(this, "FloatVisualState3", false);
break;
}
}

// 选中
public void Checked()
{
VisualStateManager.GoToState(this, "CheckedVisualState", false);
}

// 恢复
public void Reset()
{
VisualStateManager.GoToState(this, "NormalVisualState", false);
}
}


View Code

第二步:定义不同的 Tile 在重叠时,判断中心点的距离,是否小于 40px,如果是,则交换两个 Tile。

如果 Tile 控件的 宽和 高设置为 150px,则各个控件的坐标为:



当拖动一个 Tile 到其它 Tile 上时,判断两个 Tile 的中心点坐标的距离:



第三步:在 MainPage 页面中,添加一个 Grid,分别放置各个自定义控件,并且每个自定义控件放在一个 Boder 里面,

并且分别注册 Border 的长按事件 Hold 统一为 Border_Hold:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.Resources>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent"/>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<i:Interaction.Behaviors>
<el:FluidMoveBehavior x:Name="fluidBehavior" AppliesTo="Children" Duration="00:00:01">
<el:FluidMoveBehavior.EaseY>
<QuinticEase EasingMode="EaseOut"/>
</el:FluidMoveBehavior.EaseY>
<el:FluidMoveBehavior.EaseX>
<QuinticEase EasingMode="EaseOut"/>
</el:FluidMoveBehavior.EaseX>
</el:FluidMoveBehavior>
</i:Interaction.Behaviors>
<Border Hold="Border_Hold">
<local:TileControl TileBackground="/Assets/Tiles/01.png"/>
</Border>
<Border Grid.Column="1" Hold="Border_Hold">
<local:TileControl TileBackground="/Assets/Tiles/02.png"/>
</Border>
<Border  Grid.Row="1"  Hold="Border_Hold">
<local:TileControl TileBackground="/Assets/Tiles/03.png"/>
</Border>
<Border Grid.Row="1" Grid.Column="1"  Hold="Border_Hold">
<local:TileControl TileBackground="/Assets/Tiles/04.png"/>
</Border>
<Border Grid.Row="2"  Hold="Border_Hold">
<local:TileControl TileBackground="/Assets/Tiles/05.png"/>
</Border>
<Border  Grid.Row="2" Grid.Column="1" Hold="Border_Hold">
<local:TileControl TileBackground="/Assets/Tiles/06.png"/>
</Border>
</Grid>


第四步:在 MainPage 页面的 CodeBehind 中,定义一个类型为 Dictionary<Point, Border> 的泛型字典 dic,

当页面加载完成时,初始化该字典:

public MainPage()
{
InitializeComponent();
this.Loaded += MainPage_Loaded;
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
InitDic();
}

// Size : 资源素距离父元素左上角的距离
Dictionary<Point, Border> dic = new Dictionary<Point, Border>();
int childWidth;

// 初始化一个字典集合,Key:border 元素的中心点坐标,value:border 自己
void InitDic()
{
dic.Clear();

foreach (var item in ContentPanel.Children)
{
childWidth = (int)item.DesiredSize.Width;

Border border = item as Border;
int row = Grid.GetRow(border);
int col = Grid.GetColumn(border);
Point position = new Point(col * childWidth + childWidth / 2, row * childWidth + childWidth / 2);
border.Tag = position;

dic.Add(position, border);

Debug.WriteLine("point-  x:" + (col * childWidth + childWidth / 2) + "  y:" + (row * childWidth + childWidth / 2));
}
}


在 Border 的 Border_Hold 方法中,添加逻辑,用来处理各个 Tile 的视图状态,并且注册 ManipulationDelta 和 MouseLeftButtonUp 事件:

Border borderTemp;
CompositeTransform transformTemp;
private void Border_Hold(object sender, System.Windows.Input.GestureEventArgs e)
{
e.Handled = true;

borderTemp = sender as Border;

// 注册 tile 右上角“图钉”图片的单击事件
(borderTemp.Child as TileControl).Assets_thumb_png.Tap += Assets_thumb_png_Tap;

transformTemp = new CompositeTransform();

transformTemp.TranslateX = x;
transformTemp.TranslateY = y;

borderTemp.RenderTransform = transformTemp;

// 拖动 border 时,改变它的坐标
borderTemp.ManipulationDelta += borderTemp_ManipulationDelta;

// 当手指离开 border 时,判断各个 tile 中心点的距离
borderTemp.MouseLeftButtonUp += borderTemp_MouseLeftButtonUp;

if (borderTemp.Child != null)
{
foreach (var item in ContentPanel.Children)
{
Border border = item as Border;
if (border.Child != null)
{
TileControl tile = border.Child as TileControl;
// 被选中的 tile 进入 CheckedVisualState 视图状态,其余的进入 FloatVisualState 视图状态
if (tile == (borderTemp.Child as TileControl))
{
tile.Checked();
border.IsHitTestVisible = true;
}
else
{
tile.Float();
// 当处于漂浮状态时,不再接收“单击”等屏幕事件
border.IsHitTestVisible = false;
}
}
}
}
}


当手指离开选择的 Tile 时,判断各个 Tile 的中心点的距离,从而判断是否具备交换两个 Tile 的条件:

void borderTemp_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;

DateTime timeStart = DateTime.Now;

Debug.WriteLine("Translation.X: " + transformTemp.TranslateX);
Debug.WriteLine("Translation.Y: " + transformTemp.TranslateY);

Point pointSource = (Point)borderTemp.Tag;

Point point = new Point(transformTemp.TranslateX + pointSource.X, transformTemp.TranslateY + pointSource.Y);

bool IsSwaped = false;

// 当手指离开屏幕时,判断选中的 tile 的中心点和其它中心点的直线距离,
// 如果距离小于 40px,则两个 tile 互换 //换
foreach (Point position in dic.Keys)
{
Border border2 = dic[position];
if (borderTemp != border2)
{
x = (int)(point.X - position.X);
y = (int)(point.Y - position.Y);

// 计算两个 Tile 中心点的直线距离
int distance = (int)Math.Sqrt(x * x + y * y);

if (distance < 40)
{
// 交换两个 tile 的位置
SwapBorder(borderTemp, border2);
IsSwaped = true;
break;
}
}
}

Debug.WriteLine(DateTime.Now - timeStart);

if (!IsSwaped)
{
transformTemp.TranslateX = 0;
transformTemp.TranslateY = 0;
}
}


重要代码粘贴到上面了,具体代码可以下载工程。至此有关 FluidMoveBehavior 行为的三篇文章暂时写完了。

这个demo 运行的交互很简单,但是算法上还是颇费周折,想了几种实现方式,最终采用了上面的方式,重点时间

都花费在了数学计算上了。不过 demo 的代码还是比较粗糙,性能也没有优化,只是实现了大概的交互。

工程源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: