对于一些需求,虽然无奈和蛋疼但还是要实现的
Forms
- 在Xamarin.Form下新建一个视图类
using System;
using Xamarin.Forms;
namespace EZPlatform.Renderer.Forms
{
public enum ClockState
{
VerifySuccess,
VerifyFailure,
SettingCipher,
ForgetCipher,
}
public class LockView : View
{
///
/// 若是验证则传入密码,否则为设置密码
///
/// The old cipher.
public string Cipher { set; get; }
///
/// 若为传入值则取屏幕高度
///
/// The height of the screen.
public double ScreenHeight { set; get; }
public delegate void DrawCompletedDelegate(ClockState state);
///
/// The draw completed.
///
public DrawCompletedDelegate DrawCompleted;
}
}
So beautiful,已经完成至关重要的第一步:开始
iOS 实现
简述下原理:在iOS下其实就是Pan拖动手势,手势有不同的状态:按下 - 滑动 - 抬起,每次手指的移动都会重绘当前页面,判断是否手指位置是否和预定的9宫格点重复,若有则更换图片:更换图片采用的是UIButton控件,设置其两张图,修改selected属性进行切换
- 实现1:自定义渲染器,注意命名空间就行
using System.ComponentModel;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(EZPlatform.Renderer.Forms.EllipseView), typeof(EZPlatform.iOS.Renderer.EllipseViewRenderer))]
namespace EZPlatform.iOS.Renderer
{
public class EllipseViewRenderer : ViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (Control == null)
{
SetNativeControl(new EllipseUIView());
}
if (e.NewElement != null)
{
SetColor();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == EZPlatform.Renderer.Forms.EllipseView.ColorProperty.PropertyName)
{
SetColor();
}
}
void SetColor()
{
if (Element.Color != Color.Default)
{
Control.SetColor(Element.Color.ToUIColor());
} else
{
Control.SetColor(UIColor.Clear);
}
}
}
}
- 实现2:手势所的具体实现
using System;
using UIKit;
using Foundation;
using ObjCRuntime;
using CoreGraphics;
using CoreAnimation;
using System.Collections.Generic;
using EZPlatform.Renderer.Forms;
namespace EZPlatform.iOS.Renderer
{
/*
Require
1.获取出发点和停止点
2.判断交点,绘制线条
3.绘线
Feature
1.UIButton有高亮图片,设置两种图片状态,之后设置其seleted的BOOL状态即可切换:Button不可交互
2.绘制线需要绘制交点;在Pan手势为结束时还需要添加绘制到点击处:Pan手势有状态
*/
public class LockView : UIView
{
public float Height { set; get; }
public string OldCipher { set; get; }
private int errorCount = 5;
private bool isVerify { set; get; }
///
/// The draw completed.
///
public EZPlatform.Renderer.Forms.LockView.DrawCompletedDelegate DrawCompleted;
private List arrButton { get; set; }
private CGPoint currentPoint;
private bool isEnd = false;
private bool isError = false;
private UILabel tipsLabel = null;
private UIView viewLock = null;
private UIButton operationButton = null;
public LockView()
{
BackgroundColor = UIColor.White;
arrButton = new List();
AddGestureRecognizer(new UIPanGestureRecognizer((UIPanGestureRecognizer panGR) => panAction(panGR)));
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
isVerify = (OldCipher == null);
layout((int)UIScreen.MainScreen.Bounds.Width);
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
// 容错循环外的 AddLineTo()
if (arrButton.Count == 0) return;
UIBezierPath path = new UIBezierPath();
for (int i = 0; i < arrButton.Count; i++)
{
UIButton btn = arrButton[i];
if (i == 0)
{
//设置起点
path.MoveTo(btn.Center);
} else
{
path.AddLineTo(btn.Center);
}
}
// 去掉Pan手势结束后未连的线
if (isEnd == false)
{
path.AddLineTo(currentPoint);
}
path.LineJoinStyle = CGLineJoin.Round;
// 错误提示
if (isError)
{
UIColor.Red.SetColor();
} else
{
UIColor.Orange.SetColor();
}
path.LineWidth = 8;
path.Stroke();
}
protected internal void layout(int WID)
{
float WH = WID >= 375 ? 58 : 50;
float MR = (WID - 3 * WH) / 4;
tipsLabel = new UILabel
{
Font = UIFont.FromName("Heiti SC", 16),
Text = "请绘制手势密码",
TextColor = UIColor.Orange,
TextAlignment = UITextAlignment.Center,
Frame = new CGRect(0, MR, WID, WID / 10),
};
Add(tipsLabel);
viewLock = new UIView(Bounds);
int cols = 3; //总列数
float x = 0, y = 0; //bounds
float margin = (WID - cols * WH) / (cols + 1);//间距
float col = 0;
float row = 0;
for (int i = 0; i < 9; i++)
{
col = i % cols;
row = i / cols;
x = margin + (WH + margin) * col;
y = margin + (WH + margin) * row;
var buton = new UIButton
{
Tag = i,
UserInteractionEnabled = false,
Frame = new CGRect(x, y + tipsLabel.Bounds.Bottom + MR, WH, WH),
};
buton.SetImage(new UIImage("gesture_normal"), UIControlState.Normal);
buton.SetImage(new UIImage("gesture_selected"), UIControlState.Selected);
viewLock.Add(buton);
}
Add(viewLock);
/* #Example 1
int tag = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
var buton = new UIButton
{
Tag = tag,
UserInteractionEnabled = false,
Frame = new CGRect(MR * (i + 1) + WH * i, MR * (j + 1) + WH * j + tipsLabel.Bounds.Bottom + MR, WH, WH),
};
buton.SetImage(new UIImage("gesture_normal"), UIControlState.Normal);
buton.SetImage(new UIImage("gesture_selected"), UIControlState.Selected);
viewLock.Add(buton);
tag++;
}
}
Add(viewLock);
*/
float HEI = Height > 0 ? Height : (float)UIScreen.MainScreen.Bounds.Height - 64;
operationButton = new UIButton
{
Hidden = isVerify,
Font = UIFont.FromName("Heiti SC", 14),
Frame = new CGRect(0, HEI - WID / 10, WID, WID / 10),
};
operationButton.SetTitle(isVerify ? "重新绘制" : "忘记手势密码?", UIControlState.Normal);
operationButton.SetTitleColor(UIColor.Blue, UIControlState.Normal);
operationButton.SetTitleColor(UIColor.Orange, UIControlState.Highlighted);
operationButton.TouchUpInside += (sender, e) => operationEvent();
Add(operationButton);
}
private void operationEvent()
{
if (isVerify)
{
OldCipher = null;
operationButton.Hidden = true;
tipsLabel.Text = "请绘制手势密码";
}
else
{
DrawCompleted?.Invoke(ClockState.ForgetCipher);
}
}
private void panAction(UIPanGestureRecognizer panGR)
{
currentPoint = panGR.LocationInView(this);
if (panGR.State == UIGestureRecognizerState.Began)
{
isEnd = false;
isError = false;
}
foreach (UIButton btn in viewLock.Subviews)
{
if (btn.Frame.Contains(currentPoint) && btn.Selected == false)
{
btn.Selected = true;
arrButton.Add(btn);
}
}
if (panGR.State == UIGestureRecognizerState.Ended)
{
// 没触发点。不做任何操作
if (arrButton.Count == 0) return;
isEnd = true;
UserInteractionEnabled = false;
string keys = "";
foreach (UIButton btn in arrButton)
{
keys += btn.Tag;
}
verifyCipher(keys);
Xamarin.Forms.Device.StartTimer(TimeSpan.FromSeconds(0.75), () =>
{
foreach (UIButton btn in viewLock.Subviews)
{
btn.Selected = false;
arrButton.Remove(btn);
}
//arrButton.RemoveRange(0, arrButton.Count);
SetNeedsDisplay();
UserInteractionEnabled = true;
return false;
});
}
SetNeedsDisplay();
}
private void verifyCipher(string keys)
{
bool isShow = false;
if (keys.Length < 4 && isVerify && OldCipher == null)
{
isShow = true;
tipsLabel.Text = "至少连接4个点,请重新绘制";
} else if (OldCipher != null && !OldCipher.Equals(keys) || keys.Length < 4)
{
isShow = true;
if (isVerify)
{
tipsLabel.Text = "与上一次绘制不一样,请重新绘制";
} else
{
tipsLabel.Text = $"密码错误,还可以输入{--errorCount}次";
}
}
if (isShow)
{
if (errorCount == 0)
{
if (DrawCompleted != null)
{
DrawCompleted(ClockState.VerifyFailure);
}
} else
{
isError = true;
tipsLabel.TextColor = UIColor.Red;
shakeAnimation(tipsLabel);
}
} else
{
if (OldCipher != null)
{
if (OldCipher.Equals(keys))
{
tipsLabel.Text = "设置成功";
UserInteractionEnabled = false;
operationButton.Hidden = true;
if (DrawCompleted != null)
{
DrawCompleted(ClockState.SettingCipher);
}
} else
{
tipsLabel.Text = "请绘制手势密码";
}
} else
{
if (isVerify)
{
OldCipher = keys;
operationButton.Hidden = false;
tipsLabel.Text = "请再次绘制手势密码";
} else
{
tipsLabel.Text = "验证成功";
UserInteractionEnabled = false;
viewLock.UserInteractionEnabled = false;
if (DrawCompleted != null)
{
DrawCompleted(ClockState.VerifySuccess);
}
}
}
tipsLabel.TextColor = UIColor.Orange;
}
}
private void shakeAnimation(UIView view)
{
CALayer layer = view.Layer;
CGPoint position = layer.Position;
CGPoint left = new CGPoint(position.X - 10, position.Y);
CGPoint right = new CGPoint(position.X + 10, position.Y);
CABasicAnimation animation = CABasicAnimation.FromKeyPath("position");
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut);
animation.SetFrom(NSValue.FromCGPoint(left));
animation.SetTo(NSValue.FromCGPoint(right));
animation.AutoReverses = true;
animation.Duration = 0.08;
animation.RepeatCount = 3;
layer.AddAnimation(animation, null);
}
}
}
Android 实现
搞了半年的Xamarin,庆幸自己还看得懂点Android代码,Android实现原理和iOS基本一样,也是判断手势的状态,重绘页面,判断焦点
- 实现1:单个显示点的视图
using Android.Content;
using Android.Graphics;
using Style = Android.Graphics.Paint.Style;
namespace EZPlatform.Droid.Renderer
{
// Xamarin.Android 控件初始化都需要添加Context参数,且不在方法内部调用base,而是以继承的格式 base(context)
public class GestureLockView : Android.Views.View
{
private static string TAG = "GestureLockView";
/**
* GestureLockView的三种状态
*/
public enum LockViewMode
{
STATUS_NO_FINGER,
STATUS_FINGER_ON,
STATUS_FINGER_UP,
}
/**
* GestureLockView的当前状态
*/
private LockViewMode mCurrentStatus = LockViewMode.STATUS_NO_FINGER;
/**
* 宽度
*/
private int mWidth;
/**
* 高度
*/
private int mHeight;
/**
* 外圆半径
*/
private int mRadius;
/**
* 画笔的宽度
*/
private int mStrokeWidth = 2;
/**
* 圆心坐标
*/
private int mCenterX;
private int mCenterY;
private Paint mPaint;
/**
* 箭头(小三角最长边的一半长度 = mArrawRate * mWidth / 2 )
*/
private float mArrowRate = 0.333f;
private int mArrowDegree = -1;
private Path mArrowPath;
/**
* 内圆的半径 = mInnerCircleRadiusRate * mRadus
*
*/
private float mInnerCircleRadiusRate = 0.3F;
/**
* 四个颜色,可由用户自定义,初始化时由GestureLockViewGroup传入
*/
private string mColorNoFingerInner;
private string mColorNoFingerOutter;
private string mColorFingerOn;
private string mColorFingerUp;
public GestureLockView(Context context, string colorNoFingerInner, string colorNoFingerOutter, string colorFingerOn, string colorFingerUp) : base(context)
{
this.mColorNoFingerInner = colorNoFingerInner;
this.mColorNoFingerOutter = colorNoFingerOutter;
this.mColorFingerOn = colorFingerOn;
this.mColorFingerUp = colorFingerUp;
mPaint = new Paint(PaintFlags.AntiAlias);
mArrowPath = new Path();
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.GetSize(widthMeasureSpec);
mHeight = MeasureSpec.GetSize(heightMeasureSpec);
// 取长和宽中的小值
mWidth = mWidth < mHeight ? mWidth : mHeight;
mRadius = mCenterX = mCenterY = mWidth / 2;
mRadius -= mStrokeWidth / 2;
// 绘制三角形,初始时是个默认箭头朝上的一个等腰三角形,用户绘制结束后,根据由两个GestureLockView决定需要旋转多少度
float mArrowLength = mWidth / 2 * mArrowRate;
mArrowPath.MoveTo(mWidth / 2, mStrokeWidth + 2);
mArrowPath.LineTo(mWidth / 2 - mArrowLength, mStrokeWidth + 2 + mArrowLength);
mArrowPath.LineTo(mWidth / 2 + mArrowLength, mStrokeWidth + 2 + mArrowLength);
mArrowPath.Close();
mArrowPath.SetFillType(Path.FillType.Winding);
}
protected override void OnDraw(Canvas canvas)
{
switch (mCurrentStatus)
{
case LockViewMode.STATUS_FINGER_ON:
// 绘制外圆
mPaint.SetStyle(Style.Stroke);
mPaint.Color = Color.ParseColor(mColorFingerOn);
mPaint.StrokeWidth = mStrokeWidth;
canvas.DrawCircle(mCenterX, mCenterY, mRadius, mPaint);
// 绘制内圆
mPaint.SetStyle(Style.Fill);
canvas.DrawCircle(mCenterX, mCenterY, mRadius * mInnerCircleRadiusRate, mPaint);
break;
case LockViewMode.STATUS_FINGER_UP:
// 绘制外圆
mPaint.Color = Color.ParseColor(mColorFingerUp);
mPaint.SetStyle(Style.Stroke);
mPaint.StrokeWidth = mStrokeWidth;
canvas.DrawCircle(mCenterX, mCenterY, mRadius, mPaint);
// 绘制内圆
mPaint.SetStyle(Style.Fill);
canvas.DrawCircle(mCenterX, mCenterY, mRadius * mInnerCircleRadiusRate, mPaint);
drawArrow(canvas);
break;
case LockViewMode.STATUS_NO_FINGER:
// 绘制外圆
mPaint.SetStyle(Style.Fill);
mPaint.Color = Color.ParseColor(mColorNoFingerOutter);
canvas.DrawCircle(mCenterX, mCenterY, mRadius, mPaint);
// 绘制内圆
mPaint.Color = Color.ParseColor(mColorNoFingerInner);
canvas.DrawCircle(mCenterX, mCenterY, mRadius * mInnerCircleRadiusRate, mPaint);
break;
}
}
/**
* 绘制箭头
* @param canvas
*/
private void drawArrow(Canvas canvas)
{
if (mArrowDegree != -1)
{
mPaint.SetStyle(Paint.Style.Fill);
canvas.Save();
canvas.Rotate(mArrowDegree, mCenterX, mCenterY);
canvas.DrawPath(mArrowPath, mPaint);
canvas.Restore();
}
}
/**
* 设置当前模式并重绘界面
*
* @param mode
*/
// 必须将Enum置为public,否则编译报错,不一致的可发微信:Error CS0051: Inconsistent accessibility: parameter type
public void setMode(LockViewMode mode)
{
this.mCurrentStatus = mode;
Invalidate();
}
public void setArrowDegree(int degree)
{
this.mArrowDegree = degree;
}
public int getArrowDegree()
{
return this.mArrowDegree;
}
}
}
- 实现2:布局和绘制以及焦点处理
using Java.Util;
using Android.Content;
using Android.Graphics;
using Android.Util;
using Android.Views;
using Android.Widget;
using Android.Content.Res;
using Android.Content.PM;
using System.Collections.Generic;
using Style = Android.Graphics.Paint.Style;
/**
* 整体包含n*n个GestureLockView,每个GestureLockView间间隔mMarginBetweenLockView,
* 最外层的GestureLockView与容器存在mMarginBetweenLockView的外边距
*
* 关于GestureLockView的边长(n*n): n * mGestureLockViewWidth + ( n + 1 ) *
* mMarginBetweenLockView = mWidth ; 得:mGestureLockViewWidth = 4 * mWidth / ( 5
* * mCount + 1 ) 注
*/
namespace EZPlatform.Droid.Renderer
{
public class GestureLockViewGroup : RelativeLayout
{
private static string TAG = "GestureLockViewGroup";
/**
* 保存所有的GestureLockView
*/
private GestureLockView[] mGestureLockViews;
/**
* 每个边上的GestureLockView的个数
*/
private int mCount = 3;
/**
* 存储答案
*/
private int[] mAnswer = { 1, 2, 3, 6, 9 };
/**
* 保存用户选中的GestureLockView的id
*/
private List mChoose = new List();
private Paint mPaint;
/**
* 每个GestureLockView中间的间距 设置为:mGestureLockViewWidth * 25%
*/
private int mMarginBetweenLockView = 30;
/**
* GestureLockView的边长 4 * mWidth / ( 5 * mCount + 1 )
*/
private int mGestureLockViewWidth;
/**
* GestureLockView无手指触摸的状态下内圆的颜色
*/
private string mNoFingerInnerCircleColor = "#FF939090";
/**
* GestureLockView无手指触摸的状态下外圆的颜色
*/
private string mNoFingerOuterCircleColor = "#FFE0DBDB";
/**
* GestureLockView手指触摸的状态下内圆和外圆的颜色
*/
private string mFingerOnColor = "#FF378FC9";
/**
* GestureLockView手指抬起的状态下内圆和外圆的颜色
*/
private string mFingerUpColor = "#FFFF0000";
/**
* 宽度
*/
private int mWidth;
/**
* 高度
*/
private int mHeight;
private Path mPath;
/**
* 指引线的开始位置x
*/
private int mLastPathX;
/**
* 指引线的开始位置y
*/
private int mLastPathY;
/**
* 指引下的结束位置
*/
private Point mTmpTarget = new Point();
/**
* 最大尝试次数
*/
private int mTryTimes = 4;
/**
* 回调接口
*/
private OnGestureLockViewListener mOnGestureLockViewListener;
private Context context;
public GestureLockViewGroup(Context context, IAttributeSet attrs, int defStyle = 0) : base(context)
{
this.context = context;
/**
* 获得所有自定义的参数的值
*/
TypedArray a = context.Theme.ObtainStyledAttributes(attrs, Resource.Styleable.GestureLockViewGroup, defStyle, 0);
int n = a.IndexCount;
for (int i = 0; i < n; i++)
{
int aa = (int)Resource.Styleable.GestureLockViewGroup_color_no_finger_inner_circle;
// 如果第一个参数没有找到对应的资源,则返回defValue设置的值。
// Color.ParseColor(“FFFF0000”),返回值为Color,其本质就是一个int类型:可能Color在Android就是整型吧
int attr = a.GetIndex(i);
switch (attr)
{
case 0: // Resource.Styleable.GestureLockViewGroup_color_no_finger_inner_circle
mNoFingerInnerCircleColor = a.GetColor(attr, Color.ParseColor(mNoFingerInnerCircleColor)).ToString();
break;
case 1:
mNoFingerOuterCircleColor = a.GetColor(attr, Color.ParseColor(mNoFingerOuterCircleColor)).ToString();
break;
case 2:
mFingerOnColor = a.GetColor(attr, Color.ParseColor(mFingerOnColor)).ToString();
break;
case 3:
mFingerUpColor = a.GetColor(attr, Color.ParseColor(mFingerUpColor)).ToString();
break;
case 4:
mCount = a.GetInt(attr, 3);
break;
case 5:
mTryTimes = a.GetInt(attr, 5);
break;
default:
break;
}
}
a.Recycle();
// 初始化画笔
mPaint = new Paint(PaintFlags.AntiAlias);
mPaint.SetStyle(Style.Stroke);
// mPaint.setStrokeWidth(20);
mPaint.StrokeCap = Paint.Cap.Round;
mPaint.StrokeJoin = Paint.Join.Round;
// mPaint.setColor(Color.parseColor("#aaffffff"));
mPath = new Path();
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.GetSize(widthMeasureSpec);
mHeight = MeasureSpec.GetSize(heightMeasureSpec);
mHeight = mWidth = mWidth < mHeight ? mWidth : mHeight;
// 初始化mGestureLockViews
if (mGestureLockViews == null)
{
mGestureLockViews = new GestureLockView[mCount * mCount];
// 计算每个GestureLockView的宽度
mGestureLockViewWidth = (int)(4 * mWidth * 1.0f / (5 * mCount + 1));
//计算每个GestureLockView的间距
mMarginBetweenLockView = (int)(mGestureLockViewWidth * 0.25);
// 设置画笔的宽度为GestureLockView的内圆直径稍微小点(不喜欢的话,随便设)
mPaint.StrokeWidth = mGestureLockViewWidth * 0.29f;
for (int i = 0; i < mGestureLockViews.Length; i++)
{
//初始化每个GestureLockView
mGestureLockViews[i] = new GestureLockView(context,
mNoFingerInnerCircleColor, mNoFingerOuterCircleColor,
mFingerOnColor, mFingerUpColor);
mGestureLockViews[i].Id = (i + 1);
//设置参数,主要是定位GestureLockView间的位置
RelativeLayout.LayoutParams lockerParams = new RelativeLayout.LayoutParams(
mGestureLockViewWidth, mGestureLockViewWidth);
// 不是每行的第一个,则设置位置为前一个的右边
if (i % mCount != 0)
{
lockerParams.AddRule(LayoutRules.RightOf, mGestureLockViews[i - 1].Id);
}
// 从第二行开始,设置为上一行同一位置View的下面
if (i > mCount - 1)
{
lockerParams.AddRule(LayoutRules.Below, mGestureLockViews[i - mCount].Id);
}
//设置右下左上的边距
int rightMargin = mMarginBetweenLockView;
int bottomMargin = mMarginBetweenLockView;
int leftMagin = 0;
int topMargin = 0;
/**
* 每个View都有右外边距和底外边距 第一行的有上外边距 第一列的有左外边距
*/
if (i >= 0 && i < mCount)// 第一行
{
topMargin = mMarginBetweenLockView;
}
if (i % mCount == 0)// 第一列
{
leftMagin = mMarginBetweenLockView;
}
lockerParams.SetMargins(leftMagin, topMargin, rightMargin, bottomMargin);
mGestureLockViews[i].setMode(GestureLockView.LockViewMode.STATUS_NO_FINGER);
AddView(mGestureLockViews[i], lockerParams);
}
}
}
// 重写Ontouch方法和实现其Touch事件是一样的
public override bool OnTouchEvent(MotionEvent e)
{
// MotionEvent对象的ActionIndex属性不是其状态,恆为0;虽然其对象的Action返回值不是int,但C#的Switch是兼容非值类型的,可能该值也是值类型吧
int x = (int) e.GetX();
int y = (int) e.GetY();
switch (e.Action)
{
case MotionEventActions.Down:
// 重置
reset();
break;
case MotionEventActions.Move:
mPaint.Color = Color.ParseColor(mFingerOnColor);
mPaint.Alpha = 50;
GestureLockView child = getChildIdByPos(x, y);
if (child != null)
{
int cId = child.Id;
if (!mChoose.Contains(cId))
{
mChoose.Add(cId);
child.setMode(GestureLockView.LockViewMode.STATUS_FINGER_ON);
if (mOnGestureLockViewListener != null)
mOnGestureLockViewListener.OnBlockSelected(cId);
// 设置指引线的起点
mLastPathX = child.Left / 2 + child.Right / 2;
mLastPathY = child.Top / 2 + child.Bottom / 2;
if (mChoose.Count == 1)// 当前添加为第一个
{
mPath.MoveTo(mLastPathX, mLastPathY);
} else
// 非第一个,将两者使用线连上
{
mPath.LineTo(mLastPathX, mLastPathY);
}
}
}
// 指引线的终点
mTmpTarget.X = x;
mTmpTarget.Y = y;
break;
case MotionEventActions.Up:
mPaint.Color = Color.ParseColor(mFingerUpColor);
mPaint.Alpha = 50;
this.mTryTimes--;
// 回调是否成功
if (mOnGestureLockViewListener != null && mChoose.Count > 0)
{
mOnGestureLockViewListener.OnGestureEvent(checkAnswer());
if (this.mTryTimes == 0)
{
mOnGestureLockViewListener.OnUnmatchedExceedBoundary();
}
}
System.Diagnostics.Debug.WriteLine(TAG, "mUnMatchExceedBoundary = " + mTryTimes);
System.Diagnostics.Debug.WriteLine(TAG, "mChoose = " + mChoose);
// 将终点设置位置为起点,即取消指引线
mTmpTarget.X = mLastPathX;
mTmpTarget.Y = mLastPathY;
// 改变子元素的状态为UP
changeItemMode();
// 计算每个元素中箭头需要旋转的角度
for (int i = 0; i + 1 < mChoose.Count; i++)
{
int childId = mChoose[i];
int nextChildId = mChoose[i + 1];
GestureLockView startChild = (GestureLockView)FindViewById(childId);
GestureLockView nextChild = (GestureLockView)FindViewById(nextChildId);
int dx = nextChild.Left - startChild.Left;
int dy = nextChild.Top - startChild.Top;
// 计算角度
int angle = (int)Java.Lang.Math.ToDegrees(Java.Lang.Math.Atan2(dy, dx)) + 90;
startChild.setArrowDegree(angle);
}
break;
}
Invalidate();
return true;
}
private void changeItemMode()
{
foreach (GestureLockView gestureLockView in mGestureLockViews)
{
if (mChoose.Contains(gestureLockView.Id))
{
gestureLockView.setMode(GestureLockView.LockViewMode.STATUS_FINGER_UP);
}
}
}
/**
*
* 做一些必要的重置
*/
private void reset()
{
mChoose.Clear();
mPath.Reset();
foreach (GestureLockView gestureLockView in mGestureLockViews)
{
gestureLockView.setMode(GestureLockView.LockViewMode.STATUS_NO_FINGER);
gestureLockView.setArrowDegree(-1);
}
}
/**
* 检查用户绘制的手势是否正确
* @return
*/
private bool checkAnswer()
{
if (mAnswer.Length != mChoose.Count)
return false;
for (int i = 0; i < mAnswer.Length; i++)
{
if (mAnswer[i] != mChoose[i])
return false;
}
return true;
}
/**
* 检查当前左边是否在child中
* @param child
* @param x
* @param y
* @return
*/
private bool checkPositionInChild(View child, int x, int y)
{
//设置了内边距,即x,y必须落入下GestureLockView的内部中间的小区域中,可以通过调整padding使得x,y落入范围不变大,或者不设置padding
int padding = (int)(mGestureLockViewWidth * 0.15);
if (x >= child.Left + padding && x <= child.Right - padding && y >= child.Top + padding && y <= child.Bottom - padding)
{
return true;
}
return false;
}
/**
* 通过x,y获得落入的GestureLockView
* @param x
* @param y
* @return
*/
private GestureLockView getChildIdByPos(int x, int y)
{
foreach (GestureLockView gestureLockView in mGestureLockViews)
{
if (checkPositionInChild(gestureLockView, x, y))
{
return gestureLockView;
}
}
return null;
}
/**
* 设置回调接口
*
* @param listener
*/
public void setOnGestureLockViewListener(OnGestureLockViewListener listener)
{
this.mOnGestureLockViewListener = listener;
}
/**
* 对外公布设置答案的方法
*
* @param answer
*/
public void setAnswer(int[] answer)
{
this.mAnswer = answer;
}
/**
* 设置最大实验次数
*
* @param boundary
*/
public void setUnMatchExceedBoundary(int boundary)
{
this.mTryTimes = boundary;
}
protected override void DispatchDraw(Canvas canvas)
{
base.DispatchDraw(canvas);
//绘制GestureLockView间的连线
if (mPath != null)
{
canvas.DrawPath(mPath, mPaint);
}
//绘制指引线
if (mChoose.Count > 0)
{
if (mLastPathX != 0 && mLastPathY != 0)
{
canvas.DrawLine(mLastPathX, mLastPathY, mTmpTarget.X, mTmpTarget.Y, mPaint);
}
}
}
public interface OnGestureLockViewListener
{
/**
* 单独选中元素的Id
*
* @param position
*/
void OnBlockSelected(int cId);
/**
* 是否匹配
*
* @param matched
*/
void OnGestureEvent(bool matched);
/**
* 超过尝试次数
*/
void OnUnmatchedExceedBoundary();
}
}
}
- 实现3:对Forms上渲染,注意命名空间就行
using Android.Content;
using Android.Graphics;
using Android.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(EZPlatform.Renderer.Forms.LockView), typeof(EZPlatform.Droid.Renderer.LockViewRenderer))]
namespace EZPlatform.Droid.Renderer
{
public class LockViewRenderer : ViewRenderer
{
private GestureLockViewGroup mGestureLockViewGroup;
protected override void OnElementChanged(ElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (Control == null)
{
mGestureLockViewGroup = (GestureLockViewGroup)FindViewById(Resource.Id.id_gestureLockViewGroup);
mGestureLockViewGroup?.setAnswer(new int[] { 1, 2, 3, 4, 5 });
System.Diagnostics.Debug.WriteLine($"======== {mGestureLockViewGroup} - {mGestureLockViewGroup == null} ========");
//SetNativeControl(new GestureLockView(Context, "#FF939090", "#FFE0DBDB" ,"#FF378FC9", "#FFFF0000"));
SetNativeControl(new GestureLockViewGroup(Toolkit.main, null));
}
if (e.NewElement != null)
{
}
}
}
}
- Noti:需要添加对应的xml文件
希望我以后慢慢看得懂 -_-||
至此,文件都添加完成,参考文档 iOS Android - 都是原生代码的项目,Android可能不是的原因,地址不能跳转,因此贴出地址 http://blog.csdn.net/lmj623565791/article/details/36236113
代码都已经贴了,就不上传项目了,注意命名空间就行
- 调用就 So easy 啦,
var lockView = new EZPlatform.Renderer.Forms.LockView()
{
Cipher = "1234",
};
lockView.DrawCompleted += (state) =>
{
System.Diagnostics.Debug.WriteLine($"--- {state} {lockView.Cipher} ---");
};
Content = lockView;
最后,附上两个小图