美文网首页Xamarin
Xamarin Renderer 手势锁 LockView

Xamarin Renderer 手势锁 LockView

作者: pruple_Boy | 来源:发表于2017-09-11 16:55 被阅读0次

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

    Forms

    1. 在Xamarin.Form下新建一个视图类
    using System;
    using Xamarin.Forms;
    
    namespace EZPlatform.Renderer.Forms
    {
        public enum ClockState
        {
            VerifySuccess,
            VerifyFailure,
            SettingCipher,
            ForgetCipher,
        }
    
        public class LockView : View
        {
            /// <summary>
            /// 若是验证则传入密码,否则为设置密码
            /// </summary>
            /// <value>The old cipher.</value>
            public string Cipher { set; get; }
            /// <summary>
            /// 若为传入值则取屏幕高度
            /// </summary>
            /// <value>The height of the screen.</value>
            public double ScreenHeight { set; get; }
    
         
            public delegate void DrawCompletedDelegate(ClockState state);
            /// <summary>
            /// The draw completed.
            /// </summary>
            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<EZPlatform.Renderer.Forms.EllipseView, EllipseUIView>
        {
            protected override void OnElementChanged(ElementChangedEventArgs<EZPlatform.Renderer.Forms.EllipseView> 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; }
    
            /// <summary>
            /// The draw completed.
            /// </summary>
            public EZPlatform.Renderer.Forms.LockView.DrawCompletedDelegate DrawCompleted;
    
            private List<UIButton> 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<UIButton>();
    
                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<int> mChoose = new List<int>();
    
            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<EZPlatform.Renderer.Forms.LockView, GestureLockViewGroup>
        {
            private GestureLockViewGroup mGestureLockViewGroup;
    
            protected override void OnElementChanged(ElementChangedEventArgs<EZPlatform.Renderer.Forms.LockView> 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文件
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <EZPlatform.Droid.Renderer.GestureLockViewGroup
            android:id="@+id/id_gestureLockViewGroup"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#F2F2F7"
            android:gravity="center_vertical" />
    </RelativeLayout>
    

    希望我以后慢慢看得懂 -_-||

        <?xml version="1.0" encoding="utf-8"?>  
        <resources> 
            <attr name="color_no_finger_inner_circle" format="color" />  
            <attr name="color_no_finger_outer_circle" format="color" />  
            <attr name="color_finger_on" format="color" />  
            <attr name="color_finger_up" format="color" />  
            <attr name="count" format="integer" />  
            <attr name="tryTimes" format="integer" />  
          
            <declare-styleable name="GestureLockViewGroup">  
                <attr name="color_no_finger_inner_circle" />  
                <attr name="color_no_finger_outer_circle" />  
                <attr name="color_finger_on" />  
                <attr name="color_finger_up" />  
                <attr name="count" />  
                <attr name="tryTimes" />  
            </declare-styleable>  
          
        </resources>  
    

    至此,文件都添加完成,参考文档 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;
    

    最后,附上两个小图


    iOS Android

    相关文章

      网友评论

        本文标题:Xamarin Renderer 手势锁 LockView

        本文链接:https://www.haomeiwen.com/subject/jlsfsxtx.html