To prevent you from getting discouraged, I'll share my solution with you. Here's the challange: Draw arbitrary shapes (splashes) in different colors in a view.(using canvas, drawing methods, paints etc.) Be able to drag and drop it around the view. If the splashes intersects, the intersection should be colored according to the color mixture of the two parts. Then i should be able to read the pixel of the new color to use it the way i want. (Ohhhh my god...!!!!) That's a challange right? ;-)
So i did it!!! ;-) There is a lot of fragments out there, but nothing complete and because i know this is a very common task desired by many developers, i took the time to do it as clean and reusable as i could. The result will be something like this:
ShapeTypes
Let's define the shape types we wanna use it first. For sure you could enhance it by adding new types if you need.public enum ShapeType {
CIRCLE, RECTANBLE, ARBITRARY_SHAPE
}
ShapeFactory
Then let's define a class that is responsible for creating the shapes. In our cases the splashes. The empty cases can be enhenced by you by adding new shapes to it.public class ShapeFactory {
public static void createShape(Path path, float shapeSizeFactor, final int randomShapeId) {
switch (randomShapeId) {
case 1:
path.moveTo(379 * shapeSizeFactor, 616 * shapeSizeFactor);
int[] shapeCoordinates;
shapeCoordinates = new int[] { 379, 616, 379, 614, 379, 613, 379, 612, 379, 610, 379, 609, 379, 607, 381, 600, 383, 593, 384, 585, 381, 565, 375, 555, 361, 530, 354, 517,
345, 503, 337, 489, 315, 457, 303, 441, 292, 425, 283, 412, 274, 398, 266, 384, 261, 373, 256, 363, 253, 354, 252, 334, 253, 322, 255, 309, 258, 298, 262, 288,
266, 280, 270, 272, 276, 265, 283, 256, 294, 249, 304, 244, 314, 239, 322, 236, 329, 235, 336, 235, 355, 237, 373, 242, 380, 245, 390, 251, 393, 254, 402, 263,
408, 269, 416, 278, 422, 289, 430, 300, 437, 313, 450, 340, 453, 355, 456, 369, 458, 381, 461, 402, 462, 412, 464, 421, 465, 430, 466, 438, 467, 446, 470, 453,
476, 465, 481, 470, 490, 474, 499, 476, 510, 478, 520, 477, 529, 475, 538, 472, 545, 468, 553, 462, 559, 455, 565, 446, 570, 435, 578, 414, 581, 402, 588, 374,
595, 347, 599, 333, 610, 300, 616, 287, 622, 275, 630, 265, 639, 256, 648, 247, 656, 239, 665, 232, 672, 226, 681, 222, 692, 217, 704, 215, 713, 213, 728, 213,
734, 213, 740, 214, 747, 215, 753, 218, 759, 221, 765, 226, 771, 231, 777, 237, 781, 245, 785, 252, 786, 259, 788, 267, 788, 276, 788, 285, 787, 294, 784, 303,
783, 312, 780, 320, 776, 328, 771, 336, 767, 345, 762, 354, 752, 374, 748, 385, 744, 394, 741, 403, 735, 414, 729, 427, 721, 440, 715, 452, 708, 463, 702, 474,
694, 486, 690, 499, 685, 511, 678, 534, 677, 542, 676, 553, 677, 558, 684, 563, 693, 563, 705, 560, 718, 554, 730, 547, 747, 539, 785, 520, 800, 512, 835, 496,
850, 490, 871, 481, 880, 478, 888, 477, 894, 476, 900, 476, 906, 477, 910, 478, 915, 481, 918, 484, 927, 492, 931, 500, 933, 507, 935, 515, 936, 522, 937, 537,
933, 552, 930, 561, 919, 575, 912, 581, 904, 588, 896, 594, 888, 599, 879, 603, 855, 611, 839, 614, 827, 616, 818, 620, 801, 630, 793, 637, 782, 653, 780, 661,
780, 670, 782, 678, 789, 689, 797, 703, 806, 718, 829, 750, 842, 768, 853, 787, 861, 802, 869, 819, 879, 857, 882, 874, 885, 892, 887, 913, 888, 930, 887, 947,
885, 966, 880, 986, 873, 1002, 863, 1019, 849, 1035, 832, 1049, 795, 1064, 771, 1062, 757, 1051, 744, 1037, 730, 1018, 719, 996, 712, 975, 708, 954, 706, 907, 706,
888, 704, 868, 701, 853, 695, 839, 688, 828, 663, 813, 637, 813, 616, 817, 600, 826, 567, 863, 558, 883, 554, 908, 554, 933, 570, 982, 581, 1006, 590, 1029, 598,
1055, 602, 1104, 597, 1133, 588, 1161, 573, 1191, 533, 1244, 505, 1268, 477, 1286, 402, 1315, 364, 1324, 339, 1326, 286, 1316, 271, 1306, 253, 1291, 236, 1269,
219, 1227, 219, 1205, 222, 1188, 228, 1172, 256, 1137, 275, 1116, 300, 1094, 321, 1074, 356, 1032, 373, 1011, 385, 991, 394, 961, 393, 947, 388, 934, 368, 915,
352, 907, 314, 892, 305, 886, 309, 866, 332, 831, 344, 814, 363, 782, 367, 764, 365, 759, 354, 750, 344, 748, 330, 747, 310, 747, 278, 754, 243, 769, 195, 799,
160, 810, 142, 809, 136, 805, 124, 789, 119, 774, 116, 760, 115, 743, 117, 710, 121, 695, 126, 681, 140, 652, 151, 641, 168, 629, 192, 619, 204, 619, 218, 619,
231, 620, 253, 626, 261, 629, 269, 633, 278, 636, 306, 637, 318, 634, 330, 631, 342, 627, 370, 615, 373, 614, 373, 614 };
for (int i = 0; i < shapeCoordinates.length - 2; i += 2) {
path.lineTo(shapeCoordinates[i] * shapeSizeFactor, shapeCoordinates[i + 1] * shapeSizeFactor);
}
path.close();
break;
case 2:
break;
case 3:
break;
default:
break;
}
}
}
ObjectView
That's the class responsible for drawing the shapes.import java.util.Random;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelXorXfermode;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class ObjectView extends View {
private Paint paint;
private boolean isTouched;
private ShapeType shapeType;
private float shapeSizeFactor;
public ObjectView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}
public ObjectView(Context context, AttributeSet attrs) {super(context, attrs);}
public ObjectView(Context context, int shapeColor, int initialPositionX, int initialPositionY, ShapeType type) {
super(context);
setBackgroundColor(Color.TRANSPARENT);
paint = new Paint();
paint.setColor(shapeColor);
paint.setXfermode(new PorterDuffXfermode(Mode.ADD));
// paint.setXfermode(new PixelXorXfermode(0xFFFFFFFF));
shapeType = type;
setTranslationX(initialPositionX);
setTranslationY(initialPositionY);
}
public ObjectView(Context context, int shapeColor, int initialPositionX, int initialPositionY, ShapeType type, float shapeSizeFactor) {
this(context, shapeColor, initialPositionX, initialPositionY, type);
this.shapeSizeFactor = shapeSizeFactor;
}
public void onDrawEx(Canvas canvas) {
switch (shapeType) {
case CIRCLE:
int circleXCoordinate = getWidth() / 2;
int circleYCoordinate = getHeight() / 2;
int circleRadius = getWidth() / 2;
canvas.drawCircle(circleXCoordinate, circleYCoordinate, circleRadius, paint);
break;
case RECTANBLE:
float left = 0;
float top = 0;
float right = getWidth();
float bottom = getHeight();
canvas.drawRect(left, top, right, bottom, paint);
break;
case ARBITRARY_SHAPE:
Path path = new Path();
path.addPath(path, new Matrix());
int min = 1;
int max = 10;
Random randomShapeIdGenerator = new Random();
int randomShapeId = randomShapeIdGenerator.nextInt(max - min + 1) + min;
ShapeFactory.createShape(path, this.shapeSizeFactor, /*randomShapeId*/1);
canvas.drawPath(path, paint);
break;
}
}
public boolean isTouched() {return isTouched;}
public void setTouched(boolean touched) {isTouched = touched;}
@Override
public boolean onTouchEvent(MotionEvent event) {
isTouched = true;
return super.onTouchEvent(event);
}
}
ObjectViewLayout
thats the main layout that holds all views and it is responsible for moving the shapes around and find out the intersection's color, if the shapes are mixed in the middle of the screen.import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
public class ObjectViewLayout extends FrameLayout {
public ObjectViewLayout(Context context) {
super(context);
}
public ObjectViewLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObjectViewLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP:
for (int i = 0; i < getChildCount() - 1; i++) {
ObjectView objectView = (ObjectView) getChildAt(i);
objectView.setTouched(false);
}
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
for (int i = 0; i < getChildCount() - 1; i++) {
ObjectView objectView = (ObjectView) getChildAt(i);
if (objectView.isTouched()) {
objectView.setTranslationX(event.getX() - objectView.getWidth() / 2);
objectView.setTranslationY(event.getY() - objectView.getHeight() / 2);
break;
}
}
}
// THE NEXT LINE REPRESENTS/CALLS THE OVERLAYVIEW
getChildAt(getChildCount() - 1).invalidate();
// THIS LINE SETS/CHECKS MIXED COLORS TO USE THE WAY YOU MAY WANT
new Colorize().setColorToPen(getColorFromMiddleOfThisObject());
return true;
}
public int getColorFromMiddleOfThisObject() {
Bitmap returnedBitmap = getBitmapFromView(this);
int pixel = returnedBitmap.getPixel(this.getWidth()/2, this.getHeight()/2);
Log.i("TEST", "Pixel value ObjectViewLayout: "+pixel);
return pixel;
}
private Bitmap getBitmapFromView(View view) {
Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedBitmap);
Drawable backgroundDrawable = view.getBackground();
if (backgroundDrawable != null)
backgroundDrawable.draw(canvas);
else
// does not have background drawable, then draw dark grey background
// on the canvas to be able to detect it.
canvas.drawColor(Color.DKGRAY);
view.draw(canvas);
return returnedBitmap;
}
}
OverlayView
That's the view responsible for triggering the repaint/redrawing process.import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@SuppressLint("DrawAllocation")
public class OverlayView extends View {
public OverlayView(Context context) {
super(context);
}
public OverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public OverlayView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onDraw(Canvas canvas) {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
Canvas canvasBitmap = new Canvas(bitmap);
ViewGroup viewGroup = (ViewGroup) getParent();
for (int i = 0; i < viewGroup.getChildCount() - 1; i++) {
ObjectView objectView = (ObjectView) viewGroup.getChildAt(i);
canvasBitmap.save();
canvasBitmap.translate(objectView.getTranslationX(), objectView.getTranslationY());
objectView.onDrawEx(canvasBitmap);
canvasBitmap.restore();
}
float left = 0;
float top = 0;
canvas.drawBitmap(bitmap, left, top, new Paint());
}
}
Colorize
This is the class that interprets the mixed pixel color. Here you could do / set what ever you want.import android.graphics.Color;
import android.util.Log;
public class Colorize {
public void setColorToPen(int pixelColor) {
if (pixelColor == Color.MAGENTA) {
Log.i("TEST", "It is MAGENTA");
}
if (pixelColor == Color.RED) {
Log.i("TEST", "It is RED");
}
if (pixelColor == Color.BLUE) {
Log.i("TEST", "It is BLUE");
}
if (pixelColor == Color.GREEN) {
Log.i("TEST", "It is GREEN");
}
if (pixelColor == Color.YELLOW) {
Log.i("TEST", "It is YELLOW");
}
if (pixelColor == Color.CYAN) {
Log.i("TEST", "It is CYAN");
}
if (pixelColor == Color.BLACK) {
Log.i("TEST", "It is BLACK");
}
if (pixelColor == Color.TRANSPARENT) {
Log.i("TEST", "It is TRANSPARENT");
}
if (pixelColor == Color.WHITE) {
Log.i("TEST", "It is WHITE");
}
if (pixelColor == Color.GRAY) {
Log.i("TEST", "It is GREY");
}
}
}
MainActivity
Finally let's take a look at the main activity class.import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Menu;
import android.widget.FrameLayout;
public class MainActivity extends Activity {
private OverlayView overlayView;
private ObjectViewLayout objectViewLayout;
private ObjectView shape1;
private ObjectView shape2;
private ObjectView shape3;
private int initialPositionX = 70;
private int initialPositionY = 70;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
float sizeFactor = 0.1f;
objectViewLayout = new ObjectViewLayout(this);
initialPositionX = 70;
initialPositionY = 70;
shape1 = new ObjectView(this, Color.RED, initialPositionX, initialPositionY, ShapeType.ARBITRARY_SHAPE, sizeFactor);
objectViewLayout.addView(shape1, new FrameLayout.LayoutParams(150, 150));
initialPositionX = 140;
initialPositionY = 140;
shape2 = new ObjectView(this, Color.BLUE, initialPositionX, initialPositionY, ShapeType.ARBITRARY_SHAPE, sizeFactor);
objectViewLayout.addView(shape2, new FrameLayout.LayoutParams(150, 150));
int initialPositionX = 210;
int initialPositionY = 210;
shape3 = new ObjectView(this, Color.GREEN, initialPositionX, initialPositionY, ShapeType.ARBITRARY_SHAPE, sizeFactor);
objectViewLayout.addView(shape3, new FrameLayout.LayoutParams(150, 150));
overlayView = new OverlayView(this);
objectViewLayout.addView(overlayView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
setContentView(objectViewLayout);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Thats all. Hope you like it. :) If you run this project now, you should get the result i showed to you at the beginning of this post.
😱👇 PROMOTIONAL DISCOUNT: BOOKS AND IPODS PRO 😱👇
Be sure to read, it will change your life!
Show your work by Austin Kleon: https://amzn.to/34NVmwx
This book is a must read - it will put you in another level! (Expert)
Agile Software Development, Principles, Patterns, and Practices: https://amzn.to/30WQSm2
Write cleaner code and stand out!
Clean Code - A Handbook of Agile Software Craftsmanship: https://amzn.to/33RvaSv
This book is very practical, straightforward and to the point! Worth every penny!
Kotlin for Android App Development (Developer's Library): https://amzn.to/33VZ6gp
Needless to say, these are top right?
Apple AirPods Pro: https://amzn.to/2GOICxy
😱👆 PROMOTIONAL DISCOUNT: BOOKS AND IPODS PRO 😱👆