欢迎使用CSDN-markdown编辑器

来源:互联网 发布:淘宝上正品篮球店铺 编辑:程序博客网 时间:2024/06/13 01:54

/**
* 手势的View
lmj
*/
public class LockPatternView extends View {
// Aspect to use when rendering this view
private static final int ASPECT_SQUARE = 0; // View will be the minimum of
// width/height
private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will
// be minimum of (w,h)
private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will
// be minimum of (w,h)

public static final int MIN_LOCK_PATTERN_SIZE = 4;private static final boolean PROFILE_DRAWING = false;private boolean mDrawingProfilingStarted = false;private Paint mPaint = new Paint();private Paint mPathPaint = new Paint();/** * How many milliseconds we spend animating each circle of a lock pattern if * the animating mode is set. The entire animation should take this constant * * the length of the pattern to complete. */private static final int MILLIS_PER_CIRCLE_ANIMATING = 700;/** * This can be used to avoid updating the display for very small motions or * noisy panels. It didn't seem to have much impact on the devices tested, * so currently set to 0. */private static final float DRAG_THRESHHOLD = 0.0f;private OnPatternListener mOnPatternListener;private ArrayList<Cell> mPattern = new ArrayList<Cell>(9);/** * Lookup table for the circles of the pattern we are currently drawing. * This will be the cells of the complete pattern unless we are animating, * in which case we use this to hold the cells we are drawing for the in * progress animation. */private boolean[][] mPatternDrawLookup = new boolean[3][3];/** * the in progress point: - during interaction: where the user's finger is - * during animation: the current tip of the animating line */private float mInProgressX = -1;private float mInProgressY = -1;private long mAnimatingPeriodStart;private DisplayMode mPatternDisplayMode = DisplayMode.Correct;private boolean mInputEnabled = true;private boolean mInStealthMode = false;private boolean mEnableHapticFeedback = true;private boolean mPatternInProgress = false;private float mDiameterFactor = 0.10f; // TODO: move to attrsprivate final int mStrokeAlpha = 128;private float mHitFactor = 0.6f;private float mSquareWidth;private float mSquareHeight;private Bitmap mBitmapBtnDefault;private Bitmap mBitmapBtnTouched;private Bitmap mBitmapCircleDefault;private Bitmap mBitmapCircleGreen;private Bitmap mBitmapCircleRed;private Bitmap mBitmapArrowGreenUp;private Bitmap mBitmapArrowRedUp;private final Path mCurrentPath = new Path();private final Rect mInvalidate = new Rect();private final Rect mTmpInvalidateRect = new Rect();private int mBitmapWidth;private int mBitmapHeight;private int mAspect;private final Matrix mArrowMatrix = new Matrix();private final Matrix mCircleMatrix = new Matrix();/** * Represents a cell in the 3 X 3 matrix of the unlock pattern view. */public static class Cell {    int row;    int column;    // keep # objects limited to 9    static Cell[][] sCells = new Cell[3][3];    static {        for (int i = 0; i < 3; i++) {            for (int j = 0; j < 3; 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 > 2) {            throw new IllegalArgumentException("row must be in range 0-2");        }        if (column < 0 || column > 2) {            throw new IllegalArgumentException("column must be in range 0-2");        }    }    public String toString() {        return "(row=" + row + ",clmn=" + column + ")";    }}/** * How to display the current pattern. */public enum DisplayMode {    /**     * The pattern drawn is correct (i.e draw it in a friendly color)     */    Correct,    /**     * Animate the pattern (for demo, and help).     */    Animate,    /**     * The pattern is wrong (i.e draw a foreboding color)     */    Wrong}/** * The call back interface for detecting patterns entered by the user. */public static interface OnPatternListener {    /**     * A new pattern has begun.     */    void onPatternStart();    /**     * The pattern was cleared.     */    void onPatternCleared();    /**     * The user extended the pattern currently being drawn by one cell.     *      * @param pattern     *            The pattern with newly added cell.     */    void onPatternCellAdded(List<Cell> pattern);    /**     * A pattern was detected from the user.     *      * @param pattern     *            The pattern.     */    void onPatternDetected(List<Cell> pattern);}public LockPatternView(Context context) {    this(context, null);}public LockPatternView(Context context, AttributeSet attrs) {    super(context, attrs);    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LockPatternView);    final String aspect = a.getString(R.styleable.LockPatternView_aspect);    if ("square".equals(aspect)) {        mAspect = ASPECT_SQUARE;    } else if ("lock_width".equals(aspect)) {        mAspect = ASPECT_LOCK_WIDTH;    } else if ("lock_height".equals(aspect)) {        mAspect = ASPECT_LOCK_HEIGHT;    } else {        mAspect = ASPECT_SQUARE;    }    setClickable(true);    mPathPaint.setAntiAlias(true);    mPathPaint.setDither(true);    mPathPaint.setColor(Color.WHITE); // TODO this should be from the style    mPathPaint.setAlpha(mStrokeAlpha);    mPathPaint.setStyle(Paint.Style.STROKE);    mPathPaint.setStrokeJoin(Paint.Join.ROUND);    mPathPaint.setStrokeCap(Paint.Cap.ROUND);    // lot's of bitmaps!    mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_holo);    mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_holo);    mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default_holo);    mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green_holo);    mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red_holo);    mBitmapArrowGreenUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_green_up);    mBitmapArrowRedUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_red_up);    // bitmaps have the size of the largest bitmap in this group    final Bitmap bitmaps[] = { mBitmapBtnDefault, mBitmapBtnTouched, mBitmapCircleDefault, mBitmapCircleGreen,            mBitmapCircleRed };    for (Bitmap bitmap : bitmaps) {        mBitmapWidth = Math.max(mBitmapWidth, bitmap.getWidth());        mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight());    }}private Bitmap getBitmapFor(int resId) {    return BitmapFactory.decodeResource(getContext().getResources(), resId);}/** * @return Whether the view is in stealth mode. */public boolean isInStealthMode() {    return mInStealthMode;}/** * @return Whether the view has tactile feedback enabled. */public boolean isTactileFeedbackEnabled() {    return mEnableHapticFeedback;}/** * Set whether the view is in stealth mode. If true, there will be no * visible feedback as the user enters the pattern. * * @param inStealthMode *            Whether in stealth mode. */public void setInStealthMode(boolean inStealthMode) {    mInStealthMode = inStealthMode;}/** * Set whether the view will use tactile feedback. If true, there will be * tactile feedback as the user enters the pattern. * * @param tactileFeedbackEnabled *            Whether tactile feedback is enabled */public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) {    mEnableHapticFeedback = tactileFeedbackEnabled;}/** * Set the call back for pattern detection. *  * @param onPatternListener *            The call back. */public void setOnPatternListener(OnPatternListener onPatternListener) {    mOnPatternListener = onPatternListener;}/** * Set the pattern explicitely (rather than waiting for the user to input a * pattern). *  * @param displayMode *            How to display the pattern. * @param pattern *            The pattern. */public 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);}/** * 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(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() {    sendAccessEvent(R.string.lockscreen_access_pattern_cell_added);    if (mOnPatternListener != null) {        mOnPatternListener.onPatternCellAdded(mPattern);    }}private void notifyPatternStarted() {    sendAccessEvent(R.string.lockscreen_access_pattern_start);    if (mOnPatternListener != null) {        mOnPatternListener.onPatternStart();    }}private void notifyPatternDetected() {    sendAccessEvent(R.string.lockscreen_access_pattern_detected);    if (mOnPatternListener != null) {        mOnPatternListener.onPatternDetected(mPattern);    }}private void notifyPatternCleared() {    sendAccessEvent(R.string.lockscreen_access_pattern_cleared);    if (mOnPatternListener != null) {        mOnPatternListener.onPatternCleared();    }}/** * Clear the pattern. */public void clearPattern() {    resetPattern();}/** * Reset all pattern state. */private void resetPattern() {    mPattern.clear();    clearPatternDrawLookup();    mPatternDisplayMode = DisplayMode.Correct;    invalidate();}/** * Clear the pattern lookup table. */private void clearPatternDrawLookup() {    for (int i = 0; i < 3; i++) {        for (int j = 0; j < 3; j++) {            mPatternDrawLookup[i][j] = false;        }    }}/** * Disable input (for instance when displaying a message that will timeout * so user doesn't get view into messy state). */public void disableInput() {    mInputEnabled = false;}/** * Enable input. */public void enableInput() {    mInputEnabled = true;}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {    final int width = w - getPaddingLeft() - getPaddingRight();    mSquareWidth = width / 3.0f;    final int height = h - getPaddingTop() - getPaddingBottom();    mSquareHeight = height / 3.0f;}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;}@Overrideprotected int getSuggestedMinimumWidth() {    // View should be large enough to contain 3 side-by-side target bitmaps    return 3 * mBitmapWidth;}@Overrideprotected int getSuggestedMinimumHeight() {    // View should be large enough to contain 3 side-by-side target bitmaps    return 3 * mBitmapWidth;}@Overrideprotected 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;    }    // Log.v(TAG, "LockPatternView dimensions: " + viewWidth + "x" +    // viewHeight);    setMeasuredDimension(viewWidth, viewHeight);}/** * 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 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;}private void addCellToPattern(Cell newCell) {    mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true;    mPattern.add(newCell);    notifyCellAdded();}// helper method to find which cell a point maps toprivate 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);}/** * Helper method to find the row that y falls into. *  * @param y *            The y coordinate * @return The row that y falls in, or -1 if it falls in no row. */private int getRowHit(float y) {    final float squareHeight = mSquareHeight;    float hitSize = squareHeight * mHitFactor;    float offset = getPaddingTop() + (squareHeight - hitSize) / 2f;    for (int i = 0; i < 3; i++) {        final float hitTop = offset + squareHeight * i;        if (y >= hitTop && y <= hitTop + hitSize) {            return i;        }    }    return -1;}/** * Helper method to find the column x fallis into. *  * @param x *            The x coordinate. * @return The column that x falls in, or -1 if it falls in no column. */private int getColumnHit(float x) {    final float squareWidth = mSquareWidth;    float hitSize = squareWidth * mHitFactor;    float offset = getPaddingLeft() + (squareWidth - hitSize) / 2f;    for (int i = 0; i < 3; i++) {        final float hitLeft = offset + squareWidth * i;        if (x >= hitLeft && x <= hitLeft + hitSize) {            return i;        }    }    return -1;}@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)@SuppressLint("NewApi")@Overridepublic boolean onHoverEvent(MotionEvent event) {    AccessibilityManager accessibilityManager = (AccessibilityManager) getContext()            .getSystemService(Context.ACCESSIBILITY_SERVICE);    if (accessibilityManager.isTouchExplorationEnabled()) {        final int action = event.getAction();        switch (action) {        case MotionEvent.ACTION_HOVER_ENTER:            event.setAction(MotionEvent.ACTION_DOWN);            break;        case MotionEvent.ACTION_HOVER_MOVE:            event.setAction(MotionEvent.ACTION_MOVE);            break;        case MotionEvent.ACTION_HOVER_EXIT:            event.setAction(MotionEvent.ACTION_UP);            break;        }        onTouchEvent(event);        event.setAction(action);    }    return super.onHoverEvent(event);}@Overridepublic 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:        if (mPatternInProgress) {            mPatternInProgress = false;            resetPattern();            notifyPatternCleared();        }        if (PROFILE_DRAWING) {            if (mDrawingProfilingStarted) {                Debug.stopMethodTracing();                mDrawingProfilingStarted = false;            }        }        return true;    }    return false;}@SuppressLint("NewApi")private void handleActionMove(MotionEvent event) {    // Handle all recent motion events so we don't skip any cells even when    // the device    // is busy...    final float radius = (mSquareWidth * mDiameterFactor * 0.5f);    final int historySize = event.getHistorySize();    mTmpInvalidateRect.setEmpty();    boolean invalidateNow = false;    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();        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 > DRAG_THRESHHOLD || dy > DRAG_THRESHHOLD) {            invalidateNow = true;        }        if (mPatternInProgress && patternSize > 0) {            final ArrayList<Cell> pattern = mPattern;            final Cell lastCell = pattern.get(patternSize - 1);            float lastCellCenterX = getCenterXForColumn(lastCell.column);            float lastCellCenterY = getCenterYForRow(lastCell.row);            // Adjust for drawn segment from last cell to (x,y). Radius            // accounts for line width.            float left = Math.min(lastCellCenterX, x) - radius;            float right = Math.max(lastCellCenterX, x) + radius;            float top = Math.min(lastCellCenterY, y) - radius;            float bottom = Math.max(lastCellCenterY, y) + radius;            // Invalidate between the pattern's new cell and the pattern's            // previous cell            if (hitCell != null) {                final float width = mSquareWidth * 0.5f;                final float height = mSquareHeight * 0.5f;                final float hitCellCenterX = getCenterXForColumn(hitCell.column);                final float hitCellCenterY = getCenterYForRow(hitCell.row);                left = Math.min(hitCellCenterX - width, left);                right = Math.max(hitCellCenterX + width, right);                top = Math.min(hitCellCenterY - height, top);                bottom = Math.max(hitCellCenterY + height, bottom);            }            // Invalidate between the pattern's last cell and the previous            // location            mTmpInvalidateRect.union(Math.round(left), Math.round(top), Math.round(right), Math.round(bottom));        }    }    mInProgressX = event.getX();    mInProgressY = event.getY();    // To save updates, we only invalidate if the user moved beyond a    // certain amount.    if (invalidateNow) {        mInvalidate.union(mTmpInvalidateRect);        invalidate(mInvalidate);        mInvalidate.set(mTmpInvalidateRect);    }}@SuppressLint("NewApi")private void sendAccessEvent(int resId) {    announceForAccessibility(getContext().getString(resId));}private void handleActionUp(MotionEvent event) {    // report pattern detected    if (!mPattern.isEmpty()) {        mPatternInProgress = false;        notifyPatternDetected();        invalidate();    }    if (PROFILE_DRAWING) {        if (mDrawingProfilingStarted) {            Debug.stopMethodTracing();            mDrawingProfilingStarted = false;        }    }}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 if (mPatternInProgress) {        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;    if (PROFILE_DRAWING) {        if (!mDrawingProfilingStarted) {            Debug.startMethodTracing("LockPatternDrawing");            mDrawingProfilingStarted = true;        }    }}private float getCenterXForColumn(int column) {    return getPaddingLeft() + column * mSquareWidth + mSquareWidth / 2f;}private float getCenterYForRow(int row) {    return getPaddingTop() + row * mSquareHeight + mSquareHeight / 2f;}@Overrideprotected 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;        }        // TODO: Infinite loop here...        invalidate();    }    final float squareWidth = mSquareWidth;    final float squareHeight = mSquareHeight;    float radius = (squareWidth * mDiameterFactor * 0.5f);    mPathPaint.setStrokeWidth(radius);    final Path currentPath = mCurrentPath;    currentPath.rewind();    // draw the circles    final int paddingTop = getPaddingTop();    final int paddingLeft = getPaddingLeft();    for (int i = 0; i < 3; i++) {        float topY = paddingTop + i * squareHeight;        // float centerY = getPaddingTop() + i * mSquareHeight +        // (mSquareHeight / 2);        for (int j = 0; j < 3; j++) {            float leftX = paddingLeft + j * squareWidth;            drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]);        }    }    // TODO: the path should be created and cached every time we hit-detect    // a cell    // 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 = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong);    // draw the arrows associated with the path (unless the user is in    // progress, and    // we are in stealth mode)    boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;    mPaint.setFilterBitmap(true); // draw with higher quality since we                                    // render with transforms    if (drawPath) {        for (int i = 0; i < count - 1; i++) {            Cell cell = pattern.get(i);            Cell next = pattern.get(i + 1);            // only draw the part of the pattern stored in            // the lookup table (this is only different in the case            // of animation).            if (!drawLookup[next.row][next.column]) {                break;            }            float leftX = paddingLeft + cell.column * squareWidth;            float topY = paddingTop + cell.row * squareHeight;            drawArrow(canvas, leftX, topY, cell, next);        }    }    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);        }        canvas.drawPath(currentPath, mPathPaint);    }    mPaint.setFilterBitmap(oldFlag); // restore default flag}private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) {    boolean green = mPatternDisplayMode != DisplayMode.Wrong;    final int endRow = end.row;    final int startRow = start.row;    final int endColumn = end.column;    final int startColumn = start.column;    // offsets for centering the bitmap in the cell    final int offsetX = ((int) mSquareWidth - mBitmapWidth) / 2;    final int offsetY = ((int) mSquareHeight - mBitmapHeight) / 2;    // compute transform to place arrow bitmaps at correct angle inside    // circle.    // This assumes that the arrow image is drawn at 12:00 with it's top    // edge    // coincident with the circle bitmap's top edge.    Bitmap arrow = green ? mBitmapArrowGreenUp : mBitmapArrowRedUp;    final int cellWidth = mBitmapWidth;    final int cellHeight = mBitmapHeight;    // the up arrow bitmap is at 12:00, so find the rotation from x axis and    // add 90 degrees.    final float theta = (float) Math.atan2((double) (endRow - startRow), (double) (endColumn - startColumn));    final float angle = (float) Math.toDegrees(theta) + 90.0f;    // compose matrix    float sx = Math.min(mSquareWidth / mBitmapWidth, 1.0f);    float sy = Math.min(mSquareHeight / mBitmapHeight, 1.0f);    mArrowMatrix.setTranslate(leftX + offsetX, topY + offsetY); // transform                                                                // to cell                                                                // position    mArrowMatrix.preTranslate(mBitmapWidth / 2, mBitmapHeight / 2);    mArrowMatrix.preScale(sx, sy);    mArrowMatrix.preTranslate(-mBitmapWidth / 2, -mBitmapHeight / 2);    mArrowMatrix.preRotate(angle, cellWidth / 2.0f, cellHeight / 2.0f); // rotate                                                                        // about                                                                        // cell                                                                        // center    mArrowMatrix.preTranslate((cellWidth - arrow.getWidth()) / 2.0f, 0.0f); // translate                                                                            // to                                                                            // 12:00                                                                            // pos    canvas.drawBitmap(arrow, mArrowMatrix, mPaint);}/** * @param canvas * @param leftX * @param topY * @param partOfPattern *            Whether this circle is part of the pattern. */private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) {    Bitmap outerCircle;    Bitmap innerCircle;    if (!partOfPattern || (mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong)) {        // unselected circle        outerCircle = mBitmapCircleDefault;        innerCircle = mBitmapBtnDefault;    } else if (mPatternInProgress) {        // user is in middle of drawing a pattern        outerCircle = mBitmapCircleGreen;        innerCircle = mBitmapBtnTouched;    } else if (mPatternDisplayMode == DisplayMode.Wrong) {        // the pattern is wrong        outerCircle = mBitmapCircleRed;        innerCircle = mBitmapBtnDefault;    } else if (mPatternDisplayMode == DisplayMode.Correct || mPatternDisplayMode == DisplayMode.Animate) {        // the pattern is correct        outerCircle = mBitmapCircleGreen;        innerCircle = mBitmapBtnDefault;    } 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 / 2, mBitmapHeight / 2);    mCircleMatrix.preScale(sx, sy);    mCircleMatrix.preTranslate(-mBitmapWidth / 2, -mBitmapHeight / 2);    canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint);    canvas.drawBitmap(innerCircle, mCircleMatrix, mPaint);}@Overrideprotected Parcelable onSaveInstanceState() {    Parcelable superState = super.onSaveInstanceState();    return new SavedState(superState, patternToString(mPattern), mPatternDisplayMode.ordinal(), mInputEnabled,            mInStealthMode, mEnableHapticFeedback);}@Overrideprotected 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();}/** * Deserialize a pattern. *  * @param string *            The pattern serialized with {@link #patternToString} * @return The pattern. */public static 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 / 3, b % 3));    }    return result;}/** * Serialize a pattern. *  * @param pattern *            The pattern. * @return The pattern in string form. */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() * 3 + cell.getColumn());    }    return new String(res);}/** * The parecelable for saving and restoring a lock pattern view. */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);    }    public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {        public SavedState createFromParcel(Parcel in) {            return new SavedState(in);        }        public SavedState[] newArray(int size) {            return new SavedState[size];        }    };}

}
dit

原创粉丝点击