Merge "Draw rectangle shadow fast in low elevation cases"
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index aed85a7..0866577 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -18,13 +18,28 @@
import android.annotation.NonNull;
import android.graphics.Canvas;
+import android.graphics.LinearGradient;
import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.Path.FillType;
+import android.graphics.RadialGradient;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader.TileMode;
+
+import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.layoutlib.bridge.shadowutil.SpotShadow;
import com.android.layoutlib.bridge.shadowutil.ShadowBuffer;
public class RectShadowPainter {
+ private static final float SIMPLE_SHADOW_ELEVATION_THRESHOLD = 16f;
+ private static final int SIMPLE_SHADOW_START_COLOR = ResourceHelper.getColor("#37000000");
+ private static final int SIMPLE_SHADOW_END_COLOR = ResourceHelper.getColor("#03000000");
+ private static final float PERPENDICULAR_ANGLE = 90f;
+
private static final float SHADOW_STRENGTH = 0.1f;
private static final int LIGHT_POINTS = 8;
@@ -58,23 +73,30 @@
return;
}
- // view's absolute position in this canvas.
- int viewLeft = -originCanvasRect.left + outline.left;
- int viewTop = -originCanvasRect.top + outline.top;
- int viewRight = viewLeft + outline.width();
- int viewBottom = viewTop + outline.height();
+ if (elevation <= SIMPLE_SHADOW_ELEVATION_THRESHOLD) {
+ // When elevation is not high, the shadow is very similar to a small outline of
+ // View. For the performance reason, we draw the shadow in simple way.
+ simpleRectangleShadow(canvas, outline, radius, elevation);
+ } else {
+ // view's absolute position in this canvas.
+ int viewLeft = -originCanvasRect.left + outline.left;
+ int viewTop = -originCanvasRect.top + outline.top;
+ int viewRight = viewLeft + outline.width();
+ int viewBottom = viewTop + outline.height();
- float[][] rectangleCoordinators = generateRectangleCoordinates(viewLeft, viewTop,
- viewRight, viewBottom, radius, elevation);
+ float[][] rectangleCoordinators =
+ generateRectangleCoordinates(viewLeft, viewTop, viewRight, viewBottom,
+ radius, elevation);
- // TODO: get these values from resources.
- float lightPosX = canvas.getWidth() / 2;
- float lightPosY = 0;
- float lightHeight = 1800;
- float lightSize = 200;
+ // TODO: get these values from resources.
+ float lightPosX = canvas.getWidth() / 2;
+ float lightPosY = 0;
+ float lightHeight = 1800;
+ float lightSize = 200;
- paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight,
- lightSize, canvas);
+ paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight,
+ lightSize, canvas);
+ }
} finally {
canvas.restoreToCount(saved);
}
@@ -86,6 +108,94 @@
return canvas.save();
}
+ private static void simpleRectangleShadow(@NonNull Canvas canvas, @NonNull Rect outline,
+ float radius, float elevation) {
+ float shadowSize = elevation / 2;
+
+ Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+ cornerPaint.setStyle(Style.FILL);
+ Paint edgePaint = new Paint(cornerPaint);
+ edgePaint.setAntiAlias(false);
+ float outerArcRadius = radius + shadowSize;
+ int[] colors = {SIMPLE_SHADOW_START_COLOR, SIMPLE_SHADOW_START_COLOR,
+ SIMPLE_SHADOW_END_COLOR};
+ cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
+ new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
+ edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, SIMPLE_SHADOW_START_COLOR,
+ SIMPLE_SHADOW_END_COLOR,
+ TileMode.CLAMP));
+ Path path = new Path();
+ path.setFillType(FillType.EVEN_ODD);
+ // A rectangle bounding the complete shadow.
+ RectF shadowRect = new RectF(outline);
+ shadowRect.inset(-shadowSize, -shadowSize);
+ // A rectangle with edges corresponding to the straight edges of the outline.
+ RectF inset = new RectF(outline);
+ inset.inset(radius, radius);
+ // A rectangle used to represent the edge shadow.
+ RectF edgeShadowRect = new RectF();
+
+
+ // left and right sides.
+ edgeShadowRect.set(-shadowSize, 0f, 0f, inset.height());
+ // Left shadow
+ drawSideShadow(canvas, edgePaint, edgeShadowRect, outline.left, inset.top, 0);
+ // Right shadow
+ drawSideShadow(canvas, edgePaint, edgeShadowRect, outline.right, inset.bottom, 2);
+ // Top shadow
+ edgeShadowRect.set(-shadowSize, 0, 0, inset.width());
+ drawSideShadow(canvas, edgePaint, edgeShadowRect, inset.right, outline.top, 1);
+ // bottom shadow. This needs an inset so that blank doesn't appear when the content is
+ // moved up.
+ edgeShadowRect.set(-shadowSize, 0, shadowSize / 2f, inset.width());
+ edgePaint.setShader(
+ new LinearGradient(edgeShadowRect.right, 0, edgeShadowRect.left, 0, colors,
+ new float[]{0f, 1 / 3f, 1f}, TileMode.CLAMP));
+ drawSideShadow(canvas, edgePaint, edgeShadowRect, inset.left, outline.bottom, 3);
+
+ // Draw corners.
+ drawCorner(canvas, cornerPaint, path, inset.right, inset.bottom, outerArcRadius, 0);
+ drawCorner(canvas, cornerPaint, path, inset.left, inset.bottom, outerArcRadius, 1);
+ drawCorner(canvas, cornerPaint, path, inset.left, inset.top, outerArcRadius, 2);
+ drawCorner(canvas, cornerPaint, path, inset.right, inset.top, outerArcRadius, 3);
+ }
+
+ private static void drawSideShadow(@NonNull Canvas canvas, @NonNull Paint edgePaint,
+ @NonNull RectF shadowRect, float dx, float dy, int rotations) {
+ if ((int) shadowRect.left >= (int) shadowRect.right ||
+ (int) shadowRect.top >= (int) shadowRect.bottom) {
+ // Rect is empty, no need to draw shadow
+ return;
+ }
+ int saved = canvas.save();
+ canvas.translate(dx, dy);
+ canvas.rotate(rotations * PERPENDICULAR_ANGLE);
+ canvas.drawRect(shadowRect, edgePaint);
+ canvas.restoreToCount(saved);
+ }
+
+ /**
+ * @param canvas Canvas to draw the rectangle on.
+ * @param paint Paint to use when drawing the corner.
+ * @param path A path to reuse. Prevents allocating memory for each path.
+ * @param x Center of circle, which this corner is a part of.
+ * @param y Center of circle, which this corner is a part of.
+ * @param radius radius of the arc
+ * @param rotations number of quarter rotations before starting to paint the arc.
+ */
+ private static void drawCorner(@NonNull Canvas canvas, @NonNull Paint paint, @NonNull Path path,
+ float x, float y, float radius, int rotations) {
+ int saved = canvas.save();
+ canvas.translate(x, y);
+ path.reset();
+ path.arcTo(-radius, -radius, radius, radius, rotations * PERPENDICULAR_ANGLE,
+ PERPENDICULAR_ANGLE, false);
+ path.lineTo(0, 0);
+ path.close();
+ canvas.drawPath(path, paint);
+ canvas.restoreToCount(saved);
+ }
+
@NonNull
private static float[][] generateRectangleCoordinates(float left, float top, float right,
float bottom, float radius, float elevation) {
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
index 67355b8..4951629 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
Binary files differ