android自定义view之九宫格解锁

android自定义view之九宫格解锁

更多细节请看源码
https://github.com/que123567/lockview

  • 1. 定义一个类作为九宫格的格子

    包含坐标和索引(用来记录密码)
    point含三种状态分别对应三种不同情况下点的图片样式

package lockview;

/**
 * Created by smaug on 2017/5/11.
 */

public class Point {
    public float x;
    public float y;
    private int index;

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public static final int STATU_NORNAL = 0;
    public static final int STATU_PRESSED = 1;
    public static final int STATU_ERROR = 2;
    public int state;


    public void setState(int state) {
        this.state = state;
    }

    public int getState() {
        return state;
    }

    public Point() {super();}

    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }
}
  • 2.新建类LockView继承自View
    重写onDraw()方法
    • 初始化点的时候根据获取当前屏幕的宽高,比对来确定当前状态是横屏还是竖屏
    • 从资源中获取Bitmap,将点设置成图像。
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
            initPoints();
            initPaint();    
    }

  private void drawPoints(Canvas canvas) {
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                Point point = points[i][j];
                if (point.getState() == Point.STATU_NORNAL) {
                    //获取当前点的坐标,减去图片的半径得到left和top的坐标点
                    canvas.drawBitmap(point_normal, point.x - br, point.y - br, null);
                } else if (point.getState() == Point.STATU_PRESSED) {
                    canvas.drawBitmap(point_pressed, point.x - br, point.y - br, null);

                } else if (point.getState() == Point.STATU_ERROR) {
                    canvas.drawBitmap(point_error, point.x - br, point.y - br, null);
                }
            }
        }
    }

public class LockView extends View {

 private void initPoints() {
        width = getWidth();
        height = getHeight();
        if (height > width) {//竖屏
            offsetY = (height - width) / 2;
            height = width;
        } else { //横屏
            offsetX = (height - width) / 2;
            width = height;
        }

        /**
         *     定义9个点的坐标
         */
        points = new Point[row][colun];
        int index = 1;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < colun; j++) {
                points[i][j] = new Point(offsetX + (width / (row + 1)) * (i + 1), offsetY +
                        (width / (colun + 1)) * (j + 1));
                points[i][j].setIndex(index);
                index++;
            }
        }

        /**
         * 从资源中获取Bitmap
         */
        point_normal = BitmapFactory.decodeResource(getResources(), R.drawable.point_normal);
        point_pressed = BitmapFactory.decodeResource(getResources(), R.drawable.point_pressed);
        point_error = BitmapFactory.decodeResource(getResources(), R.drawable.point_error);

        br = point_normal.getWidth() / 2;
    }
}

显示结果如下图

九宫格

  • 3.重写onTouchEvent
  case MotionEvent.ACTION_DOWN:
                reset();//先清空之前绘制的状态
                checkedPoint = checkPoint(eventX, eventY, br);
                if (checkedPoint != null) {
                    isSelecte = true;
                    checkedPoint.setState(Point.STATU_PRESSED);
                }
                break;

checkPoint()通过计算坐标距离判断当前手指坐标是否在point图片上

  private Point checkPoint(float eventX, float eventY, float br) {
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                Point point = points[i][j];
                double distance = getDistance(point.x, point.y, eventX, eventY);
                if (distance < br) {
                    return point;
                }
            }
        }
        return null;
    }

getDistance()利用勾股定理计算坐标距离

private double getDistance(float x, float y, float eventX, float eventY) {
        return Math.sqrt(Math.abs(x - eventX) * Math.abs(x - eventX) + Math.abs(y - eventY) *
                Math.abs(y - eventY));
    }

这个时候手指接触都某个点,某个点的状态就会从normal转换为pressed,其对应的图片也会改变。

  • 4.添加点与点之间的连线
    LockView中定义一个Point类的List —>pointList 记录用户按下的点

  • 当pointList为空,即用户未按下任一点的时候不绘制线段,其余时刻都绘制

  • 绘制线段调用canvas的drawLine()方法,传入起始点x,y坐标与目标点x,y坐标与一个自定义的paint画笔绘制线段

 private void drawLine(Canvas canvas) {
        if (pointList.size() > 0) {
            Point a = pointList.get(0);
            for (int i = 1; i < pointList.size(); i++) {
                Point b = pointList.get(i);
                canvas.drawLine(a.x, a.y, b.x, b.y, paint);
                a = b;
            }
            if (!moveOnPoint) { //
                canvas.drawLine(a.x, a.y, eventX, eventY, paint);
            }
        }
    }

效果如图所示

九宫格连线

  • 5.自动连接中间点

在九宫格中,如果用户跳开中间点只连接了头尾两个点,按照规则应该把中间点自动连上。

即在ACTION_MOVE过程中始终判断中间点是否被连入,而中间点的判断方式为首尾点坐标/2.

case MotionEvent.ACTION_MOVE:
    if (isSelecte) {
       checkedPoint = checkPoint(eventX, eventY, br);
        if (checkedPoint != null) {
             if (!pointList.contains(checkedPoint)) {
                   middlePoint = checkPoint((checkedPoint.x + lastPoint.x) / 2, (checkedPoint.y + lastPoint.y) / 2, br);
     if (middlePoint != null) {
                middlePoint.setState(Point.STATU_PRESSED);
                        }
                 }
            checkedPoint.setState(Point.STATU_PRESSED);
              moveOnPoint = true;
           } else {
            moveOnPoint = false;
        }
       }
     break;
  • 6.此外,在绘制过程中会记录密码,在使用该LockView时可设置setOnLockSuccessed监听来判断,其中带有参数:正确密码-passward

    对应的,连接数少于5判定为连接失败,也有对应的监听事件。

 if (pointList != null) {
                if (pointList.size() == 1) {
                    reset();//重置
                } else if (pointList.size() < 5) {//个数不足
                    errorPoint();//绘制错误的图案
                    if (onLockChangeListener != null) { //失败
                        onLockChangeListener.setOnLockError();
                    }
                } else if (pointList.size() >= 5) { //九宫格连接数应大于等于5
                    StringBuilder passward = new StringBuilder();
                    for (int i = 0; i < pointList.size(); i++) {
                        int tmpIndex = pointList.get(i).getIndex();
                        passward.append(tmpIndex + "");
                    }
                    if (onLockChangeListener != null)//成功
                        onLockChangeListener.setOnLockSuccessed(passward);
                }
            }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TypeArray 是 Android 中的一个特殊的资源类型,用于在 XML 中声明自定义 View 的属性。使用 TypeArray 可以方便地在 XML 布局中指定 View 的属性,而不需要在 Java 代码中进行硬编码。 使用 TypeArray 的步骤如下: 1. 在 res/values/attrs.xml 文件中定义定义 View 的属性。 ```xml <resources> <declare-styleable name="MyCustomView"> <attr name="customAttr1" format="integer" /> <attr name="customAttr2" format="string" /> <attr name="customAttr3" format="boolean" /> </declare-styleable> </resources> ``` 2. 在自定义 View 的构造函数中获取 TypedArray 对象,并从中获取属性值。 ```java public class MyCustomView extends View { private int customAttr1; private String customAttr2; private boolean customAttr3; public MyCustomView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView); customAttr1 = a.getInt(R.styleable.MyCustomView_customAttr1, 0); customAttr2 = a.getString(R.styleable.MyCustomView_customAttr2); customAttr3 = a.getBoolean(R.styleable.MyCustomView_customAttr3, false); a.recycle(); } } ``` 在上面的代码中,`context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)` 用于获取 TypedArray 对象,`R.styleable.MyCustomView` 是在 attrs.xml 文件中定义的自定义属性集合,`a.getInt()`、`a.getString()`、`a.getBoolean()` 用于从 TypedArray 对象中获取属性值,最后需要调用 `a.recycle()` 来回收 TypedArray 对象。 3. 在 XML 布局中使用自定义 View,并设置属性值。 ```xml <com.example.MyCustomView android:layout_width="match_parent" android:layout_height="wrap_content" app:customAttr1="123" app:customAttr2="hello" app:customAttr3="true" /> ``` 在上面的代码中,`app:customAttr1`、`app:customAttr2`、`app:customAttr3` 是在 attrs.xml 文件中定义的自定义属性名,可以在 XML 布局中使用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值