Android 9格锁屏

这个教程呢,并不是up原创的,而是参考了网上的一篇素材 https://www.300168.com/yidong/show-1593.html

但是代码中存在一些bug,并做了一些改进和优化,(bug如下中间经过的键未能被选中)并没有轻视原楼主的意思,还是很棒的吐舌头

修改方法是只要在ACTION_MOVE里加这样一段代码就可以了(源代码待会儿我会贴出来的,大家不要觉得麻烦



第一个javaBean

/**
 * 锁屏中点的信息类
 */

public class PointInfo {

    private int mId; //点的id

    private int mNextId;//当前点指向的下一个点的id

    private boolean mSelected;//是否选中

    private int mDefaultX;//默认图片左上角的X坐标

    private int mDefaultY;//默认图片左上角Y的坐标

    private int mSelectedX; // 被选中时图片的左上角X坐标

    private int mSelectedY; // 被选中时图片的左上角Y坐标

    private int mRangeWidth;//范围

    private int mSelectedWidth;//选中显示的范围

    /**
     *  构造方法
     *  selectedWidth 选中时图片选中的范围
     *  rangeWidth 触摸能响应事件的范围 如果rangeWidth > selectedWith 相当于加了个padding
     */
    public PointInfo(int id, int defaultX, int defaultY, int selectedX,
                     int selectedY, int selectedWidth, int rangeWidth) {
        mId = id;
        mNextId = mId;
        mDefaultX = defaultX;
        mDefaultY = defaultY;
        mSelectedX = selectedX;
        mSelectedY = selectedY;
        mSelectedWidth = selectedWidth;//选中范围
        mRangeWidth = rangeWidth;
    }

    public void setNextId(int nextId) {
        mNextId = nextId;
    }

    public void setSelected(boolean selected) {
        mSelected = selected;
    }

    public int getId() {
        return mId;
    }

    public int getNextId() {
        return mNextId;
    }

    public boolean isSelected() {
        return mSelected;
    }

    public int getDefaultX() {
        return mDefaultX;
    }

    public int getDefaultY() {
        return mDefaultY;
    }

    public int getSelectedX() {
        return mSelectedX;
    }

    public int getSelectedY() {
        return mSelectedY;
    }

    /**
     * 是否有下一个id
     * @return
     */
    public boolean hasNextId() {
        return mNextId != mId;
    }

    /**
     * 得到中心X
     * @return
     */
    public int getCenterX(){
        return mSelectedX + mSelectedWidth/2;
    }

    /**
     * 得到中心Y
     * @return
     */
    public int getCenterY(){
        return mSelectedY + mSelectedWidth/2;
    }

    /**
     * 坐标(x,y)是否在当前点的范围内
     * @return
     */
    public boolean isInMyPlace(int x, int y) {
        boolean inX = x >= getCenterX() - mRangeWidth/2 && x <= getCenterX() + mRangeWidth/2;
        boolean inY = y >= getCenterY() - mRangeWidth/2 && y <= getCenterY() + mRangeWidth/2;
        return (inX && inY);//只有两个都为真时才为真
    }

}

第二个主类

package com.example.ninepointtask.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.example.ninepointtask.R;
import com.example.ninepointtask.bean.PointInfo;
import com.example.ninepointtask.util.DensityUtil;

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

/**
 * Created by xiaohan on 2018/4/25.
 */

public class NinePointView extends View {

    private static final String TAG = "NinePointView";

    private Paint mLinePaint = new Paint();//线画笔
    private Bitmap mDefaultBitmap;
    private int mDefaultBitmapRadius;//宽为20dp
    private Bitmap mSelectedBitmap;//选中的图片
    private int mSelectedBitmapDiameter;//选中图片的直径
    private int mSelectedBitmapRadius;//选中图片的半径
    //private Bitmap mFailBitmap;//失败图
    private int mPointRange;//点的半径

    private PointInfo[] mPoints = new PointInfo[25];//点信息

    private int mWidth, mHeight;//用于记录view的宽高
    private int mMoveX, mMoveY;//获取用户的移动坐标
    private int mStartX, mStartY;//开始坐标
    private boolean mIsUp = false;//用户是否抬起

    private List<Integer> mPasswordArr = new ArrayList<>();//存放用户输入的密码
    private int mColumnNum;

    public NinePointView(Context context) {
        super(context);
        init();
    }

    public NinePointView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public NinePointView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始化
     */
    private void init() {

        mColumnNum = (int) Math.sqrt(mPoints.length);

        mDefaultBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lock);//默认图形
        mDefaultBitmapRadius = DensityUtil.dipToPx(40);//默认图形的大小半径
        mSelectedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_lock_area);//选中的图片
        mSelectedBitmapDiameter = DensityUtil.dipToPx(40);//选中的图形直径为40
        mPointRange = DensityUtil.dipToPx(55);//直径给个50吧
        mSelectedBitmapRadius = mSelectedBitmapDiameter / 2;//选中时图片的半径

        Matrix matrix = new Matrix();
        matrix.postScale(mDefaultBitmapRadius * 2 / (float) mDefaultBitmap.getWidth(), mDefaultBitmapRadius * 2 / (float) mDefaultBitmap.getHeight());//先压缩一下
        mDefaultBitmap = Bitmap.createBitmap(mDefaultBitmap, 0, 0, mDefaultBitmap.getWidth(),
                mDefaultBitmap.getHeight(), matrix, true);//压缩一下默认图片

        Matrix matrix1 = new Matrix();
        matrix1.postScale(mSelectedBitmapDiameter / (float) mSelectedBitmap.getWidth(), mSelectedBitmapDiameter / (float) mSelectedBitmap.getHeight());//先压缩一下
        mSelectedBitmap = Bitmap.createBitmap(mSelectedBitmap, 0, 0, mSelectedBitmap.getWidth(), mSelectedBitmap.getHeight(), matrix1, true);

        initPaint();
    }

    /**
     * 初始化一下画笔
     */
    private void initPaint() {
        mLinePaint.setColor(Color.GRAY);//线性画笔的颜色
        mLinePaint.setStrokeWidth(DensityUtil.dipToPx(5));//线的宽度默认为5dp
        mLinePaint.setAntiAlias(true);
        mLinePaint.setStrokeCap(Paint.Cap.ROUND);//圆帽
    }

    /**
     * 初始化点数组
     */
    private void initPoint() {

        int len = mPoints.length;
        boolean HGreaterThanW = (mHeight > mWidth);//高是否大于宽
        int selectedSpacing = (Math.min(mWidth, mHeight) - mSelectedBitmapDiameter * mColumnNum) / (mColumnNum + 1);//分割宽度为宽度和高度中小的值减去6个圆的大小除以列数加1
        mPointRange = Math.min(mPointRange, mSelectedBitmapDiameter + selectedSpacing);////防止点范围越界

        int selectedX = HGreaterThanW ? selectedSpacing : (mWidth - mHeight) / 2 + selectedSpacing;//图片的左上角X点坐标
        int selectedY = HGreaterThanW ? mHeight - mWidth + selectedSpacing : selectedSpacing;//左上角Y坐标

        int defaultX = selectedX + mSelectedBitmapRadius - mDefaultBitmapRadius;//默认X
        int defaultY = selectedY + mSelectedBitmapRadius - mDefaultBitmapRadius;//选中的中心点

        for (int i = 0; i < len; i++) {
            if ((i % mColumnNum) == 0 && i != 0) {
                selectedX = HGreaterThanW ? selectedSpacing : (mWidth - mHeight) / 2 + selectedSpacing; //重归开头
                selectedY += mSelectedBitmapDiameter + selectedSpacing;//向下偏移
                defaultX = selectedX + mSelectedBitmapRadius - mDefaultBitmapRadius;
                defaultY += mSelectedBitmapDiameter + selectedSpacing;//向下偏移
            }
            mPoints[i] = new PointInfo(i, defaultX, defaultY, selectedX, selectedY, mSelectedBitmapDiameter, mPointRange);//初始化
            selectedX += mSelectedBitmapDiameter + selectedSpacing;//向右偏
            defaultX += mSelectedBitmapDiameter + selectedSpacing;//向右偏
        }

    }

    /**
     * 用户的触摸事件
     * 这个DOWN和MOVE、UP是成对的,如果没从UP释放,就不会再获得DOWN;
     * 而获得DOWN时,一定要确认消费该事件,否则MOVE和UP不会被这个VIEW的onTouchEvent接收
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mIsUp) {
            finishDraw();
        }
        handleEvent(event);
        return true;
    }

    /**
     * 结束绘制  重置一下
     */
    private void finishDraw() {
        if (mIsUp) {
            for (PointInfo pointInfo : mPoints) {
                pointInfo.setSelected(false);//
                pointInfo.setNextId(pointInfo.getId());
            }
            mPasswordArr.clear();//清除已存的密码
            mIsUp = false;
            invalidate();//重绘一下
        }
    }

    /**
     * 处理事件
     *
     * @param event
     */
    private void handleEvent(MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:

                int downX = (int) event.getX();
                int downY = (int) event.getY();
                for (PointInfo pointInfo : mPoints) {
                    if (pointInfo.isInMyPlace(downX, downY)) {
                        pointInfo.setSelected(true);//选中
                        mStartX = pointInfo.getCenterX();
                        mStartY = pointInfo.getCenterY();
                        mPasswordArr.add(pointInfo.getId());//将id加进去
                        break;//找到了就不用再找了
                    }
                }
                invalidate();//重绘
                break;

            case MotionEvent.ACTION_MOVE:
                mMoveX = (int) event.getX();
                mMoveY = (int) event.getY();
                handlePoint();
                invalidate();//重绘
                break;

            case MotionEvent.ACTION_UP:
                mStartX = mStartY = mMoveX = mMoveY = 0;
                mIsUp = true;
                invalidate();//重绘

                if (mOnEndListener != null && mPasswordArr != null && mPasswordArr.size() != 0)
                    mOnEndListener.onFinish(mPasswordArr);//将数组传递出去

                break;

        }

    }

    /**
     * 处理一下这些点
     */
    private void handlePoint() {

        for (PointInfo pointInfo : mPoints) {
            if (pointInfo.isInMyPlace(mMoveX, mMoveY) && !pointInfo.isSelected()) {//如果在范围之内 并且没选中
                pointInfo.setSelected(true);//设为选中
                mStartX = pointInfo.getCenterX();
                mStartY = pointInfo.getCenterY();
                if (mPasswordArr.size() != 0) {
                    int preId = mPasswordArr.get(mPasswordArr.size() - 1);//前一个点的id

                    int firstRax = preId / mColumnNum;//求出行数
                    int firstColumn = preId % mColumnNum;//求出列数
                    int secondRax = pointInfo.getId() / mColumnNum;//当前经过点的行数
                    int secondColumn = pointInfo.getId() % mColumnNum;//当前经过点的列数

                    Log.d(TAG, "handlePoint: firstRaw  " + firstRax + " firstColumn " + firstColumn);
                    Log.d(TAG, "handlePoint: secondRaw " + secondRax + " secondColumn  " + secondColumn);

                    if (firstRax == secondRax) {//如果在同一行上
                        for (int i = firstColumn + 1; i < secondColumn; i++)  //如果第二个点在第一个点右边
                            preId = connect(preId, firstRax * mColumnNum + i);
                        for (int i = firstColumn - 1; i > secondColumn; i--) //如果第二个点在第一个点左边
                            preId = connect(preId, firstRax * mColumnNum + i);
                    } else if (firstColumn == secondColumn) {//如果在同一列上
                        for (int i = firstRax + 1; i < secondRax; i++) //如果第一个点在第二个点上面
                            preId = connect(preId, i * mColumnNum + firstColumn);
                        for (int i = firstRax - 1; i > secondRax; i--) //如果第一个点在第二个点下面
                            preId = connect(preId, i * mColumnNum + firstColumn);
                    } else if (Math.abs(firstColumn - secondColumn) == Math.abs(firstRax - secondRax)) {//如果在同一对角线上
                        int raxOffset = (firstRax > secondRax) ? -1 : 1;
                        int indexRax = firstRax + raxOffset;//临时行
                        for (int i = firstColumn + 1; i < secondColumn; i++) { //如果第二个点在第一个点右边
                            preId = connect(preId, indexRax * mColumnNum + i);//再连一下
                            indexRax = firstRax + raxOffset;//行数也跟着偏移
                        }
                        for (int i = firstColumn - 1; i > secondColumn; i--) { //如果第二个点在第一个点左边
                            preId = connect(preId, indexRax * mColumnNum + i);//再连一下
                            indexRax = firstRax + raxOffset;//行数也跟着偏移
                        }
                    }
                    mPoints[preId].setNextId(pointInfo.getId());
                }
                mPasswordArr.add(pointInfo.getId());//将点id添加进去
                break;//返回
            }
        }

    }

    /**
     * 将两个点相连返回下一个点
     */
    private int connect(int preId, int nextId) {
        Log.d(TAG, "connect: preId " + preId + " nextId " + nextId);
        if (!mPoints[nextId].isSelected()) {//如果没被选中
            mPoints[preId].setNextId(nextId);//下一个id
            mPasswordArr.add(nextId);//把这个点加进来
            mPoints[nextId].setSelected(true);//设为选中
            return nextId;
        }
        return preId;//把第一个id返回去
    }

    /**
     * 绘制
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        if (mMoveX != 0 && mMoveY != 0 && mStartX != 0 && mStartY != 0)// 绘制当前活动的线段
            canvas.drawLine(mStartX, mStartY, mMoveX, mMoveY, mLinePaint);//绘制线
        drawNinePoint(canvas);
    }

    /**
     * 绘制那几个点
     *
     * @param canvas
     */
    private void drawNinePoint(Canvas canvas) {

        for (PointInfo pointInfo : mPoints) { //先把用户的画出的线绘制好
            if (pointInfo.hasNextId()) { //如果有下一个
                int nextId = pointInfo.getNextId();//下一个的id
                canvas.drawLine(pointInfo.getCenterX(), pointInfo.getCenterY(),
                        mPoints[nextId].getCenterX(), mPoints[nextId].getCenterY(), mLinePaint);
            }
        }

        for (PointInfo pointInfo : mPoints) {
            if (pointInfo.isSelected()) {//如果被选中
                canvas.drawBitmap(mSelectedBitmap, pointInfo.getSelectedX(),
                        pointInfo.getSelectedY(), mLinePaint);//绘制一下默认图片
            } else {
                canvas.drawBitmap(mDefaultBitmap, pointInfo.getDefaultX(),
                        pointInfo.getDefaultY(), mLinePaint);//绘制一下默认图片
            }
        }

    }

    /**
     * 在这里得到view的宽高
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
        super.onSizeChanged(w, h, oldW, oldH);
        Log.d(TAG, "onSizeChanged: " + " w " + w + " h " + h + " oldW " + oldW + " oldH " + oldH);
        mWidth = w;
        mHeight = h;//得到view宽高
        initPoint();//当获得宽高好得到一下
    }

    //绘制结束监听
    public interface OnEndListener {
        void onFinish(List<Integer> passwordArr);//将密码触底出去
    }

    private OnEndListener mOnEndListener;

    /**
     * 设置结束的监听
     */
    public void setOnEndListener(OnEndListener onEndListener) {
        mOnEndListener = onEndListener;
    }
}

第三个一个小的工具类

package com.example.ninepointtask.util;

import com.example.ninepointtask.base.MyApplication;

/**
 * Created by xiaohan on 2018/4/25.
 */

public class DensityUtil {

    /**
     * 工具类 构造器私有
     */
    private DensityUtil() {
    }

    public static int dipToPx(float dpValue) {
        float scale = MyApplication.getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public static int pxToDp(float pxValue) {
        float scale = MyApplication.getContext().getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

}


因为代码写的比较灵活 只要把NinePointView的

扫描二维码关注公众号,回复: 104024 查看本文章
private PointInfo[] mPoints = new PointInfo[16];//点信息

9改为16就变成16键了 当然25键也是可以的 不过不建议太密

猜你喜欢

转载自blog.csdn.net/bouquet12138/article/details/80105452