Xamarin Renderer 手势锁 LockView

对于一些需求,虽然无奈和蛋疼但还是要实现的

Forms

  1. 在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;

最后,附上两个小图


Xamarin Renderer 手势锁 LockView_第1张图片
iOS

Xamarin Renderer 手势锁 LockView_第2张图片
Android

Xamarin Renderer 手势锁 LockView_第3张图片

你可能感兴趣的:(Xamarin Renderer 手势锁 LockView)