九宫格安全手势锁

这些东西是从网上搜罗的,自己整理了然后以后自己用着方便,东西可以直接用,demo下载

效果图就不传了,直接运行就能看效果,最好还是下载下来看一下,博客上可能有漏说的,开发工具eclipse

直接上代码,代码中有解释

/**
 * 九宫格手势锁组件,可以通过xml来设置的有连线的颜色, 每个格子的背景图和选中选错的图片 其他的设置可以通过代码来设置(看代码中的注释)
 * 在使用的时候必须要设置setOnPatternListener(OnPatternListener onPatternListener),
 * 实现OnPatternListener 的 onPatternDetected方法,其中此方法中传入的参数既是使用者选中的单元格
 * 
 * <pre>
 * <com.luckchoudog.ui.LockPatternView
 *         xmlns:app="http://schemas.android.com/apk/res-auto"
 *         android:id="@+id/gesturepwd_create_lockview"
 *         android:layout_width="wrap_content"
 *         android:layout_height="wrap_content"
 *         android:layout_gravity="center_horizontal"
 *         app:Demo_circleNormal="@drawable/circleNormal_bg" >
 * </com.luckchoudog.ui.LockPatternView>
 * </pre>
 * 
 * 此类有用到attr文件中的相关属性
 * 
 * @author luckchoudog
 */
@SuppressLint("NewApi")
public class LockPatternView extends View {
	/**
	 * 九宫格为3 十六宫格为4 ;这个值直接关系显示出的效果
	 */
	private static int number = 3;
	/**
	 * 此view的所占区域,区域宽高都取设定宽高的最小值设置View区域(默认)
	 */
	private static final int ASPECT_SQUARE = 0; //
	/**
	 * 此view的所占区域,以宽为标准,高取设定宽高的最小值设置View区域
	 */
	private static final int ASPECT_LOCK_WIDTH = 1;
	/**
	 * 此view的所占区域,以高为标准,宽取设定宽高的最小值设置View区域
	 */
	private static final int ASPECT_LOCK_HEIGHT = 2;
	/**
	 * 动画模式时,两点之间连接的时间(毫秒),此值越小速度越快
	 */
	private static final int MILLIS_PER_CIRCLE_ANIMATING = 600;
	/**
	 * 使用者自己实现的OnPatternListener设置到此View
	 */
	private OnPatternListener mOnPatternListener;
	/**
	 * 每次操作中所有已经选中的单元格的list
	 */
	private ArrayList<Cell> mPattern = new ArrayList<Cell>(number * number);
	/**
	 * 目前正在绘制的图案圆的查找表,例如3x3模式
	 */
	private boolean[][] mPatternDrawLookup = new boolean[number][number];
	private float mInProgressX = -1;
	private float mInProgressY = -1;
	private long mAnimatingPeriodStart;
	/**
	 * 默认的单元图片模式
	 */
	private DisplayMode mPatternDisplayMode = DisplayMode.Correct;
	/**
	 * 是否允许输入,是否允许绘制,默认为true允许使用
	 */
	private boolean mInputEnabled = true;
	/**
	 * 是否显示滑动轨迹 true显示 false不显示
	 */
	private boolean mInStealthMode = true;
	/**
	 * 是否有触觉反馈 true表示有 false表示没有
	 */
	private boolean mEnableHapticFeedback = true;
	/**
	 * 线的宽度(设定值范围为1~50)
	 */
	private float mDiameterFactor = 2;
	private final int mStrokeAlpha = 128;
	/**
	 * 默认平常线的颜色
	 */
	private int lineColor_normal = Color.YELLOW;
	/**
	 * 默认错误线的颜色
	 */
	private int lineColor_woring = Color.RED;
	/**
	 * 每个单元格的背景图片
	 */
	private int circle_Normal;
	/**
	 * 单元格被选中的图片
	 */
	private int circleGreen;
	/**
	 * 单元格选中错误的图片
	 */
	private int circleRed;

	private float mSquareWidth;
	private float mSquareHeight;
	private Paint mPaint = new Paint();
	private Paint mPathPaint = new Paint();
	private Bitmap mBitmapCircleDefault;
	private Bitmap mBitmapCircleGreen;
	private Bitmap mBitmapCircleRed;
	private boolean mPatternInProgress = false;

	private final Path mCurrentPath = new Path();
	private final Rect mInvalidate = new Rect();

	private int mBitmapWidth;
	private int mBitmapHeight;

	private int mAspect;
	private final Matrix mCircleMatrix = new Matrix();

	/**
	 * 每个单元格的实例,代表使用者所看到的每个点
	 */
	public static class Cell {
		int row;
		int column;
		static Cell[][] sCells = new Cell[number][number];
		static {
			for (int i = 0; i < number; i++) {
				for (int j = 0; j < number; j++) {
					sCells[i][j] = new Cell(i, j);
				}
			}
		}

		/**
		 * @param row
		 *            The row of the cell.
		 * @param column
		 *            The column of the cell.
		 */
		private Cell(int row, int column) {
			checkRange(row, column);
			this.row = row;
			this.column = column;
		}

		public int getRow() {
			return row;
		}

		public int getColumn() {
			return column;
		}

		/**
		 * @param row
		 *            The row of the cell.
		 * @param column
		 *            The column of the cell.
		 */
		public static synchronized Cell of(int row, int column) {
			checkRange(row, column);
			return sCells[row][column];
		}

		private static void checkRange(int row, int column) {
			if (row < 0 || row > number - 1) {
				throw new IllegalArgumentException("row must be in range 0-" + (number - 1));
			}
			if (column < 0 || column > number - 1) {
				throw new IllegalArgumentException("column must be in range 0-" + (number - 1));
			}
		}

		public String toString() {
			return "(row=" + row + ",clmn=" + column + ")";
		}
	}

	/**
	 * 显示当前的模式
	 */
	public enum DisplayMode {

		/**
		 * 正确模式 (i.e draw it in a friendly color)
		 */
		Correct,

		/**
		 * 动画模式 (for demo, and help).
		 */
		Animate,

		/**
		 * 错误模式 (i.e draw a foreboding color)
		 */
		Wrong
	}

	/**
	 * 用户输入的检测模式的调用返回接口。使用者需要实现此接口,并设置到此View中
	 */
	public static interface OnPatternListener {

		/**
		 * 开始新的模式(当手指开始滑动)
		 */
		void onPatternStart();

		/**
		 * 模式被清除(完成结束模式)
		 */
		void onPatternCleared();

		/**
		 * 用户每次新增的所选择的单元格调用(正在运行模式完成后)
		 * 
		 * @param pattern
		 *            所有选中的单元格
		 */
		void onPatternCellAdded(List<Cell> pattern);

		/**
		 * 最后完成滑动(一般使用此方法来检测最后选择的单元格)
		 * 
		 * @param pattern
		 *            所有选中的单元格
		 */
		void onPatternDetected(List<Cell> pattern);
	}

	public LockPatternView(Context context) {
		this(context, null);
		initRFile(context);
	}

	private void initRFile(Context context) {
		circle_Normal = MResource.getIdByName(context, "drawable", "ui_lockpatternview_pattern_item_bg");
		circleGreen = MResource.getIdByName(context, "drawable", "ui_lockpatternview_pattern_selected");
		circleRed = MResource.getIdByName(context, "drawable", "ui_lockpatternview_pattern_selected_wrong");
	}

	public LockPatternView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initRFile(context);
		TypedArray a = context.obtainStyledAttributes(attrs, MResource.getIdsByName(context, "Demo_LockPatternView"));
		String aspect = a.getString(MResource.getIdByName(context, "styleable", "Demo_LockPatternView_Demo_aspect"));
		if ("Demo_square".equals(aspect)) {
			mAspect = ASPECT_SQUARE;
		} else if ("Demo_lock_width".equals(aspect)) {
			mAspect = ASPECT_LOCK_WIDTH;
		} else if ("Demo_lock_height".equals(aspect)) {
			mAspect = ASPECT_LOCK_HEIGHT;
		} else {
			mAspect = ASPECT_SQUARE;
		}
		lineColor_normal = a.getColor(MResource.getIdByName(context, "styleable", "Demo_LockPatternView_Demo_lineNormal"),
				lineColor_normal);
		lineColor_woring = a.getColor(MResource.getIdByName(context, "styleable", "Demo_LockPatternView_Demo_lineWrong"),
				lineColor_woring);
		setClickable(true);
		mPathPaint.setAntiAlias(true);
		mPathPaint.setDither(true);
		mPathPaint.setColor(lineColor_normal);
		mPathPaint.setAlpha(mStrokeAlpha);
		mPathPaint.setStyle(Paint.Style.STROKE);
		mPathPaint.setStrokeJoin(Paint.Join.ROUND);
		mPathPaint.setStrokeCap(Paint.Cap.ROUND);

		mBitmapCircleDefault = getBitmapFor(a.getResourceId(
				MResource.getIdByName(context, "styleable", "Demo_LockPatternView_Demo_circleNormal"), circle_Normal));
		mBitmapCircleGreen = getBitmapFor(a.getResourceId(
				MResource.getIdByName(context, "styleable", "Demo_LockPatternView_Demo_circleGreen"), circleGreen));
		mBitmapCircleRed = getBitmapFor(a.getResourceId(
				MResource.getIdByName(context, "styleable", "Demo_LockPatternView_Demo_circleRed"), circleRed));
		// bitmaps have the size of the largest bitmap in this group
		final Bitmap bitmaps[] = { mBitmapCircleDefault, mBitmapCircleGreen, mBitmapCircleRed };
		for (Bitmap bitmap : bitmaps) {
			mBitmapWidth = Math.max(mBitmapWidth, bitmap.getWidth());
			mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight());
		}
		a.recycle();

	}

	private Bitmap getBitmapFor(int resId) {
		return BitmapFactory.decodeResource(getContext().getResources(), resId);
	}

	/**
	 * 禁用输入(连续输错好多次时)
	 */
	public void disableInput() {
		mInputEnabled = false;
	}

	/**
	 * 启用输入
	 */
	public void enableInput() {
		mInputEnabled = true;
	}

	/**
	 * 获取是否显示滑动轨迹
	 * 
	 * @return true显示 false不显示
	 */
	public boolean isInStealthMode() {
		return mInStealthMode;
	}

	/**
	 * 获取是否有触觉反馈
	 * 
	 * @return true表示有 false表示没有
	 */
	public boolean isTactileFeedbackEnabled() {
		return mEnableHapticFeedback;
	}

	/**
	 * 设置是否显示滑动轨迹 true显示 false不显示
	 */
	public void setInStealthMode(boolean inStealthMode) {
		mInStealthMode = inStealthMode;
	}

	/**
	 * 设置是否有触觉反馈 true表示有 false表示没有
	 */
	public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) {
		mEnableHapticFeedback = tactileFeedbackEnabled;
	}

	/**
	 * 使用者自己实现的OnPatternListener设置到此View
	 */
	public void setOnPatternListener(OnPatternListener onPatternListener) {
		mOnPatternListener = onPatternListener;
	}

	/**
	 * 设置动画演示,传入的是动画过程中的多有单元格,动画顺序和传入的单元格顺序相同
	 * 
	 * @param pattern
	 *            动画演示的所有单元格
	 */
	public void setAnimationPattern(List<Cell> pattern) {
		mPattern.clear();
		mPattern.addAll(pattern);
		clearPatternDrawLookup();
		for (Cell cell : pattern) {
			mPatternDrawLookup[cell.getRow()][cell.getColumn()] = true;
		}
		setDisplayMode(DisplayMode.Animate);
	}

	private void setPattern(DisplayMode displayMode, List<Cell> pattern) {
		mPattern.clear();
		mPattern.addAll(pattern);
		clearPatternDrawLookup();
		for (Cell cell : pattern) {
			mPatternDrawLookup[cell.getRow()][cell.getColumn()] = true;
		}
		setDisplayMode(displayMode);
	}

	/**
	 * 设置View的所占区域模式
	 */
	public void setDisplayMode(DisplayMode displayMode) {
		mPatternDisplayMode = displayMode;
		if (displayMode == DisplayMode.Animate) {
			if (mPattern.size() == 0) {
				throw new IllegalStateException("you must have a pattern to "
						+ "animate if you want to set the display mode to animate");
			}
			mAnimatingPeriodStart = SystemClock.elapsedRealtime();
			final Cell first = mPattern.get(0);
			mInProgressX = getCenterXForColumn(first.getColumn());
			mInProgressY = getCenterYForRow(first.getRow());
			clearPatternDrawLookup();
		}
		invalidate();
	}

	private void notifyCellAdded() {
		if (mOnPatternListener != null) {
			mOnPatternListener.onPatternCellAdded(mPattern);
		}
	}

	private void notifyPatternStarted() {
		if (mOnPatternListener != null) {
			mOnPatternListener.onPatternStart();
		}
	}

	private void notifyPatternDetected() {
		if (mOnPatternListener != null) {
			mOnPatternListener.onPatternDetected(mPattern);
		}
	}

	private void notifyPatternCleared() {
		if (mOnPatternListener != null) {
			mOnPatternListener.onPatternCleared();
		}
	}

	/**
	 * 清除绘制的图案
	 */
	public void clearPattern() {
		resetPattern();
	}

	/**
	 * 重置所有图案状态
	 */
	private void resetPattern() {
		mPattern.clear();
		clearPatternDrawLookup();
		mPatternDisplayMode = DisplayMode.Correct;
		invalidate();
	}

	/**
	 * 清除图案列表-图案都标记为未选中
	 */
	private void clearPatternDrawLookup() {
		for (int i = 0; i < number; i++) {
			for (int j = 0; j < number; j++) {
				mPatternDrawLookup[i][j] = false;
			}
		}
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		final int width = w - getPaddingLeft() - getPaddingRight();
		mSquareWidth = (float) width / number;
		final int height = h - getPaddingTop() - getPaddingBottom();
		mSquareHeight = (float) height / number;
	}

	private int resolveMeasured(int measureSpec, int desired) {
		int result = 0;
		int specSize = MeasureSpec.getSize(measureSpec);
		switch (MeasureSpec.getMode(measureSpec)) {
		case MeasureSpec.UNSPECIFIED:
			result = desired;
			break;
		case MeasureSpec.AT_MOST:
			result = Math.max(specSize, desired);
			break;
		case MeasureSpec.EXACTLY:
		default:
			result = specSize;
		}
		return result;
	}

	@Override
	protected int getSuggestedMinimumWidth() {
		// View should be large enough to contain 3 side-by-side target bitmaps
		return number * mBitmapWidth;
	}

	@Override
	protected int getSuggestedMinimumHeight() {
		// View should be large enough to contain 3 side-by-side target bitmaps
		return number * mBitmapWidth;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		final int minimumWidth = getSuggestedMinimumWidth();
		final int minimumHeight = getSuggestedMinimumHeight();
		int viewWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
		int viewHeight = resolveMeasured(heightMeasureSpec, minimumHeight);

		switch (mAspect) {
		case ASPECT_SQUARE:
			viewWidth = viewHeight = Math.min(viewWidth, viewHeight);
			break;
		case ASPECT_LOCK_WIDTH:
			viewHeight = Math.min(viewWidth, viewHeight);
			break;
		case ASPECT_LOCK_HEIGHT:
			viewWidth = Math.min(viewWidth, viewHeight);
			break;
		}
		setMeasuredDimension(viewWidth, viewHeight);
	}

	private Cell detectAndAddHit(float x, float y) {
		final Cell cell = checkForNewHit(x, y);
		if (cell != null) {
			// check for gaps in existing pattern
			Cell fillInGapCell = null;
			final ArrayList<Cell> pattern = mPattern;
			if (!pattern.isEmpty()) {
				final Cell lastCell = pattern.get(pattern.size() - 1);
				int dRow = cell.row - lastCell.row;
				int dColumn = cell.column - lastCell.column;

				int fillInRow = lastCell.row;
				int fillInColumn = lastCell.column;

				if (Math.abs(dRow) == (number - 1) && Math.abs(dColumn) != 1) {
					fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1);
				}

				if (Math.abs(dColumn) == (number - 1) && Math.abs(dRow) != 1) {
					fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1);
				}

				fillInGapCell = Cell.of(fillInRow, fillInColumn);
			}

			if (fillInGapCell != null && !mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) {
				addCellToPattern(fillInGapCell);
			}
			addCellToPattern(cell);
			if (mEnableHapticFeedback) {
				performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
						| HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
			}
			return cell;
		}
		return null;
	}

	private void addCellToPattern(Cell newCell) {
		mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true;
		mPattern.add(newCell);
		notifyCellAdded();
	}

	/**
	 * 判断手指区域是否在某个单元格内
	 */
	private Cell checkForNewHit(float x, float y) {

		final int rowHit = getRowHit(y);
		if (rowHit < 0) {
			return null;
		}
		final int columnHit = getColumnHit(x);
		if (columnHit < 0) {
			return null;
		}

		if (mPatternDrawLookup[rowHit][columnHit]) {
			return null;
		}
		return Cell.of(rowHit, columnHit);
	}

	private int getRowHit(float y) {

		final float squareHeight = mSquareHeight;
		float hitSize = squareHeight * 0.7f;

		float offset = getPaddingTop() + (squareHeight - hitSize) / (float) (number - 1);
		for (int i = 0; i < number; i++) {

			final float hitTop = offset + squareHeight * i;
			if (y >= hitTop && y <= hitTop + hitSize) {
				return i;
			}
		}
		return -1;
	}

	private int getColumnHit(float x) {
		final float squareWidth = mSquareWidth;
		float hitSize = squareWidth * 0.7f;

		float offset = getPaddingLeft() + (squareWidth - hitSize) / (float) (number - 1);
		for (int i = 0; i < number; i++) {

			final float hitLeft = offset + squareWidth * i;
			if (x >= hitLeft && x <= hitLeft + hitSize) {
				return i;
			}
		}
		return -1;
	}

	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (!mInputEnabled || !isEnabled()) {
			return false;
		}

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			handleActionDown(event);
			return true;
		case MotionEvent.ACTION_UP:
			handleActionUp(event);
			return true;
		case MotionEvent.ACTION_MOVE:
			handleActionMove(event);
			return true;
		case MotionEvent.ACTION_CANCEL:
			resetPattern();
			mPatternInProgress = false;
			notifyPatternCleared();
			return true;
		}
		return false;
	}

	/**
	 * 划线的算法,有兴趣的看看吧
	 */
	private void handleActionMove(MotionEvent event) {
		final int historySize = event.getHistorySize();
		for (int i = 0; i < historySize + 1; i++) {
			final float x = i < historySize ? event.getHistoricalX(i) : event.getX();
			final float y = i < historySize ? event.getHistoricalY(i) : event.getY();
			final int patternSizePreHitDetect = mPattern.size();
			Cell hitCell = detectAndAddHit(x, y);
			final int patternSize = mPattern.size();
			if (hitCell != null && patternSize == 1) {
				mPatternInProgress = true;
				notifyPatternStarted();
			}
			// note current x and y for rubber banding of in progress patterns
			final float dx = Math.abs(x - mInProgressX);
			final float dy = Math.abs(y - mInProgressY);
			if (dx + dy > mSquareWidth * 0.01f) {
				float oldX = mInProgressX;
				float oldY = mInProgressY;
				mInProgressX = x;
				mInProgressY = y;
				if (mPatternInProgress && patternSize > 0) {
					final ArrayList<Cell> pattern = mPattern;
					final float radius = mSquareWidth * mDiameterFactor * 0.01f;
					final Cell lastCell = pattern.get(patternSize - 1);
					float startX = getCenterXForColumn(lastCell.column);
					float startY = getCenterYForRow(lastCell.row);
					float left;
					float top;
					float right;
					float bottom;
					final Rect invalidateRect = mInvalidate;
					if (startX < x) {
						left = startX;
						right = x;
					} else {
						left = x;
						right = startX;
					}
					if (startY < y) {
						top = startY;
						bottom = y;
					} else {
						top = y;
						bottom = startY;
					}
					// Invalidate between the pattern's last cell and the
					// current location
					invalidateRect.set((int) (left - radius), (int) (top - radius), (int) (right + radius),
							(int) (bottom + radius));
					if (startX < oldX) {
						left = startX;
						right = oldX;
					} else {
						left = oldX;
						right = startX;
					}
					if (startY < oldY) {
						top = startY;
						bottom = oldY;
					} else {
						top = oldY;
						bottom = startY;
					}
					// Invalidate between the pattern's last cell and the
					// previous location
					invalidateRect.union((int) (left - radius), (int) (top - radius), (int) (right + radius),
							(int) (bottom + radius));
					// Invalidate between the pattern's new cell and the
					// pattern's previous cell
					if (hitCell != null) {
						startX = getCenterXForColumn(hitCell.column);
						startY = getCenterYForRow(hitCell.row);
						if (patternSize >= (number - 1)) {
							// (re-using hitcell for old cell)
							hitCell = pattern.get(patternSize - 1 - (patternSize - patternSizePreHitDetect));
							oldX = getCenterXForColumn(hitCell.column);
							oldY = getCenterYForRow(hitCell.row);
							if (startX < oldX) {
								left = startX;
								right = oldX;
							} else {
								left = oldX;
								right = startX;
							}
							if (startY < oldY) {
								top = startY;
								bottom = oldY;
							} else {
								top = oldY;
								bottom = startY;
							}
						} else {
							left = right = startX;
							top = bottom = startY;
						}
						final float widthOffset = mSquareWidth / 2f;
						final float heightOffset = mSquareHeight / 2f;
						invalidateRect.set((int) (left - widthOffset), (int) (top - heightOffset), (int) (right + widthOffset),
								(int) (bottom + heightOffset));
					}
					invalidate(invalidateRect);
				} else {
					invalidate();
				}
			}
		}
	}

	private void handleActionUp(MotionEvent event) {
		// report pattern detected
		if (!mPattern.isEmpty()) {
			mPatternInProgress = false;
			notifyPatternDetected();
			invalidate();
		}
	}

	private void handleActionDown(MotionEvent event) {
		resetPattern();
		final float x = event.getX();
		final float y = event.getY();
		final Cell hitCell = detectAndAddHit(x, y);
		if (hitCell != null) {
			mPatternInProgress = true;
			mPatternDisplayMode = DisplayMode.Correct;
			notifyPatternStarted();
		} else {
			mPatternInProgress = false;
			notifyPatternCleared();
		}
		if (hitCell != null) {
			final float startX = getCenterXForColumn(hitCell.column);
			final float startY = getCenterYForRow(hitCell.row);

			final float widthOffset = mSquareWidth / 2f;
			final float heightOffset = mSquareHeight / 2f;

			invalidate((int) (startX - widthOffset), (int) (startY - heightOffset), (int) (startX + widthOffset),
					(int) (startY + heightOffset));
		}
		mInProgressX = x;
		mInProgressY = y;
	}

	private float getCenterXForColumn(int column) {
		return getPaddingLeft() + column * mSquareWidth + mSquareWidth / 2f;
	}

	private float getCenterYForRow(int row) {
		return getPaddingTop() + row * mSquareHeight + mSquareHeight / 2f;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		final ArrayList<Cell> pattern = mPattern;
		final int count = pattern.size();
		final boolean[][] drawLookup = mPatternDrawLookup;
		if (mPatternDisplayMode == DisplayMode.Animate) {
			// figure out which circles to draw
			// + 1 so we pause on complete pattern
			final int oneCycle = (count + 1) * MILLIS_PER_CIRCLE_ANIMATING;
			final int spotInCycle = (int) (SystemClock.elapsedRealtime() - mAnimatingPeriodStart) % oneCycle;
			final int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING;
			clearPatternDrawLookup();
			for (int i = 0; i < numCircles; i++) {
				final Cell cell = pattern.get(i);
				drawLookup[cell.getRow()][cell.getColumn()] = true;
			}
			// figure out in progress portion of ghosting line
			final boolean needToUpdateInProgressPoint = numCircles > 0 && numCircles < count;
			if (needToUpdateInProgressPoint) {
				final float percentageOfNextCircle = ((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING))
						/ MILLIS_PER_CIRCLE_ANIMATING;
				final Cell currentCell = pattern.get(numCircles - 1);
				final float centerX = getCenterXForColumn(currentCell.column);
				final float centerY = getCenterYForRow(currentCell.row);
				final Cell nextCell = pattern.get(numCircles);
				final float dx = percentageOfNextCircle * (getCenterXForColumn(nextCell.column) - centerX);
				final float dy = percentageOfNextCircle * (getCenterYForRow(nextCell.row) - centerY);
				mInProgressX = centerX + dx;
				mInProgressY = centerY + dy;
			}
			invalidate();
		}
		final float squareWidth = mSquareWidth;
		final float squareHeight = mSquareHeight;
		float radius = (squareWidth * mDiameterFactor * 0.01f);
		mPathPaint.setStrokeWidth(radius);
		final Path currentPath = mCurrentPath;
		currentPath.rewind();
		final boolean drawPath = (mInStealthMode || mPatternDisplayMode != DisplayMode.Correct);
		boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;
		mPaint.setFilterBitmap(true);
		if (drawPath) {
			boolean anyCircles = false;
			for (int i = 0; i < count; i++) {
				Cell cell = pattern.get(i);
				// only draw the part of the pattern stored in
				// the lookup table (this is only different in the case
				// of animation).
				if (!drawLookup[cell.row][cell.column]) {
					break;
				}
				anyCircles = true;
				float centerX = getCenterXForColumn(cell.column);
				float centerY = getCenterYForRow(cell.row);
				if (i == 0) {
					currentPath.moveTo(centerX, centerY);
				} else {
					currentPath.lineTo(centerX, centerY);
				}
			}
			// add last in progress section
			if ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate) && anyCircles) {
				currentPath.lineTo(mInProgressX, mInProgressY);
			}
			// chang the line color in different DisplayMode
			if (mPatternDisplayMode == DisplayMode.Wrong)
				mPathPaint.setColor(lineColor_woring);
			else
				mPathPaint.setColor(lineColor_normal);
			canvas.drawPath(currentPath, mPathPaint);
		}

		// draw the circles
		final int paddingTop = getPaddingTop();
		final int paddingLeft = getPaddingLeft();

		for (int i = 0; i < number; i++) {
			float topY = paddingTop + i * squareHeight;
			for (int j = 0; j < number; j++) {
				float leftX = paddingLeft + j * squareWidth;
				drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]);
			}
		}
		mPaint.setFilterBitmap(oldFlag); // restore default flag
	}

	/**
	 * 解密
	 */
	public List<LockPatternView.Cell> stringToPattern(String string) {
		List<LockPatternView.Cell> result = new ArrayList<LockPatternView.Cell>();
		final byte[] bytes = string.getBytes();
		for (int i = 0; i < bytes.length; i++) {
			byte b = bytes[i];
			result.add(LockPatternView.Cell.of(b / LockPatternView.number, b % LockPatternView.number));
		}
		return result;
	}

	/**
	 * 加密,将选中的单元格转换成自己字符串便于存储
	 */
	public static String patternToString(List<LockPatternView.Cell> pattern) {
		if (pattern == null) {
			return "";
		}
		final int patternSize = pattern.size();

		byte[] res = new byte[patternSize];
		for (int i = 0; i < patternSize; i++) {
			LockPatternView.Cell cell = pattern.get(i);
			res[i] = (byte) (cell.getRow() * LockPatternView.number + cell.getColumn());
		}
		return new String(res);
	}

	/**
	 * 画每个单元格
	 */
	private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) {
		Bitmap outerCircle;
		Bitmap innerCircle = null;
		if (!partOfPattern || (!mInStealthMode && mPatternDisplayMode == DisplayMode.Correct)) {
			// unselected circle
			outerCircle = mBitmapCircleDefault;
			innerCircle = null;
		} else if (mPatternInProgress) {
			// user is in middle of drawing a pattern
			outerCircle = mBitmapCircleDefault;
			innerCircle = mBitmapCircleGreen;
		} else if (mPatternDisplayMode == DisplayMode.Wrong) {
			// the pattern is wrong
			outerCircle = mBitmapCircleDefault;
			innerCircle = mBitmapCircleRed;
		} else if (mPatternDisplayMode == DisplayMode.Correct || mPatternDisplayMode == DisplayMode.Animate) {
			// the pattern is correct
			outerCircle = mBitmapCircleDefault;
			innerCircle = mBitmapCircleGreen;
		} else {
			throw new IllegalStateException("unknown display mode " + mPatternDisplayMode);
		}
		final int width = mBitmapWidth;
		final int height = mBitmapHeight;
		final float squareWidth = mSquareWidth;
		final float squareHeight = mSquareHeight;
		int offsetX = (int) ((squareWidth - width) / 2f);
		int offsetY = (int) ((squareHeight - height) / 2f);
		// Allow circles to shrink if the view is too small to hold them.
		float sx = Math.min(mSquareWidth / mBitmapWidth, 1.0f);
		float sy = Math.min(mSquareHeight / mBitmapHeight, 1.0f);
		mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY);
		mCircleMatrix.preTranslate(mBitmapWidth / (number - 1), mBitmapHeight / (number - 1));
		mCircleMatrix.preScale(sx, sy);
		mCircleMatrix.preTranslate(-mBitmapWidth / (number - 1), -mBitmapHeight / (number - 1));
		canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint);
		if (innerCircle != null)
			canvas.drawBitmap(innerCircle, mCircleMatrix, mPaint);
	}

	@Override
	protected Parcelable onSaveInstanceState() {
		Parcelable superState = super.onSaveInstanceState();
		return new SavedState(superState, patternToString(mPattern), mPatternDisplayMode.ordinal(), mInputEnabled,
				mInStealthMode, mEnableHapticFeedback);
	}

	@Override
	protected void onRestoreInstanceState(Parcelable state) {
		final SavedState ss = (SavedState) state;
		super.onRestoreInstanceState(ss.getSuperState());
		setPattern(DisplayMode.Correct, stringToPattern(ss.getSerializedPattern()));
		mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
		mInputEnabled = ss.isInputEnabled();
		mInStealthMode = ss.isInStealthMode();
		mEnableHapticFeedback = ss.isTactileFeedbackEnabled();
	}

	/**
	 * SavedState是Parcelable的子类
	 */
	private static class SavedState extends BaseSavedState {

		private final String mSerializedPattern;
		private final int mDisplayMode;
		private final boolean mInputEnabled;
		private final boolean mInStealthMode;
		private final boolean mTactileFeedbackEnabled;

		/**
		 * Constructor called from {@link LockPatternView#onSaveInstanceState()}
		 */
		private SavedState(Parcelable superState, String serializedPattern, int displayMode, boolean inputEnabled,
				boolean inStealthMode, boolean tactileFeedbackEnabled) {
			super(superState);
			mSerializedPattern = serializedPattern;
			mDisplayMode = displayMode;
			mInputEnabled = inputEnabled;
			mInStealthMode = inStealthMode;
			mTactileFeedbackEnabled = tactileFeedbackEnabled;
		}

		/**
		 * Constructor called from {@link #CREATOR}
		 */
		private SavedState(Parcel in) {
			super(in);
			mSerializedPattern = in.readString();
			mDisplayMode = in.readInt();
			mInputEnabled = (Boolean) in.readValue(null);
			mInStealthMode = (Boolean) in.readValue(null);
			mTactileFeedbackEnabled = (Boolean) in.readValue(null);
		}

		public String getSerializedPattern() {
			return mSerializedPattern;
		}

		public int getDisplayMode() {
			return mDisplayMode;
		}

		public boolean isInputEnabled() {
			return mInputEnabled;
		}

		public boolean isInStealthMode() {
			return mInStealthMode;
		}

		public boolean isTactileFeedbackEnabled() {
			return mTactileFeedbackEnabled;
		}

		@Override
		public void writeToParcel(Parcel dest, int flags) {
			super.writeToParcel(dest, flags);
			dest.writeString(mSerializedPattern);
			dest.writeInt(mDisplayMode);
			dest.writeValue(mInputEnabled);
			dest.writeValue(mInStealthMode);
			dest.writeValue(mTactileFeedbackEnabled);
		}
	}
}
其中用到的attr value有:

 	<!-- 九宫格所需的属性 -->
    <declare-styleable name="Demo_LockPatternView">
        <attr name="Demo_aspect">
            <enum name="Demo_square" value="0" />
            <enum name="Demo_lockWidth" value="1" />
            <enum name="Demo_lockHeight" value="2" />
        </attr>
        <attr name="Demo_lineNormal" format="color" />
        <attr name="Demo_lineWrong" format="color" />
        <attr name="Demo_circleNormal" format="reference" />
        <attr name="Demo_circleGreen" format="reference" />
        <attr name="Demo_circleRed" format="reference" />
    </declare-styleable>
此view可以通过xml直接配置使用,必须要强调的,必须要设置设置setOnPatternListener(OnPatternListener onPatternListener)  必须传入自己实现的回调OnPatternListener,在回调中的onPatternDetected方法中传入的就是用户选择的宫格,在这里对其进行逻辑上的操作。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值