7 package com.google.appinventor.components.runtime;
9 import android.Manifest;
10 import android.app.Activity;
12 import android.content.Context;
14 import android.graphics.Bitmap;
15 import android.graphics.BitmapFactory;
16 import android.graphics.Color;
17 import android.graphics.Paint;
18 import android.graphics.Path;
19 import android.graphics.PorterDuff;
20 import android.graphics.Rect;
21 import android.graphics.RectF;
22 import android.graphics.drawable.BitmapDrawable;
23 import android.graphics.drawable.ColorDrawable;
24 import android.graphics.drawable.Drawable;
26 import android.text.TextUtils;
28 import android.util.Base64;
29 import android.util.Log;
31 import android.view.GestureDetector;
32 import android.view.MotionEvent;
33 import android.view.View;
35 import androidx.annotation.RequiresApi;
66 import java.io.FileNotFoundException;
67 import java.io.FileOutputStream;
68 import java.io.IOException;
70 import java.util.ArrayList;
71 import java.util.LinkedList;
72 import java.util.List;
112 @DesignerComponent(version = YaVersion.CANVAS_COMPONENT_VERSION,
113 description =
"<p>A two-dimensional touch-sensitive rectangular panel on " +
114 "which drawing can be done and sprites can be moved.</p> " +
115 "<p>The <code>BackgroundColor</code>, <code>PaintColor</code>, " +
116 "<code>BackgroundImage</code>, <code>Width</code>, and " +
117 "<code>Height</code> of the Canvas can be set in either the Designer or " +
118 "in the Blocks Editor. The <code>Width</code> and <code>Height</code> " +
119 "are measured in pixels and must be positive.</p>" +
120 "<p>Any location on the Canvas can be specified as a pair of " +
121 "(X, Y) values, where <ul> " +
122 "<li>X is the number of pixels away from the left edge of the Canvas</li>" +
123 "<li>Y is the number of pixels away from the top edge of the Canvas</li>" +
125 "<p>There are events to tell when and where a Canvas has been touched or " +
126 "a <code>Sprite</code> (<code>ImageSprite</code> or <code>Ball</code>) " +
127 "has been dragged. There are also methods for drawing points, lines, " +
129 category = ComponentCategory.ANIMATION)
131 @UsesPermissions(permissionNames =
"android.permission.INTERNET")
133 private static final String LOG_TAG =
"Canvas";
135 private final Activity context;
136 private final CanvasView view;
140 private boolean drawn;
143 private int paintColor;
144 private final Paint paint;
145 private int backgroundColor;
146 private String backgroundImagePath =
"";
147 private int textAlignment;
148 private boolean extendMovesOutsideCanvas =
false;
151 private static final int MIN_WIDTH_HEIGHT = 1;
152 private static final float DEFAULT_LINE_WIDTH = 2;
156 private static final int FLING_INTERVAL = 1000;
160 private final List<Sprite> sprites;
163 private final MotionEventParser motionEventParser;
166 private final GestureDetector mGestureDetector;
173 private final Set<ExtensionGestureDetector> extensionGestureDetectors =
Sets.
newHashSet();
175 private Form form = $form();
178 private boolean havePermission =
false;
182 boolean onTouchEvent(MotionEvent event);
214 class MotionEventParser {
224 public static final int TAP_THRESHOLD = 15;
232 public static final int FINGER_WIDTH = 24;
240 public static final int FINGER_HEIGHT = 24;
242 private static final int HALF_FINGER_WIDTH = FINGER_WIDTH / 2;
243 private static final int HALF_FINGER_HEIGHT = FINGER_HEIGHT / 2;
249 private final List<Sprite> draggedSprites =
new ArrayList<Sprite>();
252 private static final int UNSET = -1;
253 private float startX = UNSET;
254 private float startY = UNSET;
257 private float lastX = UNSET;
258 private float lastY = UNSET;
262 private boolean isDrag =
false;
264 private boolean drag =
false;
266 void parse(MotionEvent event) {
268 int height = Height();
274 float x = Math.max(0, (
int) event.getX() / $form().deviceDensity());
275 float y = Math.max(0, (
int) event.getY() / $form().deviceDensity());
280 Math.max(0, (
int) x - HALF_FINGER_HEIGHT),
281 Math.max(0, (
int) y - HALF_FINGER_WIDTH),
282 Math.min(width - 1, (
int) x + HALF_FINGER_WIDTH),
283 Math.min(height - 1, (
int) y + HALF_FINGER_HEIGHT));
285 switch (event.getAction()) {
286 case MotionEvent.ACTION_DOWN:
287 draggedSprites.clear();
294 for (
Sprite sprite : sprites) {
295 if (sprite.Enabled() && sprite.Visible() && sprite.intersectsWith(rect)) {
296 draggedSprites.add(sprite);
297 sprite.TouchDown(startX, startY);
300 TouchDown(startX, startY);
303 case MotionEvent.ACTION_MOVE:
305 if (startX == UNSET || startY == UNSET || lastX == UNSET || lastY == UNSET) {
306 Log.w(LOG_TAG,
"In Canvas.MotionEventParser.parse(), " +
307 "an ACTION_MOVE was passed without a preceding ACTION_DOWN: " + event);
312 (Math.abs(x - startX) < TAP_THRESHOLD && Math.abs(y - startY) < TAP_THRESHOLD)) {
321 if (((x <= 0) || (x > width) || (y <= 0) || (y > height))
322 && (! extendMovesOutsideCanvas)) {
328 for (Sprite sprite : sprites) {
329 if (!draggedSprites.contains(sprite)
330 && sprite.Enabled() && sprite.Visible()
331 && sprite.intersectsWith(rect)) {
332 draggedSprites.add(sprite);
337 boolean handled =
false;
338 for (Sprite sprite : draggedSprites) {
339 if (sprite.Enabled() && sprite.Visible()) {
340 sprite.Dragged(startX, startY, lastX, lastY, x, y);
346 Dragged(startX, startY, lastX, lastY, x, y, handled);
351 case MotionEvent.ACTION_UP:
358 for (Sprite sprite : draggedSprites) {
359 if (sprite.Enabled() && sprite.Visible()) {
360 sprite.Touched(x, y);
361 sprite.TouchUp(x, y);
366 Touched(x, y, handled);
369 for (Sprite sprite : draggedSprites) {
370 if (sprite.Enabled() && sprite.Visible()) {
371 sprite.Touched(x, y);
372 sprite.TouchUp(x, y);
396 private final class CanvasView
extends View {
398 private android.graphics.Canvas canvas;
399 private Bitmap bitmap;
402 private BitmapDrawable backgroundDrawable;
409 private Bitmap scaledBackgroundBitmap;
414 private Bitmap completeCache;
416 public CanvasView(Context context) {
418 bitmap = Bitmap.createBitmap(ComponentConstants.CANVAS_PREFERRED_WIDTH,
419 ComponentConstants.CANVAS_PREFERRED_HEIGHT,
420 Bitmap.Config.ARGB_8888);
421 canvas =
new android.graphics.Canvas(bitmap);
428 private Bitmap buildCache() {
430 setDrawingCacheEnabled(
true);
431 destroyDrawingCache();
432 Bitmap cache = getDrawingCache();
436 int width = getWidth();
437 int height = getHeight();
438 cache = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
439 android.graphics.Canvas c =
new android.graphics.Canvas(cache);
440 layout(0, 0, width, height);
447 public void onDraw(android.graphics.Canvas canvas0) {
448 completeCache =
null;
451 super.onDraw(canvas0);
455 canvas0.drawBitmap(bitmap, 0, 0,
null);
459 for (Sprite sprite : sprites) {
460 sprite.onDraw(canvas0);
466 protected void onSizeChanged(
int w,
int h,
int oldW,
int oldH) {
467 int oldBitmapWidth = bitmap.getWidth();
468 int oldBitmapHeight = bitmap.getHeight();
469 if (w != oldBitmapWidth || h != oldBitmapHeight) {
470 Bitmap oldBitmap = bitmap;
484 Bitmap scaledBitmap = Bitmap.createScaledBitmap(oldBitmap, w, h,
false);
486 if (scaledBitmap.isMutable()) {
488 bitmap = scaledBitmap;
492 canvas =
new android.graphics.Canvas(bitmap);
497 bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
501 canvas =
new android.graphics.Canvas(bitmap);
504 Rect src =
new Rect(0, 0, oldBitmapWidth, oldBitmapHeight);
505 RectF dst =
new RectF(0, 0, w, h);
506 canvas.drawBitmap(oldBitmap, src, dst,
null);
509 }
catch (IllegalArgumentException ioe) {
518 Log.e(LOG_TAG,
"Bad values to createScaledBimap w = " + w +
", h = " + h);
525 scaledBackgroundBitmap =
null;
530 protected void onMeasure(
int widthMeasureSpec,
int heightMeasureSpec) {
533 if (backgroundDrawable !=
null) {
535 Bitmap bitmap = backgroundDrawable.getBitmap();
536 preferredWidth = bitmap.getWidth();
537 preferredHeight = bitmap.getHeight();
539 preferredWidth = ComponentConstants.CANVAS_PREFERRED_WIDTH;
540 preferredHeight = ComponentConstants.CANVAS_PREFERRED_HEIGHT;
542 setMeasuredDimension(getSize(widthMeasureSpec, preferredWidth),
543 getSize(heightMeasureSpec, preferredHeight));
546 private int getSize(
int measureSpec,
int preferredSize) {
548 int specMode = MeasureSpec.getMode(measureSpec);
549 int specSize = MeasureSpec.getSize(measureSpec);
551 if (specMode == MeasureSpec.EXACTLY) {
556 result = preferredSize;
557 if (specMode == MeasureSpec.AT_MOST) {
559 result = Math.min(result, specSize);
567 public boolean onTouchEvent(MotionEvent event) {
573 container.$form().dontGrabTouchEventsForComponent();
574 motionEventParser.parse(event);
575 mGestureDetector.onTouchEvent(event);
577 for (ExtensionGestureDetector g : extensionGestureDetectors) {
580 g.onTouchEvent(event);
593 void setBackgroundImage(String path) {
594 backgroundImagePath = (path ==
null) ?
"" : path;
595 backgroundDrawable =
null;
596 scaledBackgroundBitmap =
null;
598 if (!TextUtils.isEmpty(backgroundImagePath)) {
600 backgroundDrawable = MediaUtil.getBitmapDrawable(container.$form(), backgroundImagePath);
601 }
catch (IOException ioe) {
602 Log.e(LOG_TAG,
"Unable to load " + backgroundImagePath);
611 @RequiresApi(api = android.os.Build.VERSION_CODES.FROYO)
612 void setBackgroundImageBase64(String imageUrl) {
613 backgroundImagePath = (imageUrl ==
null) ?
"" : imageUrl;
614 backgroundDrawable =
null;
615 scaledBackgroundBitmap =
null;
617 if (!TextUtils.isEmpty(backgroundImagePath)) {
618 byte[] decodedString = Base64.decode(backgroundImagePath, Base64.DEFAULT);
619 android.graphics.Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
620 backgroundDrawable =
new BitmapDrawable(decodedByte);
627 private void setBackground() {
628 Drawable setDraw = backgroundDrawable;
629 if (backgroundImagePath !=
"" && backgroundDrawable !=
null) {
630 setDraw = backgroundDrawable.getConstantState().newDrawable();
631 setDraw.setColorFilter((backgroundColor != Component.COLOR_DEFAULT) ? backgroundColor : Component.COLOR_WHITE,
632 PorterDuff.Mode.DST_OVER);
635 setDraw =
new ColorDrawable(
636 (backgroundColor != Component.COLOR_DEFAULT) ? backgroundColor : Component.COLOR_WHITE);
638 setBackgroundDrawable(setDraw);
641 private void clearDrawingLayer() {
642 canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
650 public void setBackgroundColor(
int color) {
651 backgroundColor = color;
659 private void drawTextAtAngle(String text,
int x,
int y,
float angle) {
661 canvas.rotate(-angle, x, y);
662 canvas.drawText(text, x, y, paint);
668 private int getBackgroundPixelColor(
int x,
int y) {
670 if (x < 0 || x >= bitmap.getWidth() ||
671 y < 0 || y >= bitmap.getHeight()) {
672 return Component.COLOR_NONE;
678 int color = bitmap.getPixel(x, y);
679 if (color != Color.TRANSPARENT) {
685 if (backgroundDrawable !=
null) {
686 if (scaledBackgroundBitmap ==
null) {
687 scaledBackgroundBitmap = Bitmap.createScaledBitmap(
688 backgroundDrawable.getBitmap(),
689 bitmap.getWidth(), bitmap.getHeight(),
692 color = scaledBackgroundBitmap.getPixel(x, y);
697 if (Color.alpha(backgroundColor) != 0) {
698 return backgroundColor;
700 return Component.COLOR_NONE;
701 }
catch (IllegalArgumentException e) {
704 String.format(
"Returning COLOR_NONE (exception) from getBackgroundPixelColor."));
705 return Component.COLOR_NONE;
709 private int getPixelColor(
int x,
int y) {
711 if (x < 0 || x >= bitmap.getWidth() ||
712 y < 0 || y >= bitmap.getHeight()) {
713 return Component.COLOR_NONE;
717 if (completeCache ==
null) {
719 boolean anySpritesVisible =
false;
720 for (Sprite sprite : sprites) {
721 if (sprite.Visible()) {
722 anySpritesVisible =
true;
726 if (!anySpritesVisible) {
727 return getBackgroundPixelColor(x, y);
735 completeCache = buildCache();
740 return completeCache.getPixel(x, y);
741 }
catch (IllegalArgumentException e) {
744 String.format(
"Returning COLOR_NONE (exception) from getPixelColor."));
745 return Component.COLOR_NONE;
755 view =
new CanvasView(context);
756 container.
$add(
this);
759 paint.setFlags(Paint.ANTI_ALIAS_FLAG);
762 paint.setStrokeWidth(DEFAULT_LINE_WIDTH);
763 PaintColor(DEFAULT_PAINT_COLOR);
764 BackgroundColor(DEFAULT_BACKGROUND_COLOR);
765 TextAlignment(DEFAULT_TEXTALIGNMENT);
768 sprites =
new LinkedList<Sprite>();
769 motionEventParser =
new MotionEventParser();
770 mGestureDetector =
new GestureDetector(context,
new FlingGestureListener());
782 Manifest.permission.WRITE_EXTERNAL_STORAGE) {
784 public void onGranted() {
785 me.havePermission = true;
803 extensionGestureDetectors.add(detector);
807 extensionGestureDetectors.remove(detector);
832 void addSprite(
Sprite sprite) {
839 for (
int i = 0; i < sprites.size(); i++) {
840 if (sprites.get(i).Z() > sprite.
Z()) {
841 sprites.add(i, sprite);
855 void removeSprite(Sprite sprite) {
856 sprites.remove(sprite);
865 void changeSpriteLayer(Sprite sprite) {
866 removeSprite(sprite);
878 return container.
$form();
883 throw new UnsupportedOperationException(
"Canvas.$add() called");
888 throw new UnsupportedOperationException(
"Canvas.setChildWidth() called");
893 throw new UnsupportedOperationException(
"Canvas.setChildHeight() called");
904 void registerChange(
Sprite sprite) {
906 findSpriteCollisions(sprite);
928 for (
Sprite sprite : sprites) {
929 if (sprite != movedSprite) {
974 if ((width > 0) || (width==LENGTH_FILL_PARENT) || (width==LENGTH_PREFERRED) ||
975 (width <= LENGTH_PERCENT_TAG)) {
979 container.$form().dispatchErrorOccurredEvent(
this,
"Width",
998 if ((height > 0) || (height==LENGTH_FILL_PARENT) || (height==LENGTH_PREFERRED) ||
999 (height <= LENGTH_PERCENT_TAG)) {
1000 super.Height(height);
1003 container.$form().dispatchErrorOccurredEvent(
this,
"Height",
1018 description =
"The color of the canvas background.",
1022 return backgroundColor;
1038 view.setBackgroundColor(argb);
1047 description =
"The name of a file containing the background image for the canvas",
1049 public String BackgroundImage() {
1050 return backgroundImagePath;
1066 view.setBackgroundImage(path);
1075 @RequiresApi(api = android.os.Build.VERSION_CODES.FROYO)
1077 description =
"Set the background image in Base64 format. This requires API level >= 8. For "
1078 +
"devices with API level less than 8, setting this will end up with an empty background."
1080 public
void BackgroundImageinBase64(String imageUrl) {
1082 view.setBackgroundImageBase64(imageUrl);
1084 view.setBackgroundImageBase64(
"");
1098 description =
"The color in which lines are drawn",
1118 changePaint(paint, argb);
1121 private void changePaint(Paint paint,
int argb) {
1125 }
else if (argb == Component.COLOR_NONE) {
1126 PaintUtil.changePaintTransparent(paint);
1128 PaintUtil.changePaint(paint, argb);
1133 description =
"The font size of text drawn on the canvas.",
1134 category = PropertyCategory.APPEARANCE)
1135 public
float FontSize() {
1136 float scale = $form().deviceDensity();
1137 return paint.getTextSize() / scale;
1148 float scale = $form().deviceDensity();
1149 paint.setTextSize(size * scale);
1157 description =
"The width of lines drawn on the canvas.",
1159 public
float LineWidth() {
1160 return paint.getStrokeWidth() / $form().deviceDensity();
1169 defaultValue = DEFAULT_LINE_WIDTH +
"")
1172 paint.setStrokeWidth(width * $form().deviceDensity());
1184 @
SimpleProperty(description =
"Determines the alignment of the " +
1185 "text drawn by DrawText() or DrawAngle() with respect to the " +
1186 "point specified by that command: point at the left of the text, point at the center " +
1187 "of the text, or point at the right of the text.",
1191 public
int TextAlignment() {
1192 return textAlignment;
1208 defaultValue = DEFAULT_TEXTALIGNMENT +
"")
1210 public
void TextAlignment(
int alignment) {
1211 this.textAlignment = alignment;
1212 switch (alignment) {
1214 paint.setTextAlign(Paint.Align.LEFT);
1217 paint.setTextAlign(Paint.Align.CENTER);
1220 paint.setTextAlign(Paint.Align.RIGHT);
1226 "Determines whether moves can extend beyond the canvas borders. " +
1227 " Default is false. This should normally be false, and the property " +
1228 "is provided for backwards compatibility.",
1231 public
boolean ExtendMovesOutsideCanvas() {
1232 return extendMovesOutsideCanvas;
1241 public
void ExtendMovesOutsideCanvas(
boolean extend){
1242 extendMovesOutsideCanvas = extend;
1258 public void Touched(
float x,
float y,
boolean touchedAnySprite) {
1307 public void Flung(
float x,
float y,
float speed,
float heading,
float xvel,
float yvel,
1308 boolean flungSprite) {
1330 public void Dragged(
float startX,
float startY,
float prevX,
float prevY,
1331 float currentX,
float currentY,
boolean draggedAnySprite) {
1333 prevX, prevY, currentX, currentY, draggedAnySprite);
1342 @
SimpleFunction(description =
"Clears anything drawn on this Canvas but " +
1343 "not any background color or image.")
1344 public
void Clear() {
1345 view.clearDrawingLayer();
1356 float correctedX = x * $form().deviceDensity();
1357 float correctedY = y * $form().deviceDensity();
1358 view.canvas.drawPoint(correctedX, correctedY, paint);
1372 public void DrawCircle(
int centerX,
int centerY,
float radius,
boolean fill) {
1373 float correctedX = centerX * $form().deviceDensity();
1374 float correctedY = centerY * $form().deviceDensity();
1375 float correctedR = radius * $form().deviceDensity();
1376 Paint p =
new Paint(paint);
1377 p.setStyle(fill ? Paint.Style.FILL : Paint.Style.STROKE);
1378 view.canvas.drawCircle(correctedX, correctedY, correctedR, p);
1392 float correctedX1 = x1 * $form().deviceDensity();
1393 float correctedY1 = y1 * $form().deviceDensity();
1394 float correctedX2 = x2 * $form().deviceDensity();
1395 float correctedY2 = y2 * $form().deviceDensity();
1396 view.canvas.drawLine(correctedX1, correctedY1, correctedX2, correctedY2, paint);
1411 "Draws a shape on the canvas. " +
1412 "pointList should be a list contains sub-lists with two number which represents a coordinate. " +
1413 "The first point and last point does not need to be the same. e.g. ((x1 y1) (x2 y2) (x3 y3)) " +
1414 "When fill is true, the shape will be filled.")
1415 public
void DrawShape(
YailList pointList,
boolean fill) {
1418 path = parsePath(parsePointList(pointList));
1419 }
catch (IllegalArgumentException e) {
1424 Paint p =
new Paint(paint);
1425 p.setStyle(fill ? Paint.Style.FILL : Paint.Style.STROKE);
1426 view.canvas.drawPath(path, p);
1430 private Path parsePath(
float[][] points)
throws IllegalArgumentException {
1431 if (points ==
null || points.length == 0) {
1432 throw new IllegalArgumentException();
1434 float scalingFactor = $form().deviceDensity();
1436 Path path =
new Path();
1437 path.moveTo(points[0][0] * scalingFactor, points[0][1] * scalingFactor);
1438 for (
int i = 1; i < points.length; i++) {
1439 path.lineTo(points[i][0] * scalingFactor, points[i][1] * scalingFactor);
1445 private float[][] parsePointList(YailList pointList)
throws IllegalArgumentException {
1446 if (pointList ==
null || pointList.size() == 0) {
1447 throw new IllegalArgumentException();
1449 float[][] points =
new float[pointList.size()][2];
1451 YailList pointYailList;
1452 for (Object pointObject : pointList.toArray()) {
1453 if (pointObject instanceof YailList) {
1454 pointYailList = (YailList) pointObject;
1455 if (pointYailList.size() == 2) {
1457 points[index][0] = Float.parseFloat(pointYailList.getString(0));
1458 points[index][1] = Float.parseFloat(pointYailList.getString(1));
1460 }
catch (NullPointerException e) {
1461 throw new IllegalArgumentException(e.fillInStackTrace());
1462 }
catch (NumberFormatException e) {
1463 throw new IllegalArgumentException(e.fillInStackTrace());
1466 throw new IllegalArgumentException(
"length of item YailList("+ index +
") is not 2");
1469 throw new IllegalArgumentException(
"item("+ index +
") in YailList is not a YailList");
1490 @SimpleFunction(description =
1491 "Draw an arc on Canvas, by drawing an arc from a specified oval (specified by left, top, right & bottom). " +
1492 "Start angle is 0 when heading to the right, and increase when rotate clockwise. " +
1493 "When useCenter is true, a sector will be drawed instead of an arc. " +
1494 "When fill is true, a filled arc (or sector) will be drawed instead of just an outline.")
1495 public
void DrawArc(
int left,
int top,
int right,
int bottom,
1496 float startAngle,
float sweepAngle,
boolean useCenter,
boolean fill) {
1497 float scalingFactor = $form().deviceDensity();
1498 Paint p =
new Paint(paint);
1499 p.setStyle(fill ? Paint.Style.FILL : Paint.Style.STROKE);
1500 view.canvas.drawArc(
1501 new RectF(scalingFactor * left, scalingFactor * top,
1502 scalingFactor * right, scalingFactor * bottom),
1503 startAngle, sweepAngle, useCenter, p);
1516 @
SimpleFunction(description =
"Draws the specified text relative to the specified coordinates "
1517 +
"using the values of the FontSize and TextAlignment properties.")
1518 public
void DrawText(String text,
int x,
int y) {
1519 float fontScalingFactor = $form().deviceDensity();
1520 float correctedX = x * fontScalingFactor;
1521 float correctedY = y * fontScalingFactor;
1522 view.canvas.drawText(text, correctedX, correctedY, paint);
1536 @
SimpleFunction(description =
"Draws the specified text starting at the specified coordinates "
1537 +
"at the specified angle using the values of the FontSize and TextAlignment properties.")
1538 public
void DrawTextAtAngle(String text,
int x,
int y,
float angle) {
1539 int correctedX = (int) (x * $form().deviceDensity());
1540 int correctedY = (int) (y * $form().deviceDensity());
1541 view.drawTextAtAngle(text, correctedX, correctedY, angle);
1552 @
SimpleFunction(description =
"Gets the color of the specified point. "
1553 +
"This includes the background and any drawn points, lines, or "
1554 +
"circles but not sprites.")
1557 int correctedX = (int) (x * $form().deviceDensity());
1558 int correctedY = (int) (y * $form().deviceDensity());
1559 return view.getBackgroundPixelColor(correctedX, correctedY);
1570 @
SimpleFunction(description =
"Sets the color of the specified point. "
1571 +
"This differs from DrawPoint by having an argument for color.")
1572 public
void SetBackgroundPixelColor(
int x,
int y, @
IsColor int color) {
1573 Paint pixelPaint =
new Paint();
1575 int correctedX = (int) (x * $form().deviceDensity());
1576 int correctedY = (int) (y * $form().deviceDensity());
1577 view.canvas.drawPoint(correctedX, correctedY, pixelPaint);
1589 @
SimpleFunction(description =
"Gets the color of the specified point.")
1592 int correctedX = (int) (x * $form().deviceDensity());
1593 int correctedY = (int) (y * $form().deviceDensity());
1594 return view.getPixelColor(correctedX, correctedY);
1605 @SimpleFunction(description =
"Saves a picture of this Canvas to the " +
1606 "device's external storage. If an error occurs, the Screen's ErrorOccurred " +
1607 "event will be called.")
1608 public String Save() {
1611 return saveFile(file, Bitmap.CompressFormat.PNG,
"Save");
1613 container.$form().dispatchPermissionDeniedEvent(
this,
"Save", e);
1614 }
catch (IOException e) {
1615 container.$form().dispatchErrorOccurredEvent(
this,
"Save",
1618 container.$form().dispatchErrorOccurredEvent(
this,
"Save",
1619 e.getErrorMessageNumber());
1633 @SimpleFunction(description =
"Saves a picture of this Canvas to the device's " +
1634 "external storage in the file " +
1635 "named fileName. fileName must end with one of .jpg, .jpeg, or .png, " +
1636 "which determines the file type.")
1637 public String SaveAs(String fileName) {
1639 Bitmap.CompressFormat format;
1640 if (fileName.endsWith(
".jpg") || fileName.endsWith(
".jpeg")) {
1641 format = Bitmap.CompressFormat.JPEG;
1642 }
else if (fileName.endsWith(
".png")) {
1643 format = Bitmap.CompressFormat.PNG;
1644 }
else if (!fileName.contains(
".")) {
1645 fileName = fileName +
".png";
1646 format = Bitmap.CompressFormat.PNG;
1648 container.$form().dispatchErrorOccurredEvent(
this,
"SaveAs",
1654 return saveFile(file, format,
"SaveAs");
1656 container.$form().dispatchPermissionDeniedEvent(
this,
"SaveAs", e);
1657 }
catch (IOException e) {
1658 container.$form().dispatchErrorOccurredEvent(
this,
"SaveAs",
1661 container.$form().dispatchErrorOccurredEvent(
this,
"SaveAs",
1662 e.getErrorMessageNumber());
1668 private String saveFile(
File file, Bitmap.CompressFormat format, String method) {
1670 boolean success =
false;
1671 FileOutputStream fos =
new FileOutputStream(file);
1673 Bitmap bitmap = (view.completeCache ==
null ? view.buildCache() : view.completeCache);
1675 success = bitmap.compress(format,
1682 return file.getAbsolutePath();
1684 container.$form().dispatchErrorOccurredEvent(
this, method,
1685 ErrorMessages.ERROR_CANVAS_BITMAP_ERROR);
1687 }
catch (FileNotFoundException e) {
1688 container.$form().dispatchErrorOccurredEvent(
this, method,
1689 ErrorMessages.ERROR_MEDIA_CANNOT_OPEN, file.getAbsolutePath());
1690 }
catch (IOException e) {
1691 container.$form().dispatchErrorOccurredEvent(
this, method,
1692 ErrorMessages.ERROR_MEDIA_FILE_ERROR, e.getMessage());
1697 class FlingGestureListener
extends GestureDetector.SimpleOnGestureListener {
1699 public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX,
1701 float x = Math.max(0, (
int)(e1.getX() / $form().deviceDensity()));
1702 float y = Math.max(0, (
int)(e1.getY() / $form().deviceDensity()));
1705 float vx = velocityX / FLING_INTERVAL;
1706 float vy = velocityY / FLING_INTERVAL;
1708 float speed = (float) Math.sqrt(vx * vx + vy * vy);
1709 float heading = (float) -Math.toDegrees(Math.atan2(vy, vx));
1711 int width = Width();
1712 int height = Height();
1716 BoundingBox rect =
new BoundingBox(
1717 Math.max(0, (
int) x - MotionEventParser.HALF_FINGER_HEIGHT),
1718 Math.max(0, (
int) y - MotionEventParser.HALF_FINGER_WIDTH),
1719 Math.min(width - 1, (
int) x + MotionEventParser.HALF_FINGER_WIDTH),
1720 Math.min(height - 1, (
int) y + MotionEventParser.HALF_FINGER_HEIGHT));
1722 boolean spriteHandledFling =
false;
1724 for (Sprite sprite : sprites) {
1727 sprite.
Flung(x, y, speed, heading, vx, vy);
1728 spriteHandledFling =
true;
1731 Flung(x, y, speed, heading, vx, vy, spriteHandledFling);