这个教程呢,并不是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的
private PointInfo[] mPoints = new PointInfo[16];//点信息
9改为16就变成16键了 当然25键也是可以的 不过不建议太密