这个教程呢,并不是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键也是可以的 不过不建议太密