手势在绘制手势的过程中,View是如何判断手指当前位置是否选中某个 Cell ,以及是否应该把该 Cell 连接入手势。这里需要了解几个函数:

  • getRowHit ( float y )

用来确定手指当前坐标 (x, y) 位于九宫格的第几排。

  • getColumnHit (float x )

用来确定手指当前坐标 (x, y) 位于九宫格的第几列。

  • checkForNewHit (float x, float y)
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);
  }

函数代码很好理解,mPatternDrawLookup 是个全局变量,同样采用矩阵的形式,用于标记九宫格中哪个 Cell 被连接。从 checkForNewHit 中可以看出,已经被连接的 Cell,是不会再被选中的,这也是目前手势密码普遍的做法。如果你需要实现“每个点可以被连接多次”的需求,这部分就需要改动了。

  • detectAndAddHit (float x, float y)

用来检测并判断手指当前坐标 (x, y) 是否需要添加添加进当前手势中。
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) == 2 && Math.abs(dColumn) != 1) {
                  fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1);
              }

              if (Math.abs(dColumn) == 2 && 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;
  }
首先通过 checkForNewHit 获得当前位置的的 Cell,计算当前Cell 与手势中最后一个 Cell 的行列差值。看其中一段代码
if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {
  fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1);
}

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

fillInGapCell = Cell.of(fillInRow, fillInColumn);

判断条件是:当前 Cell 与手势中最后一个 Cell 的行或者列的绝对差值为 2,且其列或行的绝对差值不为1,即两个 Cell 不相邻(包括水平、竖直、45°方向的相邻),获得当前 Cell 与手势中最后一个 Cell 之间的 Cell,如果该 Cell 没有被添加进去过,则添加进手势。

意思就是说,绘制的手势不会跨过没有添加的点。