diff options
-rw-r--r-- | tools/layoutlib/bridge/src/android/view/RectShadowPainter.java | 138 | ||||
-rw-r--r-- | tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png | bin | 13569 -> 11423 bytes |
2 files changed, 124 insertions, 14 deletions
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java index aed85a746c20..08665778373e 100644 --- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java +++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java @@ -18,13 +18,28 @@ package android.view; 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 @@ public class RectShadowPainter { 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 @@ public class RectShadowPainter { 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 Binary files differindex 67355b821345..4951629a50b6 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 |