自己动手做一个--手势解锁

手势解锁在Android手机上算是很普遍的了,在网上也有很多大牛做的很完美, 但还是想自己琢磨着做一遍~~


首先分析一下:
手势解锁其实就相当于 就是1~9的不同排列方式,根密码差不多,但不同的是,这个密码是数字不重复的,一个数字只能出现一次.

在这里我们暂且就把9个圆形编个号,就横向排列1~9号
分析过小米手势解锁的就会发现:
如图: 如果密码是1-7-4 ,事实上手从1绕过4滑到7上去时,事实上小米的手势解锁已经算作划过了4,出来的密码也就是 1-4-7了, 也就是说,这样的设置你永远都不可能划出1-7-4的密码出来
这里写图片描述

我实现的这个手势解锁没有要求不能出现这种1-7-4的这种密码,没有任何限制的这种,看了其他的一些APP或手机,大部分手势密码也都是没有这种限制的.

好了,既然手势密码分析得差不多了,就该想想怎么实现了~
首先准备好图片资源:
–这是按钮未选中时的样子:
未选中时的按钮
–这是按钮已选中时的样子:
这里写图片描述
–这是密码输入错误时的样子:
这里写图片描述

然后写一个CheckBox的选择器:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:state_enabled="true" android:state_checked="false" android:drawable="@drawable/btn_radio_off"></item>
    <item android:state_enabled="true" android:state_checked="true" android:drawable="@drawable/btn_radio_on"></item>

    <item android:state_enabled="false" android:state_checked="false" android:drawable="@drawable/btn_radio_off"></item>
    <item android:state_enabled="false" android:state_checked="true" android:drawable="@drawable/btn_radio_error"></item>

</selector>

然后写一个9宫格解锁的那个布局,用RelativeLayout,大概样子就是:
这里写图片描述
现在看着是挺丑的哈..没关系,因为最终效果并不是这个样子啦,贴上代码:

<?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"
    >

    <CheckBox 
        android:id="@+id/unlock_cb_0"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/radio_btn"
        android:button="@null"
        />
    <CheckBox 
        android:id="@+id/unlock_cb_1"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/radio_btn"
        android:button="@null"
        android:layout_centerHorizontal="true"
        />
    <CheckBox 
        android:id="@+id/unlock_cb_2"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/radio_btn"
        android:button="@null"
        android:layout_alignParentRight="true"
        />
    <CheckBox 
        android:id="@+id/unlock_cb_3"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/radio_btn"
        android:button="@null"
        android:layout_centerVertical="true"
        />
    <CheckBox 
        android:id="@+id/unlock_cb_4"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/radio_btn"
        android:button="@null"
        android:layout_centerInParent="true"
        />
    <CheckBox 
        android:id="@+id/unlock_cb_5"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/radio_btn"
        android:button="@null"
        android:layout_centerVertical="true"
        android:layout_alignParentRight="true"
        />
    <CheckBox 
        android:id="@+id/unlock_cb_6"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/radio_btn"
        android:button="@null"
        android:layout_alignParentBottom="true"
        />
    <CheckBox 
        android:id="@+id/unlock_cb_7"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/radio_btn"
        android:button="@null"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        />
    <CheckBox 
        android:id="@+id/unlock_cb_8"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/radio_btn"
        android:button="@null"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        />

</RelativeLayout>

好了,到此为止好像还差很多啊,任重道远!!!
我先大概说说我的思路:

1.先自己写一个view继承自FrameLayout(其他的Layout也行)
2.然后再把刚才那个9宫格的布局加进去
3.拦截Touch事件
4.根据手指滑动的X,Y值计算出滑到哪个按钮了,然后做相应操作.

好了逻辑这个东西不是三言两语说的清楚的,直接上代码吧,我会在代码中尽量解释清楚:

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.CheckBox;
import android.widget.FrameLayout;

import com.yuc.common.R;
/**
 * YUC
 * @author Administrator
 *  2016 - 1 - 23
 */
public class UnlockView extends FrameLayout{

    private static String backgroundColor = "01000000";

    private int view_width;//整个空间宽度
    private int btn_width;//按钮宽度,直径
    private int view_height;
//  private ArrayList<int[]> btn_coord_list;
    private int[][] btn_coord;
    private List<CheckBox> checkBox_btns;

    /**
     * 密码
     */
    private List<Integer> password = new ArrayList<Integer>();
    private float x;//当前手指坐标
    private float y;

    private boolean isError = false;//当前密码是否正确
    private boolean isTouch = false;//是否正在操作

    /**
     * 正确密码. 
     *  为空时,当前模式为捕捉用户输入(用于设置手势)
     *  不为空,当前模式为验证密码模式(用于解锁验证)
     */
    private String rightPwd;
    private OnUnlockListener onUnlockListener;
    private OnGetPwdListener onGetPwdListener;

    public void setRightPwd(String rightPwd){
        this.rightPwd=rightPwd;
    }

    public void setOnUnlockListener(OnUnlockListener onUnlockListener) {
        this.onUnlockListener = onUnlockListener;
    }

    public void setOnGetPwdListener(OnGetPwdListener onGetPwdListener) {
        this.onGetPwdListener = onGetPwdListener;
    }

    /**
     * 验证模式下,密码验证监听
     * @author Administrator
     */
    public interface OnUnlockListener{
        void onSuccess();
        void onError();
    }
    /**
     * 捕捉用户输入模式下,密码获取监听
     * @author Administrator
     */
    public interface OnGetPwdListener{
        void onSetting(String pwd);
    }

    /**
     *  重置9宫格
     * @param context
     */
    Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            case 1:
                if (!isTouch) {
                    reset();
                }
                break;
            }
        }

    };

    public UnlockView(Context context) {
        super(context);
        initView();
    }

    public UnlockView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public UnlockView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        //高度,和宽度一样,保证view是个正方形
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
    }

    /**
     * 初始化view
     */
    private void initView() {
        //将9宫格按钮的布局文件加载进来
        inflate(getContext(), R.layout.unlock_layout, this);
        setBackgroundColor(Color.parseColor(backgroundColor));

        CheckBox unlock_cb_0 = (CheckBox) findViewById(R.id.unlock_cb_0);
        CheckBox unlock_cb_1 = (CheckBox) findViewById(R.id.unlock_cb_1);
        CheckBox unlock_cb_2 = (CheckBox) findViewById(R.id.unlock_cb_2);
        CheckBox unlock_cb_3 = (CheckBox) findViewById(R.id.unlock_cb_3);
        CheckBox unlock_cb_4 = (CheckBox) findViewById(R.id.unlock_cb_4);
        CheckBox unlock_cb_5 = (CheckBox) findViewById(R.id.unlock_cb_5);
        CheckBox unlock_cb_6 = (CheckBox) findViewById(R.id.unlock_cb_6);
        CheckBox unlock_cb_7 = (CheckBox) findViewById(R.id.unlock_cb_7);
        CheckBox unlock_cb_8 = (CheckBox) findViewById(R.id.unlock_cb_8);

        checkBox_btns = new ArrayList<CheckBox>();
        checkBox_btns.add(unlock_cb_0);
        checkBox_btns.add(unlock_cb_1);
        checkBox_btns.add(unlock_cb_2);
        checkBox_btns.add(unlock_cb_3);
        checkBox_btns.add(unlock_cb_4);
        checkBox_btns.add(unlock_cb_5);
        checkBox_btns.add(unlock_cb_6);
        checkBox_btns.add(unlock_cb_7);
        checkBox_btns.add(unlock_cb_8);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //在这里获取按钮的尺寸,view的尺寸等
        view_width = getWidth();
        view_height = getHeight();
        View lock_btn = findViewById(R.id.unlock_cb_0);
        btn_width = lock_btn.getWidth();

        //计算各按钮中点坐标
        int[] btn1_coord = {btn_width/2,btn_width/2};
        int[] btn2_coord = {view_width/2,btn_width/2};
        int[] btn3_coord = {view_width-(btn_width/2),btn_width/2};
        int[] btn4_coord = {btn_width/2,view_height/2};
        int[] btn5_coord = {view_width/2,view_height/2};
        int[] btn6_coord = {view_width-(btn_width/2),view_height/2};
        int[] btn7_coord = {btn_width/2,view_height-(btn_width/2)};
        int[] btn8_coord = {view_width/2,view_height-(btn_width/2)};
        int[] btn9_coord = {view_width-(btn_width/2),view_height-(btn_width/2)};

        //保存到一个二维数组中
        btn_coord = new int[9][2];
        btn_coord[0][0] = btn1_coord[0];
        btn_coord[0][1] = btn1_coord[1];

        btn_coord[1][0] = btn2_coord[0];
        btn_coord[1][1] = btn2_coord[1];

        btn_coord[2][0] = btn3_coord[0];
        btn_coord[2][1] = btn3_coord[1];

        btn_coord[3][0] = btn4_coord[0];
        btn_coord[3][1] = btn4_coord[1];

        btn_coord[4][0] = btn5_coord[0];
        btn_coord[4][1] = btn5_coord[1];

        btn_coord[5][0] = btn6_coord[0];
        btn_coord[5][1] = btn6_coord[1];

        btn_coord[6][0] = btn7_coord[0];
        btn_coord[6][1] = btn7_coord[1];

        btn_coord[7][0] = btn8_coord[0];
        btn_coord[7][1] = btn8_coord[1];

        btn_coord[8][0] = btn9_coord[0];
        btn_coord[8][1] = btn9_coord[1];

        //拦截Touch事件
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //当 当前密码不为空时,说明用户正在绘制手势,现在就要把手势按钮中间的连线画出来
        if (password!=null) {
            Paint paint=new Paint();
            //按钮之间的连接画线
            if (isError) {
                //输入错误时线的颜色
                paint.setColor(Color.RED);
            }else{
                //正常输入时线的颜色
                paint.setColor(Color.YELLOW);
            }
            paint.setAlpha(125);
            paint.setStrokeWidth(10);
    //      canvas.drawLine(50, 50, 500, 500, paint);

            //绘制按钮间中间连线
            for (int i = 0; i < password.size(); i++) {
                if (i==password.size()-1) {
                    //最后一个,只有一个
                    if (x>0&&y>0) {
                        canvas.drawLine(btn_coord[password.get(i)-1][0], btn_coord[password.get(i)-1][1], x, y, paint);
                    }
                }else{
                    //不是第一个,也不是最后一个
                    canvas.drawLine(btn_coord[password.get(i)-1][0], btn_coord[password.get(i)-1][1], btn_coord[password.get(i+1)-1][0], btn_coord[password.get(i+1)-1][1], paint);
                }
            }
        }


        super.onDraw(canvas);

    }

    /**
     * 在这里控制9个checkbox的选中状态
     */
    @Override
    public boolean onTouchEvent(MotionEvent e) {
//      Log.e("yuc", "X:"+e.getX());

        switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //手指按下,重置一下9宫格
            isTouch=true;
            reset();
            break;
        case MotionEvent.ACTION_MOVE:

            x = e.getX();
            y = e.getY();

            //判断当前位置是否处于某个按钮之中
            for (int i = 0; i < btn_coord.length; i++) {
                float dx=x-btn_coord[i][0];
                float dy=y-btn_coord[i][1];

                if ((dx*dx)+(dy*dy)<=((btn_width*btn_width)/2)) {
//                  Log.e("yuc", "选中:"+(i+1));
                    if (!password.contains(i+1)) {
                        password.add(i+1);
                        checkBox_btns.get(i).setChecked(true);
                    }
                }
            }

            break;
        case MotionEvent.ACTION_UP:
            isTouch=false;
            x=0;
            y=0;
            StringBuffer sb = new StringBuffer("");
            for (Integer i : password) {
                sb.append(i);
            }
            //正确密码为空则不判断
            if (TextUtils.isEmpty(rightPwd)) {
                reset();
                if (onGetPwdListener!=null) {
                    onGetPwdListener.onSetting(sb.toString());
                }
            }else{
                //错误后,变红
                if (sb.toString().equals(rightPwd)) {
                    isError=false;
                }else{
                    isError=true;
                }

                if (!isError) {
                    if (onUnlockListener!=null) {
                        onUnlockListener.onSuccess();
                    }
                }else{
                    if (onUnlockListener!=null) {
                        onUnlockListener.onError();
                    }
                }

                for (CheckBox checkBox : checkBox_btns) {
                    checkBox.setEnabled(false);
                }
//          Toast.makeText(getContext(), "密码:"+password, 0).show();
                if (isError) {
                    Message msg=new Message();
                    msg.what=1;
                    handler.sendMessageDelayed(msg, 600);
                }else{
                    reset();
                }
            }

            break;
        }

        invalidate();

        return true;
    }

    private void reset() {
        //重置
        if (password.size()>0) {
            password.clear();
            isError=false;
            for (CheckBox cb : checkBox_btns) {
                cb.setEnabled(true);
                cb.setChecked(false);
            }
            invalidate();
        }
    }

}

好了,现在自定义控件做好了,是骡子是马拉出来溜溜吧:
在你想要添加手势控件的地方添加进来:

注意:这里的高度设置是没用的,因为在代码里已经控制高度和宽度一样
<com.yuc.common.view.UnlockView
        android:id="@+id/unlock"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        />

Activity里面设置相应监听:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        unlock = (UnlockView) findViewById(R.id.unlock);
        et = (EditText) findViewById(R.id.et);

        //设置验证密码时的监听
        unlock.setOnUnlockListener(new OnUnlockListener() {

            @Override
            public void onSuccess() {
                Toast.makeText(MainActivity.this, "密码正确", 0).show();
            }
            @Override
            public void onError() {
                Toast.makeText(MainActivity.this, "密码错误", 0).show();
            }
        });
        //设置捕捉密码时的监听
        unlock.setOnGetPwdListener(new OnGetPwdListener() {

            @Override
            public void onSetting(String pwd) {
                Toast.makeText(MainActivity.this, "密码:"+pwd, 0).show();
            }
        });
    }

运行起来看看效果吧:
我把正确密码设置为14789,画一个147896的图案,提升失败
这里写图片描述

附上源码地址:
https://github.com/yuchong123/GestureLock

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,下面是手势解锁的Demo实现过程: 1. 首先创建一个UIView的子类,作为手势解锁的主体视图,我们称之为`GestureLockView`。 2. 在`GestureLockView`中创建一个数组`circleArray`,用于存储手势解锁的圆。 ``` @property (nonatomic, strong) NSMutableArray *circleArray; ``` 3. 在`GestureLockView`的`layoutSubviews`方法中,创建9个圆,并加入到`circleArray`中。 ``` - (void)layoutSubviews { [super layoutSubviews]; CGFloat margin = (self.frame.size.width - 3 * kCircleSize) / 4.0; for (int i = 0; i < 9; i++) { CGFloat x = margin + (i % 3) * (margin + kCircleSize); CGFloat y = margin + (i / 3) * (margin + kCircleSize); CGRect frame = CGRectMake(x, y, kCircleSize, kCircleSize); GestureLockCircle *circle = [[GestureLockCircle alloc] initWithFrame:frame]; circle.tag = i + 1; [self addSubview:circle]; [self.circleArray addObject:circle]; } } ``` 4. 在`GestureLockView`中创建一个数组`selectedArray`,用于存储用户选择的圆。 ``` @property (nonatomic, strong) NSMutableArray *selectedArray; ``` 5. 在`GestureLockView`中实现手势识别的方法`touchesMoved:withEvent:`,通过判断触摸是否在圆内来确定用户选择的圆,并绘制用户选择的线条。 ``` - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; for (GestureLockCircle *circle in self.circleArray) { if (CGRectContainsPoint(circle.frame, point) && !circle.selected) { circle.selected = YES; [self.selectedArray addObject:circle]; break; } } self.currentPoint = point; [self setNeedsDisplay]; } ``` 6. 在`GestureLockView`中实现绘制方法`drawRect:`,根据用户选择的圆绘制线条。 ``` - (void)drawRect:(CGRect)rect { if (self.selectedArray.count == 0) { return; } UIBezierPath *path = [UIBezierPath bezierPath]; path.lineWidth = kLineWidth; path.lineJoinStyle = kCGLineJoinRound; path.lineCapStyle = kCGLineCapRound; [[UIColor whiteColor] set]; for (int i = 0; i < self.selectedArray.count; i++) { GestureLockCircle *circle = self.selectedArray[i]; if (i == 0) { [path moveToPoint:circle.center]; } else { [path addLineToPoint:circle.center]; } } [path addLineToPoint:self.currentPoint]; [path stroke]; } ``` 7. 在`GestureLockView`中实现手势结束的方法`touchesEnded:withEvent:`,判断用户手势是否正确,并通过代理方法通知外部。 ``` - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSMutableString *password = [NSMutableString string]; for (GestureLockCircle *circle in self.selectedArray) { [password appendFormat:@"%ld", circle.tag]; } BOOL success = [password isEqualToString:self.password]; if (success) { for (GestureLockCircle *circle in self.selectedArray) { circle.selected = NO; } [self.selectedArray removeAllObjects]; [self setNeedsDisplay]; if (self.delegate && [self.delegate respondsToSelector:@selector(gestureLockView:didCompleteWithPassword:)]) { [self.delegate gestureLockView:self didCompleteWithPassword:password]; } } else { for (GestureLockCircle *circle in self.selectedArray) { circle.selected = NO; circle.error = YES; } [self.selectedArray removeAllObjects]; [self setNeedsDisplay]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kErrorDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ for (GestureLockCircle *circle in self.circleArray) { circle.error = NO; } [self setNeedsDisplay]; }); } } ``` 8. 在外部创建`GestureLockView`实例,并设置代理方法,实现手势解锁的逻辑。 ``` - (void)viewDidLoad { [super viewDidLoad]; GestureLockView *lockView = [[GestureLockView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenWidth)]; lockView.center = self.view.center; lockView.delegate = self; lockView.password = @"123456789"; [self.view addSubview:lockView]; } #pragma mark - GestureLockViewDelegate - (void)gestureLockView:(GestureLockView *)lockView didCompleteWithPassword:(NSString *)password { NSLog(@"password: %@", password); } ``` 至此,手势解锁的Demo已经完成了,你可以尝试在模拟器或真机上运行它。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值