自定义手势解锁

逼逼几句

  • 这几天由于UI妹子的设计图还在审核,让我有时间研究了一下手势解锁的原理,到这里得感谢一下UI妹子,废话我就少说了,前几天我在CSDN翔神的博客上看见了一篇关于手势解锁的文章,主要平时我一般亏心事干得的,所以就研究研究,改一下其中的东西,修修了bug,为自己所用了,看上感觉这个自定义的很复杂,各种状态,好几种颜色,看着都有种不想做的冲动,想要成功,必先苦其心志,然后饿几天,当你有孔子的心态的时候,此刻你就应该拥有觉老师的分享,没有破釜沉舟的心态,就算天蓬元帅八戒来了也带不动你!!!又说多了,说这篇文章,你可以设置每行3个或者4个按钮,设置你自己想要的Color,让你一直过瘾为止。

直接上效果图

  • 这里我制作GIF比较麻烦就搞了一种状态

这里写图片描述

这里写图片描述

看完效果图之后,正常的你,应该有想下看的冲动,所以讲大致的思路

思路:

一. 先绘制单个的小圆(子View:GustureLockedView)

  • 1.我们需要确定解锁园的构造:—个外圆、实心的内圆、三角形
  • 2.确定手势按圆的几种状态(无触摸、按下、抬起),根据不同的状态设置不同的颜色值(4种Color)
  • 3.计算圆的大小,以及各个尺寸大小,然后开始绘制
  • 4.确定三角形线条path,画封闭的三角形(设置只有当手指抬起的时设置旋转的角度候再显示出来)

二.绘制整个的布局,把每个子View摆放上去(整个大的:GuestureLockedViewGroup)

  • 1.获取自定义属性(主要是获取自己设置的颜色和每行子View的个数)
  • 2.实例化画笔和线(设置画笔的链接时,显示的状态)
  • 3.重写onMeasure方法,计算每个子View的大小与摆放位置(给每个子View设置一个id,然后根据id设置前后左右,或者是margin值)
  • 4.重写onThouch 方法根据不同的手势去改变子View的颜色
  • 5.自定义回调接口,方法密码匹配情况 与 错误的次数 以及选择的子View的id

实战步骤:

  • 先放上变量,方便大家阅读
  private String TAG = "GustureLockedView";

    //没有触摸时,外圆颜色
    private int noFigerOutColor = 0xFFE0DBDB;

    //没有触摸时,内圆颜色
    private int noFigerInerColor = 0xFF939090;

    //手指在上面的时候,圆的颜色
    private int onFigerColor = 0xFF378FC9;

    //手机抬起时,被触摸的圆的颜色
    private int upFigerColor = 0xFFFF0000;

    //初始化手指状态
    private Mode status = Mode.STATUS_NO_FIGER;

    //外圆的宽度
    private int outRoundWeith;

    //内圆高度
    private int outRoundHight;

    //外圆半径
    private int mRound;

    //三角型的高的基数
    private float heigthRate = 0.333f;

    //内圆半径的基数
    private float mRoundRate = 0.3f;

    //画笔的宽度
    private int mStoreWidth = 2;

    //画笔
    Paint mPaint;

    //划线
    Path mPath;

    //旋转的度数
    private int mProcess = 0;

    /**
     *记录手指触摸状态
     */
    enum Mode{
        STATUS_NO_FIGER,STATUS_FIGER_ON,STATUS_UP_FIGER
    }
  • 首先设置子View的在不同状态下的颜色值(最初只用到两种)
 public GustureLockedView(Context context,int noFigerOutColor,int noFigerInerColor,
                             int onFigerColor,int upFigerColor) {
        super(context);
        //初始化颜色
        this.noFigerOutColor = noFigerOutColor;
        this.noFigerInerColor = noFigerInerColor;
        this.onFigerColor = onFigerColor;
        this.upFigerColor = upFigerColor;
        //初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPath = new Path();
    }
  • 颜色值都有了,我们现在是不是要在onMeasure()方法里面做测量了,这面绘制的三角型的三点的坐标我也直接到这个方法做了

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);


        outRoundWeith = MeasureSpec.getSize(widthMeasureSpec);
        outRoundHight = MeasureSpec.getSize(heightMeasureSpec);


        outRoundWeith = outRoundWeith < outRoundHight ? outRoundWeith:outRoundHight;
        mRound = (outRoundWeith-mStoreWidth)/2;
        Log.i(TAG,"mRound::"+mRound);

    mPath.moveTo(outRoundWeith/2,mStoreWidth+2);
    //左边的点
                    mPath.lineTo(outRoundWeith/2mRound*heigthRate,mRound*heigthRate+mStoreWidth+2);
    //右边的点
            mPath.lineTo(outRoundWeith/2+mRound*heigthRate,mRound*heigthRate+mStoreWidth+2);
    //封闭然后组成一个三角型
    mPath.close();
    //当绘制重叠时,能融洽的结合(主要指颜色,哈)看不懂自己百度
    mPath.setFillType(Path.FillType.WINDING);

    }
  • 三角型都有了,那我们差什么???对,没错,圆。。现在根据不同的状态绘制圆的颜色咯
  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        switch (status){
        //当没有手指触摸的时候
            case STATUS_NO_FIGER:

                //外圆
                mPaint.setStyle(Paint.Style.FILL);
                mPaint.setColor(noFigerOutColor);
                canvas.drawCircle(outRoundWeith/2,outRoundWeith/2,mRound,mPaint);
                //内圆
                mPaint.setColor(noFigerInerColor);
                canvas.drawCircle(outRoundWeith / 2, outRoundWeith / 2, mRound * mRoundRate, mPaint);
                Log.i(TAG, "画圆了:::"+noFigerOutColor+outRoundWeith);
                break;
   //当手指在上面的时候
            case  STATUS_FIGER_ON:

                //外圆
                mPaint.setStyle(Paint.Style.STROKE);
                mPaint.setStrokeWidth(mStoreWidth);
                mPaint.setColor(onFigerColor);
                canvas.drawCircle(outRoundWeith/2,outRoundWeith/2,mRound,mPaint);

                //内圆
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(outRoundWeith/2,outRoundWeith/2,mRound*mRoundRate,mPaint);
                break;
//当手指离开界面,抬起的时候
            case STATUS_UP_FIGER:

                //外圆
                mPaint.setStyle(Paint.Style.STROKE);
                mPaint.setStrokeWidth(mStoreWidth);
                mPaint.setColor(upFigerColor);
                canvas.drawCircle(outRoundWeith/2,outRoundWeith/2,mRound,mPaint);

                //内圆
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(outRoundWeith/2,outRoundWeith/2,mRound*mRoundRate,mPaint);

                drawArrow(canvas);
                break;
        }

    }
  • 上面的onMeasure() onDraw()都是系统自己调用的,如果我要改变自己的状态,如果只有系统的方法,是不是远远不够呢??是不是应该有自己的方法呢???下面我另外几个方面写出来,会在GuestureLockedViewGroup会调用他

  /**
     * 在 GuestureLockedViewGroup 中调用
     * 画三角型图标
     */
    public void drawArrow(Canvas canvas){
        if (mProcess!=-1){
            canvas.save();
            //当手指抬起时设置旋转的三角型度数
            canvas.rotate(mProcess,outRoundWeith/2,outRoundWeith/2);
            //前面指设置了三角型坐标,现在就要绘制了
            canvas.drawPath(mPath,mPaint);
            canvas.restore();
        }
    }


    /**
     * 为了设置三角型旋转的度数
     */
   public void setPrecess(int process){
       this.mProcess= process;
   }


    /**
     *设置手指触摸的状态
     * @param mode
     */
    public void setMode(Mode mode){
        this.status = mode;
        invalidate();
    }

*到这里我们算完成了皮毛,因为解锁页面很多按钮,我们才完成1个,算算,如果你设置每行3个,那个是1/9,如果每行4个呢,那就1/16。。直接我把上面的做好的view 重复的放到页面上去,记住我说是重复:
* 先看注释,方便阅读

  private String TAG = "GuestureLockedViewGroup";

    //没有触摸时,外圆颜色
    private int noFigerOutColor = 0xFFE0DBDB;

    //没有触摸时,内圆颜色
    private int noFigerInerColor = 0xFF939090;

    //手指在上面的时候,圆的颜色
    private int onFigerColor = 0xFF378FC9;

    //手机抬起时,被触摸的圆的颜色
    private int upFigerColor = 0xFFFF0000;

    //每行子view的数量
    private int mCount;

    //允许尝试的次数
    private int mTryCount;

    //画笔 和 线条
    private Paint mPaint;
    private Path mPath;

    //每个子view 的宽度及间距
    private int mGestureLockViewWidth;
    private int mMarginBetweenLockView;

    //用来储存选中的id
    private List<Integer> mChoose = new ArrayList<Integer>();

    //保存所有的GestureLockView
    private GustureLockedView[] mGestureLockViews;

    //选定的子view中心的坐标
    private int lastViewCentX ;
    private int lastViewCentY ;

    //终点坐标
    Point mPoint = new Point();

    //设置密码
    private  int[] passWord;

    //是否可以解锁
    private boolean isLoack = false;

    //监听的接口
    CheckPassWordStatusListener listener;
  • 获取自己定义的属性 和 设置 画笔;
  public GuestureLockedViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.GuestureLockedViewGroup);
        noFigerOutColor = ta.getColor(R.styleable.GuestureLockedViewGroup_no_finger_color_out, 0xFFE0DBDB);
        noFigerInerColor = ta.getColor(R.styleable.GuestureLockedViewGroup_no_finger_color_inner, 0xFF939090);
        onFigerColor = ta.getColor(R.styleable.GuestureLockedViewGroup_on_finger_color, 0xFF378FC9);
        upFigerColor = ta.getColor(R.styleable.GuestureLockedViewGroup_up_finger_color, 0xFFFF0000);
        mTryCount = ta.getInt(R.styleable.GuestureLockedViewGroup_m_try_time, 3);
        mCount = ta.getInt(R.styleable.GuestureLockedViewGroup_mcount, 4);
        ta.recycle();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // 初始化画笔
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPath = new Path();
    }
  • 通过测量摆放每个子View的位置
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //获取控件的宽度
        int weigth = MeasureSpec.getSize(widthMeasureSpec);
        int higth = MeasureSpec.getSize(heightMeasureSpec);

        weigth = weigth < higth ? weigth : higth;

        //计算每个控件的大小
        // 计算每个GestureLockView的宽度
        mGestureLockViewWidth = (int) (4 * weigth * 1.0f / (5 * mCount + 1));
        //计算每个GestureLockView的间距
        mMarginBetweenLockView = (int) (mGestureLockViewWidth * 0.25);
        // 设置画笔的宽度为GestureLockView的内圆直径稍微小点(不喜欢的话,随便设)
        mPaint.setStrokeWidth(mGestureLockViewWidth * 0.29f);
        //设置子view的个数,以及摆放的位置
        if (mGestureLockViews==null){

            mGestureLockViews = new GustureLockedView[mCount*mCount];
            //设置大小
            for (int i=0; i<mGestureLockViews.length; i++){
                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(mGestureLockViewWidth,mGestureLockViewWidth);
                //初始化id
                mGestureLockViews[i] = new GustureLockedView(getContext(),noFigerOutColor,noFigerInerColor
                ,onFigerColor,upFigerColor);
                //设置id
                mGestureLockViews[i].setId(i + 1);
                //不是第一列的数,都在其的右边
                if (i%mCount!=0){
                    params.addRule(RelativeLayout.RIGHT_OF, mGestureLockViews[i - 1].getId());
                }

                //不是第一行都在指定Id的下边
                if(i>=mCount){
                    params.addRule(RelativeLayout.BELOW,mGestureLockViews[i-mCount].getId());
                }

                int leftMargin=0;
                int topMargin=0;
                int rightMargin = mGestureLockViewWidth/4;
                int belowMargin = mGestureLockViewWidth/4;
                //第一行
                if (i<mCount){
                    topMargin = mGestureLockViewWidth/4;
                }
                //第一列
                if(i%mCount==0){
                    leftMargin = mGestureLockViewWidth/4;
                }
                params.setMargins(leftMargin,topMargin,rightMargin,belowMargin);
                mGestureLockViews[i].setMode(GustureLockedView.Mode.STATUS_NO_FIGER);
                addView(mGestureLockViews[i], params);
            }
        }
    }
  • 然后我们根据手势改变状态咯,获取手指的触摸点,看是落到那个子View上面
@Override
    public boolean onTouchEvent(MotionEvent event) {
        if(!isLoack){
            int lastX = (int) event.getX();
            int lastY = (int) event.getY();
            Log.i(TAG,"lastX::"+lastX+".........."+"lastY:::"+lastY);
            switch (event.getAction()){



                case MotionEvent.ACTION_MOVE:

                    mPaint.setColor(onFigerColor);
                    mPaint.setAlpha(50);
                    GustureLockedView view = checkRange(lastX,lastY);
                    Log.i(TAG,"view为Null::"+view);
                    if (view!=null){
                        int childId = view.getId();
                        if (!mChoose.contains(childId)){
                            mChoose.add(childId);
                            view.setMode(GustureLockedView.Mode.STATUS_FIGER_ON);

                            lastViewCentX = (view.getLeft()+view.getRight())/2;

                            lastViewCentY = (view.getTop()+view.getBottom())/2;

                            //如果size为1,开始的子view
                            if (mChoose.size()==1){
                                mPath.moveTo(lastViewCentX,lastViewCentY);
                            }else{
                                mPath.lineTo(lastViewCentX,lastViewCentY);
                            }

//                        if (mChoose.size() == 1)// 当前添加为第一个
//                        {
//                            mPath.moveTo(mLastPathX, mLastPathY);
//                        } else
//                        // 非第一个,将两者使用线连上
//                        {
//                            mPath.lineTo(mLastPathX, mLastPathY);
//                        }

                        }
                    }
                    mPoint.x = lastX;
                    mPoint.y = lastY;
                    break;

                case MotionEvent.ACTION_UP:
                    mPoint.x = lastViewCentX;
                    mPoint.y = lastViewCentY;
                    mPaint.setColor(upFigerColor);
                    mPaint.setAlpha(50);
                    for (int i=0; i<mChoose.size();i++){
                        mGestureLockViews[mChoose.get(i)-1].setMode(GustureLockedView.Mode.STATUS_UP_FIGER);
                    }
                    for (int i=0; i<mChoose.size()-1; i++){
                        //设置被选中的View的状态
                        GustureLockedView startView = (GustureLockedView) findViewById(mChoose.get(i));
                        GustureLockedView endView = (GustureLockedView) findViewById(mChoose.get(i+1));

                        int tanX = endView.getLeft() - startView.getLeft();
                        int tanY = endView.getTop() - startView.getTop();

                        // 计算角度
                        int angle = (int) Math.toDegrees(Math.atan2(tanY, tanX)) + 90;
                        startView.setPrecess(angle);
                    }

                    boolean isRight = contrastsPassVoid();

                    if (listener!=null&&mChoose.size()>0){
                        listener.checkPassWordIsRight(isRight);
                    }

                    break;
            }

            invalidate();
        }

        return true;
    }

    private boolean contrastsPassVoid(){
        if (passWord.length!=mChoose.size())
            return false;
        for (int i=0; i<passWord.length; i++){
            if (passWord[i]!=mChoose.get(i)){
                return false;
            }
        }

        return true;
    }


    /**
     * 检测范围
     * @param x
     * @param y
     */
    public GustureLockedView checkRange(int x, int y){

        for (GustureLockedView gustureLockedView: mGestureLockViews){
           int  mRang = (int) (mGestureLockViewWidth*0.15);

            if (gustureLockedView.getLeft()+mRang<x&&gustureLockedView.getRight()-mRang>x&&
                     gustureLockedView.getTop()+mRang<y&&gustureLockedView.getBottom()-mRang>y){
                return gustureLockedView;
            }
        }



        return null;
    }
  • 上面的主要的方法,我都列举出来了,下面写几个一看就懂的方法
    /**
     * 一切初始化,在activity中调用。
     */
    public void resetView(){

        mPath.reset();
        mChoose.clear();
        for (GustureLockedView gustureLockedView : mGestureLockViews){
            gustureLockedView.setPrecess(-1);
            gustureLockedView.setMode(GustureLockedView.Mode.STATUS_NO_FIGER);
        }

    }

    /**
     * 设置密码
     * @param passWord
     */
    public void setPassWord(int[] passWord){
        this.passWord = passWord;
    }

    public interface  CheckPassWordStatusListener{

        void checkPassWordIsRight(boolean isRight);

    }

    /**
     * 设置回调的方法
     * @param checkPassWordStatusListener
     */

    public void setCheckPassWordStatusListener(CheckPassWordStatusListener checkPassWordStatusListener){
            this.listener =     checkPassWordStatusListener;
    }

    /**
     * 如果超过设置的次数就设置ture,用户就不能再滑动了
     * @param isLocked
     */
    public void setIsLocked(boolean isLocked){

        this.isLoack = isLocked;
    }
  • 还没有看懂童鞋,在给你们看看activity中处理的逻辑了
public class MainActivity extends Activity {


    private SharedPreferences sp ;
    private SharedPreferences.Editor editor;
    private GuestureLockedViewGroup gvLockedView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sp =  getSharedPreferences("mTryCount", MODE_PRIVATE);
        editor = sp.edit();
        //默认最多四次
        editor.putInt("allCount", 5);
        editor.commit();
        gvLockedView = (GuestureLockedViewGroup) findViewById(R.id.gv_locked_view);
        gvLockedView.setPassWord(new int[]{1,2,3,4,5,6});
        gvLockedView.setCheckPassWordStatusListener(new GuestureLockedViewGroup.CheckPassWordStatusListener() {
            @Override
            public void checkPassWordIsRight(boolean isRight) {
                if (isRight){
                    Toast.makeText(MainActivity.this,"觉哥,你既然一次性解锁成功,你应该去盗银行!!",Toast.LENGTH_SHORT).show();
                    editor.putInt("allCount",5);
                    editor.commit();
                }else{
                    int count =  sp.getInt("allCount",0);
                    if (count==1){
                        //不能再解锁了
                        gvLockedView.setIsLocked(true);
                    }
                    count = --count;
                    editor.putInt("allCount",count);
                    editor.commit();
                    Toast.makeText(MainActivity.this,"你以为你可以像觉哥一样牛B轰轰的,还有"+count+"机会哦",Toast.LENGTH_SHORT).show();
                }

                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //按密码完成,恢复最初的状态。
                        gvLockedView.resetView();
                        gvLockedView.postInvalidate();
                    }
                },1000);

            }
        });
    }


    public Handler mHandler = new Handler();
}
  • 另外附上 布局和自定义属性
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:xj="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <aguche.gestureunlocked.com.view.GuestureLockedViewGroup
        android:id="@+id/gv_locked_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xj:m_try_time="3"
        xj:mcount="3"
        xj:no_finger_color_inner="#FF939090"
        xj:no_finger_color_out="#FFE0DBDB"
        xj:on_finger_color="#FF378FC9"
        xj:up_finger_color="#FFFF0000" />
</RelativeLayout>
  • 今天就到这里了,觉老师得下班吃饭去了,别忘了点个赞。给我顶顶!!!!!!!!
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值