Created
March 17, 2017 19:41
-
-
Save murdly/f5ab479514fb4ee1301d084abec74617 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ScratchView extends View { | |
private static final float DRAWING_PAINT_STROKE_WIDTH = 75f; | |
private static final int DRAWING_MOVE_TOLERANCE = 10; | |
private static final double SCRATCHED_RATIO = 90.0; | |
private static final float DEFAULT_SCALE = 1f; | |
private Path mDrawingPath; | |
private Paint mDrawingPaint; | |
private Paint mBitmapPaint; | |
private Canvas mDrawingCanvas; | |
private Bitmap mDrawingBitmap; | |
private Bitmap mUndercoverBitmap; | |
private int mLastX, mLastY; | |
private float mScale; | |
private int mViewWidth, mViewHeight; | |
// true if we didn't touch the view yet | |
private boolean mClear; | |
private boolean mTouchable; | |
private OnScratchedCallBack mCallBack; | |
private boolean mCalled = false; | |
public interface OnScratchedCallBack { | |
void onScratched(); | |
} | |
public ScratchView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ScratchView, 0, 0); | |
try { | |
this.mScale = a.getFloat(R.styleable.ScratchView_scale, DEFAULT_SCALE); | |
int id = a.getResourceId(R.styleable.ScratchView_undercover, -1); | |
this.mUndercoverBitmap = BitmapFactory.decodeResource(context.getResources(), id); | |
Drawable cover = a.getDrawable(R.styleable.ScratchView_cover); | |
setBackground(cover); | |
} finally { | |
a.recycle(); | |
} | |
init(); | |
} | |
public ScratchView(Context context, Bitmap undercoverBitmap, Drawable cover) { | |
this(context, null, undercoverBitmap, DEFAULT_SCALE, cover); | |
} | |
public ScratchView(Context context, Bitmap undercoverBitmap, float scale, Drawable cover) { | |
this(context, null, undercoverBitmap, scale, cover); | |
} | |
public ScratchView(Context context, AttributeSet attrs, Bitmap undercoverBitmap, float scale, Drawable | |
cover) { | |
this(context, attrs, 0, undercoverBitmap, scale, cover); | |
} | |
public ScratchView(Context context, AttributeSet attrs, int defStyle, Bitmap undercoverBitmap, float scale, | |
Drawable cover) { | |
super(context, attrs, defStyle); | |
this.mUndercoverBitmap = undercoverBitmap; | |
this.mScale = scale; | |
setBackground(cover); | |
init(); | |
} | |
private void init() { | |
this.mClear = true; | |
this.mTouchable = true; | |
mBitmapPaint = new Paint(); | |
mDrawingPath = new Path(); | |
mDrawingPaint = new Paint(Paint.ANTI_ALIAS_FLAG); | |
mDrawingPaint.setStyle(Paint.Style.STROKE); | |
mDrawingPaint.setStrokeCap(Paint.Cap.ROUND); | |
mDrawingPaint.setStrokeJoin(Paint.Join.ROUND); | |
mDrawingPaint.setColor(Color.BLACK); | |
mDrawingPaint.setStrokeWidth(DRAWING_PAINT_STROKE_WIDTH * mScale); | |
if (android.os.Build.VERSION.SDK_INT >= 11) | |
setLayerType(LAYER_TYPE_SOFTWARE, mBitmapPaint); | |
} | |
@Override | |
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
super.onSizeChanged(w, h, oldw, oldh); | |
mViewWidth = w; | |
mViewHeight = h; | |
setupDrawingCanvasAndBitmap(); | |
createShader(); | |
} | |
private void setupDrawingCanvasAndBitmap() { | |
mUndercoverBitmap = Bitmap.createScaledBitmap(mUndercoverBitmap, mViewWidth, mViewHeight, true); | |
mDrawingBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.ARGB_8888); | |
mDrawingCanvas = new Canvas(mDrawingBitmap); | |
} | |
@Override | |
public boolean onTouchEvent(MotionEvent event) { | |
if (!mTouchable) return false; | |
int action = event.getAction(); | |
if (action == MotionEvent.ACTION_DOWN) | |
drawPathStart((int) event.getX(), (int) event.getY()); | |
else if (action == MotionEvent.ACTION_MOVE) | |
drawPathMove((int) event.getX(), (int) event.getY()); | |
else if (action == MotionEvent.ACTION_UP) | |
drawPathEnd(); | |
else | |
return super.onTouchEvent(event); | |
if (mCallBack != null && !mCalled) { | |
double progress = countScratchedRatio(); | |
if (progress >= SCRATCHED_RATIO) { | |
mCallBack.onScratched(); | |
mCalled = true; | |
} | |
} | |
return true; | |
} | |
private void drawPathStart(int x, int y) { | |
if (mClear) mClear = false; | |
mDrawingPath.reset(); | |
mDrawingPath.moveTo(x, y); | |
mDrawingCanvas.drawPoint(x, y, mDrawingPaint); | |
mLastX = x; | |
mLastY = y; | |
invalidate(); | |
} | |
private void drawPathMove(int x, int y) { | |
if (!checkTolerance(x, y)) return; | |
mDrawingPath.quadTo(mLastX, mLastY, (x + mLastX) / 2.0f, (y + mLastY) / 2.0f); | |
mDrawingCanvas.drawPath(mDrawingPath, mDrawingPaint); | |
mLastX = x; | |
mLastY = y; | |
invalidate(); | |
} | |
private void drawPathEnd() { | |
mDrawingPath.lineTo(mLastX, mLastY); | |
mDrawingCanvas.drawPath(mDrawingPath, mDrawingPaint); | |
mDrawingPath.reset(); | |
invalidate(); | |
} | |
private boolean checkTolerance(int x, int y) { | |
return (Math.abs(mLastX - x) > DRAWING_MOVE_TOLERANCE * mScale | |
|| Math.abs(mLastY - y) > DRAWING_MOVE_TOLERANCE * mScale); | |
} | |
@Override | |
protected void onDraw(Canvas canvas) { | |
super.onDraw(canvas); | |
if (mDrawingBitmap == null || mUndercoverBitmap == null) return; | |
canvas.drawBitmap(mDrawingBitmap.extractAlpha(), 0, 0, mBitmapPaint); | |
} | |
private void createShader() { | |
if (mUndercoverBitmap == null) return; | |
Shader targetShader = createShader(mUndercoverBitmap); | |
mBitmapPaint.setShader(targetShader); | |
} | |
private static Shader createShader(Bitmap b) { | |
return new BitmapShader(b, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); | |
} | |
private double countScratchedRatio() { | |
if (mDrawingBitmap == null) return 0; | |
int stroke = (int) mDrawingPaint.getStrokeWidth(); | |
int scratched = 0; | |
for (int i = 0; i < mViewWidth; i += stroke) { | |
for (int j = 0; j < mViewHeight; j += stroke) { | |
if (mDrawingBitmap.getPixel(i, j) != Color.TRANSPARENT) | |
scratched++; | |
} | |
} | |
double ratio = (double) scratched / ((double) (mViewWidth * mViewHeight) / (stroke * stroke)); | |
return round(ratio * 100, 2); | |
} | |
private double round(double value, int places) { | |
if (places < 0) throw new IllegalArgumentException(); | |
BigDecimal bd = new BigDecimal(value); | |
bd = bd.setScale(places, RoundingMode.HALF_UP); | |
return bd.doubleValue(); | |
} | |
public void setOnScratchedListener(OnScratchedCallBack callback) { | |
mCallBack = callback; | |
} | |
public boolean isViewClear() { | |
return mClear; | |
} | |
public void setTouchable(boolean mTouchable) { | |
this.mTouchable = mTouchable; | |
} | |
public boolean isTouchable() { | |
return mTouchable; | |
} | |
public void releaseView() { | |
if (mUndercoverBitmap != null) { | |
mUndercoverBitmap.recycle(); | |
mUndercoverBitmap = null; | |
} | |
if (mDrawingBitmap != null) { | |
mDrawingBitmap.recycle(); | |
mDrawingBitmap = null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment