Skip to content

Instantly share code, notes, and snippets.

@murdly
Created March 17, 2017 19:41
Show Gist options
  • Save murdly/f5ab479514fb4ee1301d084abec74617 to your computer and use it in GitHub Desktop.
Save murdly/f5ab479514fb4ee1301d084abec74617 to your computer and use it in GitHub Desktop.
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