Java tutorial
/* Copyright (c) 2013 Richard G. Todd. * Licensed under the terms of the GNU General Public License (GPL) Version 3.0. */ package com.richtodd.android.quiltdesign.block; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.json.JSONException; import org.json.JSONObject; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Paint.Cap; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.PointF; import android.graphics.Shader.TileMode; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.PathShape; import android.os.Parcel; import android.os.Parcelable; public class PaperPiecedBlockPiece implements Parcelable { // private static final String TAG = "PaperPiecedBlockPiece"; private static final PointF DEFAULT_POINT = new PointF(0, 0); private enum Edges { TOP, RIGHT, BOTTOM, LEFT } // // Fields // private UUID m_id; private PointF m_from; private PointF m_to; private int m_color; private int m_cachedWidth; private int m_cachedHeight; private int m_cachedShadowStrokeWidth; private List<PointF> m_cachedPoints; private Path m_cachedPath; private Drawable m_cachedDrawable; private Drawable m_cachedBlackWhiteDrawable; private Paint m_cachedShadowPaint; // // Constructors // public PaperPiecedBlockPiece() { m_id = UUID.randomUUID(); m_from = DEFAULT_POINT; m_to = DEFAULT_POINT; m_color = 0; m_cachedPoints = null; m_cachedPath = null; m_cachedDrawable = null; m_cachedBlackWhiteDrawable = null; m_cachedShadowPaint = null; } public PaperPiecedBlockPiece(PointF from, PointF to, int color) { if (from.x < 0f || from.x > 1f || from.y < 0f || from.y > 1f) { throw new IllegalArgumentException("Invalid from: " + from); } if (to.x < 0f || to.x > 1f || to.y < 0f || to.y > 1f) { throw new IllegalArgumentException("Invalid to: " + to); } m_id = UUID.randomUUID(); m_from = from; m_to = to; m_color = color; m_cachedPoints = null; m_cachedPath = null; m_cachedDrawable = null; m_cachedBlackWhiteDrawable = null; m_cachedShadowPaint = null; } private PaperPiecedBlockPiece(Parcel in) { m_id = (UUID) in.readSerializable(); m_from = in.readParcelable(PointF.class.getClassLoader()); m_to = in.readParcelable(PointF.class.getClassLoader()); m_color = in.readInt(); m_cachedPoints = null; m_cachedPath = null; m_cachedDrawable = null; m_cachedBlackWhiteDrawable = null; m_cachedShadowPaint = null; } // // Public // public UUID getId() { return m_id; } public PointF getFrom() { return m_from; } public PointF getBlockFrom(int width, int height) { return new PointF(m_from.x * width, m_from.y * height); } public PointF getTo() { return m_to; } public PointF getBlockTo(int width, int height) { return new PointF(m_to.x * width, m_to.y * height); } public int getColor() { return m_color; } // // Package-Private // void setFrom(PointF from) { if (from.x < 0f || from.x > 1f || from.y < 0f || from.y > 1f) { throw new IllegalArgumentException("Invalid from: " + from); } if (!m_from.equals(from)) { m_from = new PointF(from.x, from.y); clearCache(); } } void setTo(PointF to) { if (to.x < 0f || to.x > 1f || to.y < 0f || to.y > 1f) { throw new IllegalArgumentException("Invalid to: " + to); } if (!m_to.equals(to)) { m_to = new PointF(to.x, to.y); clearCache(); } } void setColor(int color) { if (color != m_color) { m_color = color; clearCache(); } } void flipHorizontal() { float flippedFromX = 1f - m_from.x; float flippedFromY = m_from.y; float flippedToX = 1f - m_to.x; float flippedToY = m_to.y; m_from.set(flippedToX, flippedToY); m_to.set(flippedFromX, flippedFromY); clearCache(); } void flipVertical() { float flippedFromX = m_from.x; float flippedFromY = 1f - m_from.y; float flippedToX = m_to.x; float flippedToY = 1f - m_to.y; m_from.set(flippedToX, flippedToY); m_to.set(flippedFromX, flippedFromY); clearCache(); } void rotateClockwise() { float rotatedFromX = -m_from.y + 1f; float rotatedFromY = m_from.x; m_from.set(rotatedFromX, rotatedFromY); float rotatedToX = -m_to.y + 1f; float rotatedToY = m_to.x; m_to.set(rotatedToX, rotatedToY); clearCache(); } void rotateCounterclockwise() { float rotatedFromX = m_from.y; float rotatedFromY = -m_from.x + 1f; m_from.set(rotatedFromX, rotatedFromY); float rotatedToX = m_to.y; float rotatedToY = -m_to.x + 1f; m_to.set(rotatedToX, rotatedToY); clearCache(); } boolean containsPoint(PointF point, int width, int height) { boolean oddNodes = false; List<PointF> points = getPoints(width, height); int j = points.size() - 1; for (int i = 0; i < points.size(); i++) { PointF from = points.get(i); PointF to = points.get(j); // @formatter:off if ((from.y < point.y && point.y <= to.y || to.y < point.y && point.y <= from.y) && (from.x <= point.x || to.x <= point.x)) { if (from.x + (point.y - from.y) / (to.y - from.y) * (to.x - from.x) < point.x) { oddNodes = !oddNodes; } } // @formatter:on j = i; } return oddNodes; } void draw(Canvas canvas, RenderOptions renderOptions) { if (renderOptions.getRenderShadow()) { renderShadow(canvas, renderOptions); } getDrawable(renderOptions).draw(canvas); } // // JSON // JSONObject createJSONObject() throws JSONException { JSONObject jsonPiece = new JSONObject(); jsonPiece.put("from_x", m_from.x); jsonPiece.put("from_y", m_from.y); jsonPiece.put("to_x", m_to.x); jsonPiece.put("to_y", m_to.y); jsonPiece.put("color", m_color); return jsonPiece; } static final PaperPiecedBlockPiece createFromJSONObject(JSONObject jsonObject) throws JSONException { PointF from = new PointF((float) jsonObject.getDouble("from_x"), (float) jsonObject.getDouble("from_y")); PointF to = new PointF((float) jsonObject.getDouble("to_x"), (float) jsonObject.getDouble("to_y")); int color = jsonObject.getInt("color"); PaperPiecedBlockPiece piece = new PaperPiecedBlockPiece(from, to, color); return piece; } // // Parcelable // @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeSerializable(m_id); out.writeParcelable(m_from, 0); out.writeParcelable(m_to, 0); out.writeInt(m_color); } public static final Parcelable.Creator<PaperPiecedBlockPiece> CREATOR = new Creator<PaperPiecedBlockPiece>() { @Override public PaperPiecedBlockPiece[] newArray(int size) { return new PaperPiecedBlockPiece[size]; } @Override public PaperPiecedBlockPiece createFromParcel(Parcel source) { return new PaperPiecedBlockPiece(source); } }; // // Private // private void validateCacheWidthHeight(int width, int height) { if (m_cachedWidth != width || m_cachedHeight != height) { clearCache(); m_cachedWidth = width; m_cachedHeight = height; } } private void validateCacheShawdowStrokeWidth(int shawdowStrokeWidth) { if (shawdowStrokeWidth != m_cachedShadowStrokeWidth) { clearCache(); m_cachedShadowStrokeWidth = shawdowStrokeWidth; } } private void clearCache() { m_cachedPoints = null; m_cachedPath = null; m_cachedDrawable = null; m_cachedBlackWhiteDrawable = null; m_cachedShadowPaint = null; } public List<PointF> createPoints(int width, int height) { ArrayList<PointF> points = new ArrayList<PointF>(5); int right = width; int bottom = height; PointF pieceFrom = getFrom(); PointF pieceTo = getTo(); Edges edgeFrom = getEdge(pieceFrom); Edges edgeTo = getEdge(pieceTo); if (edgeFrom == edgeTo) { // Block encompasses entire rectangle. points.add(new PointF(0, 0)); points.add(new PointF(right, 0)); points.add(new PointF(right, bottom)); points.add(new PointF(0, bottom - 0)); } else { PointF blockFrom = getBlockFrom(width, height); PointF blockTo = getBlockTo(width, height); points.add(blockFrom); points.add(blockTo); while (edgeTo != edgeFrom) { switch (edgeTo) { case TOP: points.add(new PointF(right, 0)); edgeTo = Edges.RIGHT; break; case RIGHT: points.add(new PointF(right, bottom)); edgeTo = Edges.BOTTOM; break; case BOTTOM: points.add(new PointF(0, bottom)); edgeTo = Edges.LEFT; break; case LEFT: points.add(new PointF(0, 0)); edgeTo = Edges.TOP; break; } } } return points; } private List<PointF> getPoints(int width, int height) { validateCacheWidthHeight(width, height); if (m_cachedPoints == null) { m_cachedPoints = createPoints(width, height); } return m_cachedPoints; } private Path createPath(int width, int height) { // Log.i(TAG, "pathCreate(" + width + "," + height + ")"); Path path = null; for (PointF point : getPoints(width, height)) { // Log.i(TAG, " point = " + point.x + ", " + point.y); if (path == null) { path = new Path(); path.moveTo(point.x, point.y); } else { path.lineTo(point.x, point.y); } } if (path != null) { path.close(); } return path; } private Path getPath(int width, int height) { validateCacheWidthHeight(width, height); if (m_cachedPath == null) { m_cachedPath = createPath(width, height); } return m_cachedPath; } private Drawable createDrawable(RenderOptions renderOptions) { PathShape pathShape = new PathShape(getPath(renderOptions.getWidth(), renderOptions.getHeight()), renderOptions.getWidth(), renderOptions.getHeight()); if (renderOptions.getRenderStyle() == RenderStyles.BlackWhite) { ShapeDrawable strokeDrawable = new ShapeDrawable(pathShape); strokeDrawable.setBounds(0, 0, renderOptions.getWidth(), renderOptions.getHeight()); strokeDrawable.getPaint().setColor(Color.BLACK); strokeDrawable.getPaint().setStyle(Paint.Style.STROKE); strokeDrawable.getPaint().setStrokeWidth(renderOptions.getStrokeWidth()); ShapeDrawable fillDrawable = new ShapeDrawable(pathShape); fillDrawable.setBounds(0, 0, renderOptions.getWidth(), renderOptions.getHeight()); fillDrawable.getPaint().setColor(Color.WHITE); fillDrawable.getPaint().setStyle(Paint.Style.FILL); Drawable[] layers = new Drawable[2]; layers[0] = fillDrawable; layers[1] = strokeDrawable; LayerDrawable drawable = new LayerDrawable(layers); return drawable; } else { ShapeDrawable drawable = new ShapeDrawable(pathShape); drawable.getPaint().setColor(getColor()); drawable.getPaint().setStyle(Paint.Style.FILL); return drawable; } } private Drawable getDrawable(RenderOptions renderOptions) { validateCacheWidthHeight(renderOptions.getWidth(), renderOptions.getHeight()); Drawable drawable; if (renderOptions.getRenderStyle() == RenderStyles.BlackWhite) { if (m_cachedBlackWhiteDrawable == null) { m_cachedBlackWhiteDrawable = createDrawable(renderOptions); } drawable = m_cachedBlackWhiteDrawable; } else { if (m_cachedDrawable == null) { m_cachedDrawable = createDrawable(renderOptions); } drawable = m_cachedDrawable; } drawable.setBounds(renderOptions.getLeft(), renderOptions.getTop(), renderOptions.getRight(), renderOptions.getBottom()); return drawable; } private void renderShadow(Canvas canvas, RenderOptions renderOptions) { List<PointF> points = getPoints(renderOptions.getWidth(), renderOptions.getHeight()); PointF fromPoint = points.get(0); PointF toPoint = points.get(1); if (fromPoint.y != 0f || toPoint.y != 0f) { float fromX = renderOptions.getLeft() + fromPoint.x; float fromY = renderOptions.getTop() + fromPoint.y; float toX = renderOptions.getLeft() + toPoint.x; float toY = renderOptions.getTop() + toPoint.y; canvas.drawLine(fromX, fromY, toX, toY, getShadowPaint(renderOptions.getLeft(), renderOptions.getTop(), renderOptions.getWidth(), renderOptions.getHeight(), 20)); } } private Paint createShadowPaint(int left, int top, int width, int height, int shadowStrokeWidth) { List<PointF> points = getPoints(width, height); PointF fromPoint = new PointF(left + points.get(0).x, top + points.get(0).y); PointF toPoint = new PointF(left + points.get(1).x, top + points.get(1).y); PointF midpoint = new PointF((fromPoint.x + toPoint.x) * 0.5f, (fromPoint.y + toPoint.y) * 0.5f); PointF vector = new PointF(fromPoint.x - midpoint.x, fromPoint.y - midpoint.y); PointF rotatedVector = new PointF(vector.y, -vector.x); float vectorLength = (float) Math.sqrt((vector.x * vector.x) + (vector.y * vector.y)); PointF shadowVector = new PointF(rotatedVector.x * (shadowStrokeWidth * 0.5f) / vectorLength, rotatedVector.y * (shadowStrokeWidth * 0.5f) / vectorLength); LinearGradient gradient = new LinearGradient(midpoint.x, midpoint.y, midpoint.x + shadowVector.x, midpoint.y + shadowVector.y, Color.argb(100, 0, 0, 0), Color.argb(0, 0, 0, 0), TileMode.MIRROR); Paint paint = new Paint(); paint.setStyle(Style.STROKE); paint.setShader(gradient); paint.setStrokeWidth(shadowStrokeWidth); paint.setStrokeCap(Cap.SQUARE); return paint; } private Paint getShadowPaint(int left, int top, int width, int height, int shadowStrokeWidth) { validateCacheWidthHeight(width, height); validateCacheShawdowStrokeWidth(shadowStrokeWidth); if (m_cachedShadowPaint == null) { m_cachedShadowPaint = createShadowPaint(left, top, width, height, shadowStrokeWidth); } return m_cachedShadowPaint; } private Edges getEdge(PointF piecePoint) { if (piecePoint.y == 0f) return Edges.TOP; if (piecePoint.x == 1f) return Edges.RIGHT; if (piecePoint.y == 1f) return Edges.BOTTOM; if (piecePoint.x == 0f) return Edges.LEFT; throw new IllegalArgumentException(); } }