Loading tools/layoutlib/bridge/src/android/view/RectShadowPainter.java +124 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -58,14 +73,20 @@ public class RectShadowPainter { return; } 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; Loading @@ -75,6 +96,7 @@ public class RectShadowPainter { paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight, lightSize, canvas); } } finally { canvas.restoreToCount(saved); } Loading @@ -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) { Loading tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png −2.1 KiB (11.2 KiB) Loading image diff... Loading
tools/layoutlib/bridge/src/android/view/RectShadowPainter.java +124 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -58,14 +73,20 @@ public class RectShadowPainter { return; } 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; Loading @@ -75,6 +96,7 @@ public class RectShadowPainter { paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight, lightSize, canvas); } } finally { canvas.restoreToCount(saved); } Loading @@ -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) { Loading
tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png −2.1 KiB (11.2 KiB) Loading image diff...