Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@dandc87
Created January 11, 2014 23:50
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dandc87/8378533 to your computer and use it in GitHub Desktop.
Save dandc87/8378533 to your computer and use it in GitHub Desktop.
Useful View to create a scratch-able surface for your lottery apps.
package com.dandc87.scratchnsniff;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by dandc87 on 1/11/14.
*/
public class ScratchView extends View {
public static interface OnScratchCallback {
/**
* Get stats for scratch coverage.
* <br/><br/>
* Be aware areaCovered uses path BOUNDS so if the user
* draws a square around the view, it will be near 100%.
* <br/><br/>
* A value of 1 is good for coverage.
*
* @param areaCovered [area of path bounds] / [view area]
* @param coverage [path length] * [scratch stroke width] / [area of path bounds]
*/
public void onScratch(float areaCovered, float coverage);
}
private Bitmap mFillCache;
private Canvas mFillCanvas;
private Drawable mScratchSurface;
private final Path mNullPath = new Path();
private final Paint mNullPaint = new Paint();
private final RectF mPathBounds = new RectF();
private float mViewArea = 0f;
private final PathMeasure measure = new PathMeasure();
private OnScratchCallback mScratchCallback = null;
public ScratchView(Context context) {
super(context);
init();
}
public ScratchView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ScratchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//We need to be focusable if we want to scratch
setFocusableInTouchMode(true);
//Setup paint with ProterDuff Mode Clear
mNullPaint.setColor(Color.TRANSPARENT);
mNullPaint.setStyle(Paint.Style.STROKE);
mNullPaint.setStrokeWidth(64);
mNullPaint.setStrokeCap(Paint.Cap.ROUND);
mNullPaint.setStrokeJoin(Paint.Join.ROUND);
mNullPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
//Initialize with a gray surface
setScratchSurfaceDrawable(new ColorDrawable(Color.GRAY));
}
/**
* Set the scratching surface drawable
* @param d Drawable to use as the scratching surface. Cannot be null
*/
public void setScratchSurfaceDrawable(Drawable d) {
if(d == null) throw new NullPointerException("Scratch Surface can not be null");
mScratchSurface = d;
mScratchSurface.setBounds(
getPaddingLeft(),
getPaddingTop(),
getWidth() - getPaddingRight(),
getHeight() - getPaddingBottom()
);
mViewArea = getWidth() * getHeight() + 0.1f;
postInvalidate();
}
/**
*
* @return The scratch width in pixels
*/
public float getScratchWidth() {
return mNullPaint.getStrokeWidth();
}
/**
*
* @param w The scratch width in pixels
*/
public void setScratchWidth(float w) {
mNullPaint.setStrokeWidth(w);
}
/**
* Get stats for scratch coverage.
* <br/><br/>
* Be aware areaCovered uses path BOUNDS so if the user
* draws a square around the view, it will be near 1.
* <br/><br/>
* A value of 1 is good for coverage.
*
* @param cb OnScratchCallback instance, or null to clear.
*/
public void setOnScratchCallback(OnScratchCallback cb) {
mScratchCallback = cb;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
final Drawable surface = mScratchSurface;
if(surface != null) {
//Honor padding values
mScratchSurface.setBounds(
getPaddingLeft(),
getPaddingTop(),
w - getPaddingRight(),
h - getPaddingBottom()
);
}
//Cache view area calculation
mViewArea = w * h;
//Setup new Surface bitmap to clear with scratch path
if(mFillCanvas != null) mFillCache.recycle();
mFillCache = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mFillCanvas = new Canvas(mFillCache);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//New scratch line
if(event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mNullPath.moveTo(event.getX(), event.getY());
mNullPath.lineTo(event.getX(), event.getY());
}
//More of a scratch
else if(event.getActionMasked() == MotionEvent.ACTION_MOVE) {
mNullPath.lineTo(event.getX(), event.getY());
}
//Do some stats for a callback, if there is one
final OnScratchCallback cb = mScratchCallback;
if(cb != null) {
mNullPath.computeBounds(mPathBounds, false);
float pathArea = mPathBounds.width() * mPathBounds.height();
measure.setPath(mNullPath, false);
cb.onScratch(pathArea / mViewArea, measure.getLength() * mNullPaint.getStrokeWidth() / pathArea);
}
//More scratching, so we need to repaint
postInvalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Clear out old cruft
mFillCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//Draw our surface, nice an pristine
final Drawable surface = mScratchSurface;
if(surface != null) {
surface.draw(mFillCanvas);
}
//Scratch the surface
mFillCanvas.drawPath(mNullPath, mNullPaint);
//Draw the scratched surface to the screen
canvas.drawBitmap(mFillCache, 0, 0, null);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment