ScreenUnLock 与智能手机上的图案解锁功能一样。通过绘制图形达到解锁或记忆图形的目的。

本人突发奇想,把手机上的图形解锁功能移植到WPF中。也应用到了公司的项目中。

在创建ScreenUnLock之前,先来分析一下图形解锁的实现思路。

1.创建九宫格原点(或更多格子),每个点定义一个坐标值

2.提供图形解锁相关扩展属性和事件,方便调用者定义。比如:点和线的颜色(Color),操作模式(Check|Remember),验证正确的颜色(RightColor), 验证失败的颜色(ErrorColor), 解锁事件 OnCheckedPoint,记忆事件 OnRememberPoint 等;

3.定义MouseMove事件监听画线行为。 画线部分也是本文的核心。在画线过程中。程序需判断,线条从哪个点开始绘制,经过了哪个点(排除已经记录的点)。是否完成了绘制等等。

4.画线完成,根据操作模式处理画线完成行为。并调用相关自定义事件

大致思路如上,下面开始一步一步编写ScreenUnLock吧

创建ScreenUnLock

public partial class ScreenUnlock : UserControl

定义相关属性

 1  /// <summary>
2 /// 验证正确的颜色
3 /// </summary>
4 private SolidColorBrush rightColor;
5
6 /// <summary>
7 /// 验证失败的颜色
8 /// </summary>
9 private SolidColorBrush errorColor;
10
11 /// <summary>
12 /// 图案是否在检查中
13 /// </summary>
14 private bool isChecking;
15
16 public static readonly DependencyProperty PointArrayProperty = DependencyProperty.Register("PointArray", typeof(IList<string>), typeof(ScreenUnlock));
17 /// <summary>
18 /// 记忆的坐标点
19 /// </summary>
20 public IList<string> PointArray
21 {
22 get { return GetValue(PointArrayProperty) as IList<string>; }
23 set { SetValue(PointArrayProperty, value); }
24 }
25
26 /// <summary>
27 /// 当前坐标点集合
28 /// </summary>
29 private IList<string> currentPointArray;
30
31 /// <summary>
32 /// 当前线集合
33 /// </summary>
34 private IList<Line> currentLineList;
35
36 /// <summary>
37 /// 点集合
38 /// </summary>
39 private IList<Ellipse> ellipseList;
40
41 /// <summary>
42 /// 当前正在绘制的线
43 /// </summary>
44 private Line currentLine;
45
46 public static readonly DependencyProperty OperationPorperty = DependencyProperty.Register("Operation", typeof(ScreenUnLockOperationType), typeof(ScreenUnlock), new FrameworkPropertyMetadata(ScreenUnLockOperationType.Remember));
47 /// <summary>
48 /// 操作类型
49 /// </summary>
50 public ScreenUnLockOperationType Operation
51 {
52 get { return (ScreenUnLockOperationType)GetValue(OperationPorperty); }
53 set { SetValue(OperationPorperty, value); }
54 }
55
56 public static readonly DependencyProperty PointSizeProperty = DependencyProperty.Register("PointSize", typeof(double), typeof(ScreenUnlock), new FrameworkPropertyMetadata(15.0));
57 /// <summary>
58 /// 坐标点大小
59 /// </summary>
60 public double PointSize
61 {
62 get { return Convert.ToDouble(GetValue(PointSizeProperty)); }
63 set { SetValue(PointSizeProperty, value); }
64 }
65
66
67 public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(SolidColorBrush), typeof(ScreenUnlock), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), new PropertyChangedCallback((s, e) =>
68 {
69 (s as ScreenUnlock).Refresh();
70 })));
71
72 /// <summary>
73 /// 坐标点及线条颜色
74 /// </summary>
75 public SolidColorBrush Color
76 {
77 get { return GetValue(ColorProperty) as SolidColorBrush; }
78 set { SetValue(ColorProperty, value); }
79 }         /// <summary>
        /// 操作类型
        /// </summary>
        public enum ScreenUnLockOperationType
        {
            Remember = 0, Check = 1
        }

初始化ScreenUnLock

 public ScreenUnlock()
{
InitializeComponent();
this.Loaded += ScreenUnlock_Loaded;
this.Unloaded += ScreenUnlock_Unloaded;
this.MouseMove += ScreenUnlock_MouseMove; //监听绘制事件
}
private void ScreenUnlock_Loaded(object sender, RoutedEventArgs e)
{
isChecking = false;
rightColor = new SolidColorBrush(Colors.Green);
errorColor = new SolidColorBrush(Colors.Red);
currentPointArray = new List<string>();
currentLineList = new List<Line>();
ellipseList = new List<Ellipse>();
CreatePoint();
} private void ScreenUnlock_Unloaded(object sender, RoutedEventArgs e)
{
rightColor = null;
errorColor = null;
if (currentPointArray != null)
this.currentPointArray.Clear();
if (currentLineList != null)
this.currentLineList.Clear();
if (ellipseList != null)
ellipseList.Clear();
this.canvasRoot.Children.Clear();
}

创建点

 1 /// <summary>
2 /// 创建点
3 /// </summary>
4 private void CreatePoint()
5 {
6 canvasRoot.Children.Clear();
7 int row = 3, column = 3; //三行三列,九宫格
8 double oneColumnWidth = (this.ActualWidth == 0 ? this.Width : this.ActualWidth) / 3; //单列的宽度
9 double oneRowHeight = (this.ActualHeight == 0 ? this.Height : this.ActualHeight) / 3; //单列的高度
10 double leftDistance = (oneColumnWidth - PointSize) / 2; //单列左边距
11 double topDistance = (oneRowHeight - PointSize) / 2; //单列上边距
12 for (var i = 0; i < row; i++)
13 {
14 for (var j = 0; j < column; j++)
15 {
16 Ellipse ellipse = new Ellipse()
17 {
18 Width = PointSize,
19 Height = PointSize,
20 Fill = Color,
21 Tag = string.Format("{0}{1}", i, j)
22 };
23 Canvas.SetLeft(ellipse, j * oneColumnWidth + leftDistance);
24 Canvas.SetTop(ellipse, i * oneRowHeight + topDistance);
25 canvasRoot.Children.Add(ellipse);
26 ellipseList.Add(ellipse);
27 }
28 }
29 }

创建线

1   private Line CreateLine()
2 {
3 Line line = new Line()
4 {
5 Stroke = Color,
6 StrokeThickness = 2
7 };
8 return line;
9 }

点和线都创建都定义好了,可以开始监听绘制事件了

 1  private void ScreenUnlock_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
2 {
3 if (isChecking) //如果图形正在检查中,不响应后续处理
4 return;
5 if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
6 {
7 var point = e.GetPosition(this);
8 HitTestResult result = VisualTreeHelper.HitTest(this, point);
9 Ellipse ellipse = result.VisualHit as Ellipse;
10 if (ellipse != null)
11 {
12 if (currentLine == null)
13 {
14 //从头开始绘制
15 currentLine = CreateLine();
16 var ellipseCenterPoint = GetCenterPoint(ellipse);
17 currentLine.X1 = currentLine.X2 = ellipseCenterPoint.X;
18 currentLine.Y1 = currentLine.Y2 = ellipseCenterPoint.Y;
19
20 currentPointArray.Add(ellipse.Tag.ToString());
21 Console.WriteLine(string.Join(",", currentPointArray));
22 currentLineList.Add(currentLine);
23 canvasRoot.Children.Add(currentLine);
24 }
25 else
26 {
27 //遇到下一个点,排除已经经过的点
28 if (currentPointArray.Contains(ellipse.Tag.ToString()))
29 return;
30 OnAfterByPoint(ellipse);
31 }
32 }
33 else if (currentLine != null)
34 {
35 //绘制过程中
36 currentLine.X2 = point.X;
37 currentLine.Y2 = point.Y;
38
39 //判断当前Line是否经过点
40 ellipse = IsOnLine();
41 if (ellipse != null)
42 OnAfterByPoint(ellipse);
43 }
44 }
45 else
46 {
47 if (currentPointArray.Count == 0)
48 return;
49 isChecking = true;
50 if (currentLineList.Count + 1 != currentPointArray.Count)
51 {
52 //最后一条线的终点不在点上
53 //两点一线,点的个数-1等于线的条数
54 currentLineList.Remove(currentLine); //从已记录的线集合中删除最后一条多余的线
55 canvasRoot.Children.Remove(currentLine); //从界面上删除最后一条多余的线
56 currentLine = null;
57 }
58
59 if (Operation == ScreenUnLockOperationType.Check)
60 {
61 Console.WriteLine("playAnimation Check");
62 var result = CheckPoint(); //执行图形检查
              //执行完成动画并触发检查事件
63 PlayAnimation(result, () =>
64 {
65 if (OnCheckedPoint != null)
66 {
67 this.Dispatcher.BeginInvoke(OnCheckedPoint, this, new CheckPointArgs() { Result = result }); //触发检查完成事件
68 }
69 });
70
71 }
72 else if (Operation == ScreenUnLockOperationType.Remember)
73 {
74 Console.WriteLine("playAnimation Remember");
75 RememberPoint(); //记忆绘制的坐标
76 var args = new RememberPointArgs() { PointArray = this.PointArray };
             //执行完成动画并触发记忆事件
77 PlayAnimation(true, () =>
78 {
79 if (OnRememberPoint != null)
80 {
81 this.Dispatcher.BeginInvoke(OnRememberPoint, this, args); //触发图形记忆事件
82 }
83 });
84 }
85 }
86 }

判断线是否经过了附近的某个点

 1  /// <summary>
2 /// 两点计算一线的长度
3 /// </summary>
4 /// <param name="pt1"></param>
5 /// <param name="pt2"></param>
6 /// <returns></returns>
7 private double GetLineLength(double x1, double y1, double x2, double y2)
8 {
9 return Math.Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); //根据两点计算线段长度公式 √((x1-x2)²x(y1-y2)²)
10 }
11
12 /// <summary>
13 /// 判断线是否经过了某个点
14 /// </summary>
16 /// <param name="ellipse"></param>
17 /// <returns></returns>
18 private Ellipse IsOnLine()
19 {
20 double lineAB = 0; //当前画线的长度
21 double lineCA = 0; //当前点和A点的距离
22 double lineCB = 0; //当前点和B点的距离
23 double dis = 0;
24 double deciation = 1; //允许的偏差距离
25 lineAB = GetLineLength(currentLine.X1, currentLine.Y1, currentLine.X2, currentLine.Y2); //计算当前画线的长度
26
27 foreach (Ellipse ellipse in ellipseList)
28 {
29 if (currentPointArray.Contains(ellipse.Tag.ToString())) //排除已经经过的点
30 continue;
31 var ellipseCenterPoint = GetCenterPoint(ellipse); //取当前点的中心点
32 lineCA = GetLineLength(currentLine.X1, currentLine.Y1, ellipseCenterPoint.X, ellipseCenterPoint.Y); //计算当前点到线A端的长度
33 lineCB = GetLineLength(currentLine.X2, currentLine.Y2, ellipseCenterPoint.X, ellipseCenterPoint.Y); //计算当前点到线B端的长度
34 dis = Math.Abs(lineAB - (lineCA + lineCB)); //线CA的长度+线CB的长度>当前线AB的长度 说明点不在线上
35 if (dis <= deciation) //因为绘制的点具有一个宽度和高度,所以需设定一个允许的偏差范围,让线靠近点就命中之(吸附效果)
36 {
37 return ellipse;
38 }
39 }
40 return null;
41 }

检查点是否正确,按数组顺序逐个匹配之

 1       /// <summary>
2 /// 检查坐标点是否正确
3 /// </summary>
4 /// <returns></returns>
5 private bool CheckPoint()
6 {
         //PointArray:正确的坐标值数组
        //currentPointArray:当前绘制的坐标值数组
7 if (currentPointArray.Count != PointArray.Count)
8 return false;
9 for (var i = 0; i < currentPointArray.Count; i++)
10 {
11 if (currentPointArray[i] != PointArray[i])
12 return false;
13 }
14 return true;
15 }

记录经过点,并创建一条新的线

 1         /// <summary>
2 /// 记录经过的点
3 /// </summary>
4 /// <param name="ellipse"></param>
5 private void OnAfterByPoint(Ellipse ellipse)
6 {
7 var ellipseCenterPoint = GetCenterPoint(ellipse);
8 currentLine.X2 = ellipseCenterPoint.X;
9 currentLine.Y2 = ellipseCenterPoint.Y;
10 currentLine = CreateLine();
11 currentLine.X1 = currentLine.X2 = ellipseCenterPoint.X;
12 currentLine.Y1 = currentLine.Y2 = ellipseCenterPoint.Y;
13 currentPointArray.Add(ellipse.Tag.ToString());
14 Console.WriteLine(string.Join(",", currentPointArray));
15 currentLineList.Add(currentLine);
16 canvasRoot.Children.Add(currentLine);
17 }
 1         /// <summary>
2 /// 获取原点的中心点坐标
3 /// </summary>
4 /// <param name="ellipse"></param>
5 /// <returns></returns>
6 private Point GetCenterPoint(Ellipse ellipse)
7 {
8 Point p = new Point(Canvas.GetLeft(ellipse) + ellipse.Width / 2, Canvas.GetTop(ellipse) + ellipse.Height / 2);
9 return p;
10 }
11

当绘制完成时,执行完成动画并触发响应模式的事件

 1  /// <summary>
2 /// 执行动画
3 /// </summary>
4 /// <param name="result"></param>
5 private void PlayAnimation(bool result, Action callback = null)
6 {
7 Task.Factory.StartNew(() =>
8 {
9 this.Dispatcher.Invoke((Action)delegate
10 {
11 foreach (Line l in currentLineList)
12 l.Stroke = result ? rightColor : errorColor;
13 foreach (Ellipse e in ellipseList)
14 if (currentPointArray.Contains(e.Tag.ToString()))
15 e.Fill = result ? rightColor : errorColor;
16 });
17 Thread.Sleep(1500);
18 this.Dispatcher.Invoke((Action)delegate
19 {
20 foreach (Line l in currentLineList)
21 this.canvasRoot.Children.Remove(l);
22 foreach (Ellipse e in ellipseList)
23 e.Fill = Color;
24 });
25 currentLine = null;
26 this.currentPointArray.Clear();
27 this.currentLineList.Clear();
28 isChecking = false;
29 }).ContinueWith(t =>
30 {
31 try
32 {
33 if (callback != null)
34 callback();
35 }
36 catch (Exception ex)
37 {
38 Console.WriteLine(ex.Message);
39 }
40 finally
41 {
42 t.Dispose();
43 }
44 });
45 }

图形解锁的调用

 1   <local:ScreenUnlock Width="500" Height="500"
2 PointArray="{Binding PointArray, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
3 Operation="Check"> <!--或Remember-->
4 <i:Interaction.Triggers>
5 <i:EventTrigger EventName="OnCheckedPoint">
6 <Custom:EventToCommand Command="{Binding OnCheckedPoint}" PassEventArgsToCommand="True"/>
7 </i:EventTrigger>
8 <i:EventTrigger EventName="OnRememberPoint">
9 <Custom:EventToCommand Command="{Binding OnRememberPoint}" PassEventArgsToCommand="True"/>
10 </i:EventTrigger>
11 </i:Interaction.Triggers>
12 </local:ScreenUnlock>

WPF自定义控件之图形解锁控件 ScreenUnLock的更多相关文章

  1. 【Android - 自定义View】之自定义九宫格手势解锁控件

    首先来介绍一下这个自定义View: (1)这个自定义View的名称叫做 LockView ,继承自View类: (2)这个自定义View实现了应用中常见的九宫格手势解锁功能,可以用于保证应用安全: ( ...

  2. WPF自定义控件(一)の控件分类

    一.什么是控件(Controls) 控件是指对数据和方法的封装.控件可以有自己的属性和方法,其中属性是控件数据的简单访问者,方法则是控件的一些简单而可见的功能.控件创建过程包括设计.开发.调试(就是所 ...

  3. WPF自定义控件第二 - 转盘按钮控件

    继之前那个控件,又做了一个原理差不多的控件.这个控件主要模仿百度贴吧WP版帖子浏览界面左下角那个弹出的按钮盘.希望对大家有帮助. 这个控件和之前的也差不多,为了不让大家白看,文章最后发干货. 由于这个 ...

  4. WPF自定义控件第一 - 进度条控件

    本文主要针对WPF新手,高手可以直接忽略,更希望高手们能给出一些更好的实现思路. 前期一个小任务需要实现一个类似含步骤进度条的控件.虽然对于XAML的了解还不是足够深入,还是摸索着做了一个.这篇文章介 ...

  5. WPF自定义控件一:StackPanel 控件轮播

    实现效果 带定时器的轮播图 using引用 using System.Windows; using System.Windows.Controls; using System.Windows.Mark ...

  6. Android自定义多宫格解锁控件

    在此之前,一直在想九宫格的实现方法,经过一个上午的初步研究终于完成了一个简单的N*N的宫格解锁组件,代码略显粗糙,仅仅做到简单的实现,界面等后期在做优化,纯粹是学习的目的,在算法上有点缺陷,如果有错误 ...

  7. WPF自定义控件(五)の用户控件(完结)

    用户控件,WPF中是继承自UserControl的控件,我们可以在里面融合我们的业务逻辑. 示例:(一个厌恶选择的用户控件) 后端: using iMicClassBase; using iMicCl ...

  8. WPF自定义控件(三)の扩展控件

    扩展控件,顾名思义就是对已有的控件进行扩展,一般继承于已有的原生控件,不排除继承于自定义的控件,不过这样做意义不大,因为既然都自定义了,为什么不一步到位呢,有些不同的需求也可以通过此来完成,不过类似于 ...

  9. WPF自定义控件(二)の重写原生控件样式模板

    话外篇: 要写一个圆形控件,用Clip,重写模板,去除样式引用圆形图片可以有这三种方式. 开发过程中,我们有时候用WPF原生的控件就能实现自己的需求,但是样式.风格并不能满足我们的需求,那么我们该怎么 ...

随机推荐

  1. 解决:make:cc 命令未找到的解决方法

    安装Redis的时候报这个错误 原因:未安装gcc 解决方法:安装gcc 自动安装,包括依赖库[root@VM_220_111_centos redis-3.2.9]# yum -y install ...

  2. UML中的组合、聚合、关联、继承、实现、依赖

    转自:http://justsee.iteye.com/blog/808799 UML定义的关系主要有六种:依赖.类属.关联.实现.聚合和组合. 继承 指的是一个类(称为子类.子接口)继承另外的一个类 ...

  3. wireshark初学者使用

    介绍 Wireshark是一款网络封包分析软件,截取网络封包,显示其封包的详细信息.日常工作中用的比较多.在使用wireshark之前须了解常用的网络协议.如:tcp,http,ip,udp等.(其实 ...

  4. Isolation Forest原理总结

    Isolation Forest(以下简称iForest)算法是由南京大学的周志华和澳大利亚莫纳什大学的Fei Tony Liu, Kai Ming Ting等人共同提出,用于挖掘异常数据[Isola ...

  5. 解决在for循环内判断条件多次执行

    最近遇到的这个问题,就是在for循环内if判断的条件会多次执行. 例如,在返回的30数据中,a条目是第7条则会进行30次判断,弹出29次查无数据,也就是要点击29次关闭alert,很是让人不爽. 有了 ...

  6. [感悟]马士兵Java自学之路——(精华版)

    JAVA自学之路 一: 学会选择 为了就业,不少同学参加各种各样的培训. 决心做软件的,大多数人选的是java,或是.net,也有一些选择了手机.嵌入式.游戏.3G.测试等. 那么究竟应该选择什么方向 ...

  7. Vue项目上线后刷新报错404问题(apache,nginx,tomcat)

    转自:https://www.cnblogs.com/sxshaolong/p/10219527.html 很简单,需要 服务器端 加个配置文件,然后 重启服务就好了,记住一定要 重启服务,否则无效!

  8. Windows消息队列(优先队列,结构体中放比较函数)

    Windows消息队列 消息队列是Windows系统的基础.对于每个进程,系统维护一个消息队列.如果在进程中有特定事件发生,如点击鼠标.文字改变等,系统将把这个消息加到队列当中.同时,如果队列不是空的 ...

  9. New Concept English three (23)

    31w 45 People become quite illogical when they try to decide what can be eaten and what cannot be ea ...

  10. MAC 下安装 SVN

    在mac下安装svn走了很多弯路,进过探索,现在对svn的安装做了总结,可以分为2种: 1.由于 xCode自带svn,所以可以安装xCode 1).打开App Store,搜索xCode,下载安装 ...