• Android开发-实现屏幕解锁功能
  • 三公主 发表于 2016/3/9 12:35:00 | 分类标签: Android开发 屏幕锁
  • 这两天研究了View类,自己实现了一个九点连线锁,把心得分享下。

    下面是实现截图:
    我的思路是,首先绘制每个点,就是中间的小蓝点,当手指触摸到某个点的范围内时(就是当ACTION_DOWN发生在某个范围内时),绘制灰色大圆;当手指移动时(ACTION_MOVE),绘制每个点之间的线段,和最后一个点到手指当前位置的线段;当手指抬起时,把所有相关的坐标值设为初值0,并设置标志onUp为true,来等待用户下次画线。

    我固定的给每个点设置了一个ID,如图:
    然后设置了个全局的StringBuffer lockString ,每当用户滑动到某个点的范围内时,就向 lockString 的末尾添加这个点的ID,最终生成的String就可以保存在手机里,日后验证时就拿这个String验证。

    当然里面还有很多细节,下面是整个NinePointLineView.java的源代码,通过注释应该就明白了(文章最后有整个程序的源码的下载链接):
    package org.demo.custon_view;

    import org.demo.utils.MLog;

    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Paint.Cap;
    import android.graphics.Typeface;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;

    public class NinePointLineView extends View {

    Paint linePaint = new Paint();

    Paint whiteLinePaint = new Paint();

    Paint textPaint = new Paint();

    // 由于两个图片都是正方形,所以获取一个长度就行了
    Bitmap defaultBitmap = BitmapFactory.decodeResource(getResources(),
    R.drawable.lock);
    int defaultBitmapRadius = defaultBitmap.getWidth() / 2;

    // 初始化被选中图片的直径、半径
    Bitmap selectedBitmap = BitmapFactory.decodeResource(getResources(),
    R.drawable.indicator_lock_area);
    int selectedBitmapDiameter = selectedBitmap.getWidth();
    int selectedBitmapRadius = selectedBitmapDiameter / 2;

    // 定义好9个点的数组
    PointInfo[] points = new PointInfo[9];

    // 相应ACTION_DOWN的那个点
    PointInfo startPoint = null;

    // 屏幕的宽高
    int width, height;

    // 当ACTION_MOVE时获取的X,Y坐标
    int moveX, moveY;

    // 是否发生ACTION_UP
    boolean isUp = false;

    // 最终生成的用户锁序列
    StringBuffer lockString = new StringBuffer();

    public NinePointLineView(Context context) {
    super(context);
    this.setBackgroundColor(Color.WHITE);
    initPaint();
    }

    public NinePointLineView(Context context, AttributeSet attrs) {
    super(context, attrs);
    this.setBackgroundColor(Color.WHITE);
    initPaint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    MLog.i("onMeasure");
    // 初始化屏幕大小
    width = getWidth();
    height = getHeight();
    if (width != 0 && height != 0) {
    initPoints(points);
    }
    MLog.i("width、height = " + width + "、" + height);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
    int bottom) {
    MLog.i("onLayout");
    super.onLayout(changed, left, top, right, bottom);
    }

    private int startX = 0, startY = 0;

    @Override
    protected void onDraw(Canvas canvas) {

    canvas.drawText("用户的滑动顺序:" + lockString, 0, 40, textPaint);

    if (moveX != 0 && moveY != 0 && startX != 0 && startY != 0) {
    // 绘制当前活动的线段
    drawLine(canvas, startX, startY, moveX, moveY);
    }

    drawNinePoint(canvas);

    super.onDraw(canvas);
    }

    // 记住,这个DOWN和MOVE、UP是成对的,如果没从UP释放,就不会再获得DOWN;
    // 而获得DOWN时,一定要确认消费该事件,否则MOVE和UP不会被这个View的onTouchEvent接收
    @Override
    public boolean onTouchEvent(MotionEvent event) {

    boolean flag = true;

    if (isUp) {// 如果已滑完,重置每个点的属性和lockString

    finishDraw();

    // 当UP后,要返回false,把事件释放给系统,否则无法获得Down事件
    flag = false;

    } else {// 没滑完,则继续绘制

    handlingEvent(event);

    // 这里要返回true,代表该View消耗此事件,否则不会收到MOVE和UP事件
    flag = true;

    }
    return flag;
    }

    private void handlingEvent(MotionEvent event) {
    switch (event.getAction()) {
    case MotionEvent.ACTION_MOVE:
    moveX = (int) event.getX();
    moveY = (int) event.getY();
    MLog.i("onMove:" + moveX + "、" + moveY);
    for (PointInfo temp : points) {
    if (temp.isInMyPlace(moveX, moveY) && temp.isNotSelected()) {
    temp.setSelected(true);
    startX = temp.getCenterX();
    startY = temp.getCenterY();
    int len = lockString.length();
    if (len != 0) {
    int preId = lockString.charAt(len - 1) - 48;
    points[preId].setNextId(temp.getId());
    }
    lockString.append(temp.getId());
    break;
    }
    }

    invalidate(0, height - width, width, height);
    break;

    case MotionEvent.ACTION_DOWN:
    int downX = (int) event.getX();
    int downY = (int) event.getY();
    MLog.i("onDown:" + downX + "、" + downY);
    for (PointInfo temp : points) {
    if (temp.isInMyPlace(downX, downY)) {
    temp.setSelected(true);
    startPoint = temp;
    startX = temp.getCenterX();
    startY = temp.getCenterY();
    lockString.append(temp.getId());
    break;
    }
    }
    invalidate(0, height - width, width, height);
    break;

    case MotionEvent.ACTION_UP:
    MLog.i("onUp");
    startX = startY = moveX = moveY = 0;
    isUp = true;
    invalidate();
    break;
    default:
    MLog.i("收到其他事件!!");
    break;
    }
    }

    private void finishDraw() {
    for (PointInfo temp : points) {
    temp.setSelected(false);
    temp.setNextId(temp.getId());
    }
    lockString.delete(0, lockString.length());
    isUp = false;
    invalidate();
    }

    private void initPoints(PointInfo[] points) {

    int len = points.length;

    int seletedSpacing = (width - selectedBitmapDiameter * 3) / 4;

    // 被选择时显示图片的左上角坐标
    int seletedX = seletedSpacing;
    int seletedY = height - width + seletedSpacing;

    // 没被选时图片的左上角坐标
    int defaultX = seletedX + selectedBitmapRadius - defaultBitmapRadius;
    int defaultY = seletedY + selectedBitmapRadius - defaultBitmapRadius;

    // 绘制好每个点
    for (int i = 0; i < len; i++) {
    if (i == 3 || i == 6) {
    seletedX = seletedSpacing;
    seletedY += selectedBitmapDiameter + seletedSpacing;

    defaultX = seletedX + selectedBitmapRadius
    - defaultBitmapRadius;
    defaultY += selectedBitmapDiameter + seletedSpacing;

    }
    points[i] = new PointInfo(i, defaultX, defaultY, seletedX, seletedY);

    seletedX += selectedBitmapDiameter + seletedSpacing;
    defaultX += selectedBitmapDiameter + seletedSpacing;

    }
    }

    private void initPaint() {
    initLinePaint(linePaint);
    initTextPaint(textPaint);
    initWhiteLinePaint(whiteLinePaint);
    }

    /**
    * 初始化文本画笔
    * @param paint
    */
    private void initTextPaint(Paint paint) {
    textPaint.setTextSize(30);
    textPaint.setAntiAlias(true);
    textPaint.setTypeface(Typeface.MONOSPACE);
    }

    /**
    * 初始化黑线画笔
    *
    * @param paint
    */
    private void initLinePaint(Paint paint) {
    paint.setColor(Color.GRAY);
    paint.setStrokeWidth(defaultBitmap.getWidth());
    paint.setAntiAlias(true);
    paint.setStrokeCap(Cap.ROUND);
    }

    /**
    * 初始化白线画笔
    *
    * @param paint
    */
    private void initWhiteLinePaint(Paint paint) {
    paint.setColor(Color.WHITE);
    paint.setStrokeWidth(defaultBitmap.getWidth() - 5);
    paint.setAntiAlias(true);
    paint.setStrokeCap(Cap.ROUND);

    }
    /**
    * 绘制已完成的部分
    *
    * @param canvas
    */
    private void drawNinePoint(Canvas canvas) {

    if (startPoint != null) {
    drawEachLine(canvas, startPoint);
    }

    // 绘制每个点的图片
    for (PointInfo pointInfo : points) {
    if (pointInfo.isSelected()) {// 绘制大圈
    canvas.drawBitmap(selectedBitmap, pointInfo.getSeletedX(),
    pointInfo.getSeletedY(), null);
    }
    // 绘制点
    canvas.drawBitmap(defaultBitmap, pointInfo.getDefaultX(),
    pointInfo.getDefaultY(), null);
    }

    }

    /**
    * 递归绘制每两个点之间的线段
    *
    * @param canvas
    * @param point
    */
    private void drawEachLine(Canvas canvas, PointInfo point) {
    if (point.hasNextId()) {
    int n = point.getNextId();
    drawLine(canvas, point.getCenterX(), point.getCenterY(),
    points[n].getCenterX(), points[n].getCenterY());
    // 递归
    drawEachLine(canvas, points[n]);
    }
    }
    /**
    * 先绘制黑线,再在上面绘制白线,达到黑边白线的效果
    *
    * @param canvas
    * @param startX
    * @param startY
    * @param stopX
    * @param stopY
    */
    private void drawLine(Canvas canvas, float startX, float startY,
    float stopX, float stopY) {
    canvas.drawLine(startX, startY, stopX, stopY, linePaint);
    canvas.drawLine(startX, startY, stopX, stopY, whiteLinePaint);
    }
    /**
    * 用来表示一个点
    *
    * @author zkwlx
    *
    */
    private class PointInfo {
    // 一个点的ID
    private int id;
    // 当前点所指向的下一个点的ID,当没有时为自己ID
    private int nextId;
    // 是否被选中
    private boolean selected;
    // 默认时图片的左上角X坐标
    private int defaultX;
    // 默认时图片的左上角Y坐标
    private int defaultY;
    // 被选中时图片的左上角X坐标
    private int seletedX;
    // 被选中时图片的左上角Y坐标
    private int seletedY;
    public PointInfo(int id, int defaultX, int defaultY, int seletedX,
    int seletedY) {
    this.id = id;
    this.nextId = id;
    this.defaultX = defaultX;
    this.defaultY = defaultY;
    this.seletedX = seletedX;
    this.seletedY = seletedY;
    }
    public boolean isSelected() {
    return selected;
    }
    public boolean isNotSelected() {
    return !isSelected();
    }
    public void setSelected(boolean selected) {
    this.selected = selected;
    }
    public int getId() {
    return id;
    }
    public int getDefaultX() {
    return defaultX;
    }

    public int getDefaultY() {
    return defaultY;
    }
    public int getSeletedX() {
    return seletedX;
    }
    public int getSeletedY() {
    return seletedY;
    }

    public int getCenterX() {
    return seletedX + selectedBitmapRadius;
    }

    public int getCenterY() {
    return seletedY + selectedBitmapRadius;
    }

    public boolean hasNextId() {
    return nextId != id;
    }

    public int getNextId() {
    return nextId;
    }

    public void setNextId(int nextId) {
    this.nextId = nextId;
    }

    /**
    * 坐标(x,y)是否在当前点的范围内
    *
    * @param x
    * @param y
    * @return
    */
    public boolean isInMyPlace(int x, int y) {
    boolean inX = x > seletedX
    && x < (seletedX + selectedBitmapDiameter);
    boolean inY = y > seletedY
    && y < (seletedY + selectedBitmapDiameter);

    return (inX && inY);
    }
    }
    }
    其实这个实现有个不完善的地方,就是表示用户整个滑动的顺序,我看一般的android手机上都是用小箭头代表,那个我想了好久也没想出来怎么实现,就自己发明了个方法,就是图中那种一层一层覆盖的方法,不过效果没有那个箭头好,嘿嘿。
  • 请您注意

    ·自觉遵守:爱国、守法、自律、真实、文明的原则

    ·尊重网上道德,遵守《全国人大常委会关于维护互联网安全的决定》及中华人民共和国其他各项有关法律法规

    ·严禁发表危害国家安全,破坏民族团结、国家宗教政策和社会稳定,含侮辱、诽谤、教唆、淫秽等内容的作品

    ·承担一切因您的行为而直接或间接导致的民事或刑事法律责任

    ·您在编程中国社区新闻评论发表的作品,本网站有权在网站内保留、转载、引用或者删除

    ·参与本评论即表明您已经阅读并接受上述条款

  • 感谢本文作者
  • 作者头像
  • 昵称:三公主
  • 加入时间:2013/7/2 0:00:00
  • TA的签名
  • 这家伙很懒,虾米都没写
  • +进入TA的空间
  • 以下内容也很赞哦
分享按钮