Skip to content

Instantly share code, notes, and snippets.

@peterkos
Created May 12, 2019 22:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peterkos/a1084c5b5af58c5d0360ef8e3ac361c5 to your computer and use it in GitHub Desktop.
Save peterkos/a1084c5b5af58c5d0360ef8e3ac361c5 to your computer and use it in GitHub Desktop.
CSE 340, Ex 1. Doodle - Snake
/*
* RESOURCES:
* http://www.curious-creature.com/2013/12/21/android-recipe-4-path-tracing/
* (Implementation came from this SO link, however I derived something similar w/ ViewAnimator first)
* https://stackoverflow.com/questions/50674847/how-to-draw-several-lines-slowly-in-constant-velocity-on-canvas-by-android
* https://stackoverflow.com/questions/15010156/android-using-paint-to-draw-dashed-line-with-two-different-colors
*/
// Runner class omitted
// We need to create our own Path.
// Why? Well, in order to create a tracing effect,
// the ObjectAnimator needs to modify the phase parameter of the DashPathEffect.
// In the chaos of being intelligent, no Android developer added a setPhase() method to it.
// Alas, we need to simulate this method in an effective wrapper around Path.
class SnakePath extends View {
Path snakePath;
Path foodDotsBasePath; // Duplicate base of snakePath
Path altSnakePath; // Effect of "Eating" pellets
Path snakeOutlinePath; // Outline of snake path to follow
Paint snakePaint;
Paint foodPaint;
Paint altSnakePaint;
Paint snakeOutlinePaint;
float length;
public SnakePath(Context context) {
super(context);
}
public void init() {
snakePath = new Path();
snakePath.moveTo(0, 600);
snakePath.lineTo(100, 600);
snakePath.lineTo(100, 2000);
snakePath.lineTo(1000, 2000);
snakePath.lineTo(1000, 1800);
snakePath.lineTo(300, 1800);
snakePath.lineTo(300, 1000);
snakePath.lineTo(500, 1000);
snakePath.lineTo(500, 1600);
snakePath.lineTo(1200, 1600);
snakePath.lineTo(1200, 2000);
snakePath.lineTo(1300, 2000);
snakePath.lineTo(1300, 1400);
snakePath.lineTo(700, 1400);
snakePath.lineTo(700, 800);
snakePath.lineTo(300, 800);
snakePath.lineTo(300, 600);
snakePath.lineTo(900, 600);
snakePath.lineTo(900, 1200);
snakePath.lineTo(1100, 1200);
snakePath.lineTo(1100, 600);
snakePath.lineTo(PHONE_DIMS.x, 600);
// Configure paint of snake
snakePaint = new Paint();
snakePaint.setStrokeWidth(18);
snakePaint.setColor(Color.BLUE);
snakePaint.setStyle(Paint.Style.STROKE);
// Now, we'll use the DashPathEffect to *setup* a trace effect
PathMeasure snakeLength = new PathMeasure(snakePath, false);
length = snakeLength.getLength();
// Finally, build the animator.
ObjectAnimator animator1 = ObjectAnimator.ofFloat(this, "phase", length, 0.0f);
animator1.setInterpolator(new LinearInterpolator());
animator1.setDuration(13000);
animator1.setRepeatCount(ObjectAnimator.INFINITE);
animator1.setRepeatMode(ObjectAnimator.RESTART);
// Here's another path that is layered under the snake, to simulate eating the pellets.
// (offsetting wouldn't work)
altSnakePath = new Path(snakePath);
altSnakePaint = new Paint();
altSnakePaint.setStrokeWidth(40f);
altSnakePaint.setColor(Color.parseColor("#FAFAFA"));
altSnakePaint.setStyle(Paint.Style.STROKE);
// In addition, here is another path to outline the path the snake will take.
// This helps the dots look more... aligned.
snakeOutlinePath = new Path(snakePath);
snakeOutlinePaint = new Paint();
snakeOutlinePaint.setStrokeWidth(4);
snakeOutlinePaint.setStyle(Paint.Style.STROKE);
// Now, as the snake eats things, we can use the PathDashPathEffect to stamp these
// along the snake's travel path, with no extra positioning required!
// (I can't figure out how to add multiple pathEffects :c )
// Copy the base path as duplicate
foodDotsBasePath = new Path(snakePath);
// These are the actual dots -- foodDotsPath acts as a base
Path foodDotsPath = new Path();
foodDotsPath.addCircle(0f, 0f, 20f, Path.Direction.CW);
// Apply the stamped dash effect
PathDashPathEffect dotEffect = new PathDashPathEffect(foodDotsPath, length / 14, 2000f, PathDashPathEffect.Style.TRANSLATE);
// Configure food paint
foodPaint = new Paint();
foodPaint.setPathEffect(dotEffect);
foodPaint.setStyle(Paint.Style.STROKE);
foodPaint.setColor(Color.RED);
// Start the animator as now both snakePath and foodDotsPath (which it controls)
// have been instantiated.
animator1.start();
}
public void setPhase(float phase) {
// Since we don't have a real setPhase() method in the PathEffect (grr), we'll
// have to simulate it by re-instantiating a new PaintEffect with each new phase
// we want to assign.
int lengthDiv = 2;
snakePaint.setPathEffect(new DashPathEffect(new float[] {length / lengthDiv, length / lengthDiv}, phase));
altSnakePaint.setPathEffect(new DashPathEffect(new float[] {length / lengthDiv, length / lengthDiv}, phase));
// Now, we force onDraw() to call, redrawing the path!
invalidate();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// The order of the below paths matters!
canvas.drawPath(foodDotsBasePath, foodPaint);
canvas.drawPath(altSnakePath, altSnakePaint);
// This line draws the background path for debugging.
// canvas.drawPath(snakeOutlinePath, snakeOutlinePaint);
canvas.drawPath(snakePath, snakePaint);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment