/** * Determines whether the point x, y will add a new point to the current pattern (in addition to * finding the cell, also makes heuristic choices such as filling in gaps based on current * pattern). * * @param x The x coordinate. * @param y The y coordinate. */ private Cell detectAndAddHit(float x, float y) { final Cell cell = checkForNewHit(x, y); if (cell != null) { // check for gaps in existing pattern // Cell fillInGapCelal = null; final ArrayList<Cell> newCells = new ArrayList<>(); if (!mPattern.isEmpty()) { final Cell lastCell = mPattern.get(mPattern.size() - 1); int dRow = cell.getRow() - lastCell.getRow(); int dCol = cell.getColumn() - lastCell.getColumn(); int rsign = dRow > 0 ? 1 : -1; int csign = dCol > 0 ? 1 : -1; if (dRow == 0) { for (int i = 1; i < Math.abs(dCol); i++) { newCells.add(new Cell(lastCell.getRow(), lastCell.getColumn() + i * csign)); } } else if (dCol == 0) { for (int i = 1; i < Math.abs(dRow); i++) { newCells.add(new Cell(lastCell.getRow() + i * rsign, lastCell.getColumn())); } } else if (Math.abs(dCol) == Math.abs(dRow)) { for (int i = 1; i < Math.abs(dRow); i++) { newCells.add(new Cell(lastCell.getRow() + i * rsign, lastCell.getColumn() + i * csign)); } } } for (Cell fillInGapCell : newCells) { if (fillInGapCell != null && !cellManager.isDrawn(fillInGapCell)) { addCellToPattern(fillInGapCell); } } addCellToPattern(cell); if (enableHapticFeedback) { performHapticFeedback( HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } return cell; } return null; }
/** * Set the display mode of the current pattern. This can be useful, for instance, after detecting * a pattern to tell this view whether change the in progress result to correct or wrong. * * @param displayMode The display mode. */ public void setDisplayMode(final DisplayMode displayMode) { patternDisplayMode = 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"); } animatingPeriodStart = SystemClock.elapsedRealtime(); final Cell first = mPattern.get(0); inProgressX = getCenterXForColumn(first.getColumn()); inProgressY = getCenterYForRow(first.getRow()); clearPatternDrawLookup(); } 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) { patternInProgress = true; patternDisplayMode = DisplayMode.Correct; notifyPatternStarted(); } else { /* * Original source check for patternInProgress == true first before * calling this block. But if we do that, there will be nothing * happened when the user taps at empty area and releases the * finger. We want the pattern to be reset and the message will be * updated after the user did that. */ patternInProgress = false; notifyPatternCleared(); } if (hitCell != null) { final float startX = getCenterXForColumn(hitCell.getColumn()); final float startY = getCenterYForRow(hitCell.getRow()); final float widthOffset = squareWidth / 2f; final float heightOffset = squareHeight / 2f; invalidate( (int) (startX - widthOffset), (int) (startY - heightOffset), (int) (startX + widthOffset), (int) (startY + heightOffset)); } inProgressX = x; inProgressY = y; if (PROFILE_DRAWING) { if (!drawingProfilingStarted) { Debug.startMethodTracing("LockPatternDrawing"); drawingProfilingStarted = true; } } }
@Override protected void onDraw(Canvas canvas) { final ArrayList<Cell> pattern = mPattern; final int count = pattern.size(); if (patternDisplayMode == DisplayMode.Animate) { final int oneCycle = (count + 1) * MILLIS_PER_CIRCLE_ANIMATING; final int spotInCycle = (int) (SystemClock.elapsedRealtime() - animatingPeriodStart) % oneCycle; final int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING; clearPatternDrawLookup(); for (int i = 0; i < numCircles; i++) { final Cell cell = pattern.get(i); cellManager.draw(cell, true); } 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.getColumn()); final float centerY = getCenterYForRow(currentCell.getRow()); final Cell nextCell = pattern.get(numCircles); final float dx = percentageOfNextCircle * (getCenterXForColumn(nextCell.getColumn()) - centerX); final float dy = percentageOfNextCircle * (getCenterYForRow(nextCell.getRow()) - centerY); inProgressX = centerX + dx; inProgressY = centerY + dy; } invalidate(); } final float squareWidth = this.squareWidth; final float squareHeight = this.squareHeight; float radius = (squareWidth * diameterFactor * 0.5f); pathPaint.setStrokeWidth(radius); final Path currentPath = this.currentPath; currentPath.rewind(); // draw the circles final int paddingTop = this.paddingTop; final int paddingLeft = this.paddingLeft; for (int i = 0; i < gridSize; i++) { float topY = paddingTop + i * squareHeight; for (int j = 0; j < gridSize; j++) { float leftX = paddingLeft + j * squareWidth; drawCircle(canvas, (int) leftX, (int) topY, cellManager.isDrawn(i, j)); } } // only the last segment of the path should be computed here // draw the path of the pattern (unless the user is in progress, and // we are in stealth mode) final boolean drawPath = (!inStealthMode && patternDisplayMode == DisplayMode.Correct || !inErrorStealthMode && patternDisplayMode == DisplayMode.Wrong); // draw the arrows associated with the path (unless the user is in // progress, and // we are in stealth mode) boolean oldFlag = (circlePaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0; circlePaint.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 (!cellManager.isDrawn(cell)) { break; } anyCircles = true; float centerX = getCenterXForColumn(cell.getColumn()); float centerY = getCenterYForRow(cell.getRow()); if (i == 0) { currentPath.moveTo(centerX, centerY); } else { currentPath.lineTo(centerX, centerY); } } // add last in progress section if ((patternInProgress || patternDisplayMode == DisplayMode.Animate) && anyCircles && count > 1) { currentPath.lineTo(inProgressX, inProgressY); } canvas.drawPath(currentPath, pathPaint); } circlePaint.setFilterBitmap(oldFlag); // restore default flag }
private void handleActionMove(MotionEvent event) { // Handle all recent motion events so we don't skip any cells even when // the device // is busy... 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) { patternInProgress = true; notifyPatternStarted(); } // note current x and y for rubber banding of in progress patterns final float dx = Math.abs(x - inProgressX); final float dy = Math.abs(y - inProgressY); if (dx + dy > squareWidth * 0.01f) { float oldX = inProgressX; float oldY = inProgressY; inProgressX = x; inProgressY = y; if (patternInProgress && patternSize > 0) { final ArrayList<Cell> pattern = mPattern; final float radius = squareWidth * diameterFactor * 0.5f; final Cell lastCell = pattern.get(patternSize - 1); float startX = getCenterXForColumn(lastCell.getColumn()); float startY = getCenterYForRow(lastCell.getRow()); float left; float top; float right; float bottom; final Rect invalidateRect = invalidate; 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.getColumn()); startY = getCenterYForRow(hitCell.getRow()); if (patternSize >= 2) { // (re-using hitcell for old cell) hitCell = pattern.get(patternSize - 1 - (patternSize - patternSizePreHitDetect)); oldX = getCenterXForColumn(hitCell.getColumn()); oldY = getCenterYForRow(hitCell.getRow()); 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 = squareWidth / 2f; final float heightOffset = squareHeight / 2f; invalidateRect.set( (int) (left - widthOffset), (int) (top - heightOffset), (int) (right + widthOffset), (int) (bottom + heightOffset)); } invalidate(invalidateRect); } else { invalidate(); } } } invalidate(); }