Loading tools/layoutlib/bridge/src/android/view/RectShadowPainter.java 0 → 100644 +156 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import com.android.layoutlib.bridge.impl.ResourceHelper; 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.Region.Op; import android.graphics.Shader.TileMode; /** * Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly, * since it modifies the size of the content, that we can't do. */ public class RectShadowPainter { private static final int START_COLOR = ResourceHelper.getColor("#37000000"); private static final int END_COLOR = ResourceHelper.getColor("#03000000"); private static final float PERPENDICULAR_ANGLE = 90f; public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) { float shadowSize = elevationToShadow(elevation); int saved = modifyCanvas(canvas, shadowSize); if (saved == -1) { return; } try { Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); cornerPaint.setStyle(Style.FILL); Paint edgePaint = new Paint(cornerPaint); edgePaint.setAntiAlias(false); Rect outline = viewOutline.mRect; float radius = viewOutline.mRadius; float outerArcRadius = radius + shadowSize; int[] colors = {START_COLOR, START_COLOR, 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, START_COLOR, 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 sideShadow(canvas, edgePaint, edgeShadowRect, outline.left, inset.top, 0); // Right shadow sideShadow(canvas, edgePaint, edgeShadowRect, outline.right, inset.bottom, 2); // Top shadow edgeShadowRect.set(-shadowSize, 0, 0, inset.width()); sideShadow(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)); sideShadow(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); } finally { canvas.restoreToCount(saved); } } private static float elevationToShadow(float elevation) { // The factor is chosen by eyeballing the shadow size on device and preview. return elevation * 0.5f; } /** * Translate canvas by half of shadow size up, so that it appears that light is coming * slightly from above. Also, remove clipping, so that shadow is not clipped. */ private static int modifyCanvas(Canvas canvas, float shadowSize) { Rect clipBounds = canvas.getClipBounds(); if (clipBounds.isEmpty()) { return -1; } int saved = canvas.save(); // Usually canvas has been translated to the top left corner of the view when this is // called. So, setting a clip rect at 0,0 will clip the top left part of the shadow. // Thus, we just expand in each direction by width and height of the canvas. canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(), canvas.getHeight(), Op.REPLACE); canvas.translate(0, shadowSize / 2f); return saved; } private static void sideShadow(Canvas canvas, Paint edgePaint, RectF edgeShadowRect, float dx, float dy, int rotations) { int saved = canvas.save(); canvas.translate(dx, dy); canvas.rotate(rotations * PERPENDICULAR_ANGLE); canvas.drawRect(edgeShadowRect, 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(Canvas canvas, Paint paint, 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); } } tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java +21 −69 Original line number Diff line number Diff line Loading @@ -16,12 +16,9 @@ package android.view; import com.android.annotations.NonNull; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.resources.Density; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap_Delegate; import android.graphics.Canvas; Loading @@ -29,8 +26,6 @@ import android.graphics.Outline; import android.graphics.Path_Delegate; import android.graphics.Rect; import android.graphics.Region.Op; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.animation.Transformation; import java.awt.Graphics2D; Loading @@ -50,33 +45,36 @@ public class ViewGroup_Delegate { @LayoutlibDelegate /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child, long drawingTime) { boolean retVal = thisVG.drawChild_Original(canvas, child, drawingTime); if (child.getZ() > thisVG.getZ()) { ViewOutlineProvider outlineProvider = child.getOutlineProvider(); Outline outline = new Outline(); outlineProvider.getOutline(child, outline); if (outline.mPath == null && outline.mRect == null) { // Sometimes, the bounds of the background drawable are not set until View.draw() // is called. So, we set the bounds manually and try to get the outline again. child.getBackground().setBounds(0, 0, child.mRight - child.mLeft, child.mBottom - child.mTop); outlineProvider.getOutline(child, outline); } if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) { int restoreTo = transformCanvas(thisVG, canvas, child); drawShadow(thisVG, canvas, child, outline); canvas.restoreToCount(restoreTo); } } return retVal; return thisVG.drawChild_Original(canvas, child, drawingTime); } private static void drawShadow(ViewGroup parent, Canvas canvas, View child, Outline outline) { BufferedImage shadow = null; int x = 0; float elevation = getElevation(child, parent); if(outline.mRect != null) { Shadow s = getRectShadow(parent, canvas, child, outline); if (s != null) { shadow = s.mShadow; x = -s.mShadowWidth; RectShadowPainter.paintShadow(outline, elevation, canvas); return; } } else if (outline.mPath != null) { shadow = getPathShadow(child, outline, canvas); BufferedImage shadow = null; if (outline.mPath != null) { shadow = getPathShadow(outline, canvas, elevation); } if (shadow == null) { return; Loading @@ -85,52 +83,17 @@ public class ViewGroup_Delegate { Density.getEnum(canvas.getDensity())); Rect clipBounds = canvas.getClipBounds(); Rect newBounds = new Rect(clipBounds); newBounds.left = newBounds.left + x; newBounds.inset((int)-elevation, (int)-elevation); canvas.clipRect(newBounds, Op.REPLACE); canvas.drawBitmap(bitmap, x, 0, null); canvas.drawBitmap(bitmap, 0, 0, null); canvas.clipRect(clipBounds, Op.REPLACE); } private static Shadow getRectShadow(ViewGroup parent, Canvas canvas, View child, Outline outline) { BufferedImage shadow; Rect clipBounds = canvas.getClipBounds(); if (clipBounds.isEmpty()) { return null; } float height = child.getZ() - parent.getZ(); // Draw large shadow if difference in z index is more than 10dp float largeShadowThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, getMetrics(child)); boolean largeShadow = height > largeShadowThreshold; int shadowSize = largeShadow ? ShadowPainter.SHADOW_SIZE : ShadowPainter.SMALL_SHADOW_SIZE; shadow = new BufferedImage(clipBounds.width() + shadowSize, clipBounds.height(), BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = shadow.createGraphics(); Rect rect = outline.mRect; if (largeShadow) { ShadowPainter.drawRectangleShadow(graphics, rect.left + shadowSize, rect.top, rect.width(), rect.height()); } else { ShadowPainter.drawSmallRectangleShadow(graphics, rect.left + shadowSize, rect.top, rect.width(), rect.height()); } graphics.dispose(); return new Shadow(shadow, shadowSize); } @NonNull private static DisplayMetrics getMetrics(View view) { Context context = view.getContext(); context = BridgeContext.getBaseContext(context); if (context instanceof BridgeContext) { return ((BridgeContext) context).getMetrics(); } throw new RuntimeException("View " + view.getClass().getName() + " not created with the " + "right context"); private static float getElevation(View child, ViewGroup parent) { return child.getZ() - parent.getZ(); } private static BufferedImage getPathShadow(View child, Outline outline, Canvas canvas) { private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) { Rect clipBounds = canvas.getClipBounds(); if (clipBounds.isEmpty()) { return null; Loading @@ -140,7 +103,7 @@ public class ViewGroup_Delegate { Graphics2D graphics = image.createGraphics(); graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape()); graphics.dispose(); return ShadowPainter.createDropShadow(image, ((int) child.getZ())); return ShadowPainter.createDropShadow(image, (int) elevation); } // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths Loading Loading @@ -194,15 +157,4 @@ public class ViewGroup_Delegate { } return restoreTo; } private static class Shadow { public BufferedImage mShadow; public int mShadowWidth; public Shadow(BufferedImage shadow, int shadowWidth) { mShadow = shadow; mShadowWidth = shadowWidth; } } } Loading
tools/layoutlib/bridge/src/android/view/RectShadowPainter.java 0 → 100644 +156 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import com.android.layoutlib.bridge.impl.ResourceHelper; 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.Region.Op; import android.graphics.Shader.TileMode; /** * Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly, * since it modifies the size of the content, that we can't do. */ public class RectShadowPainter { private static final int START_COLOR = ResourceHelper.getColor("#37000000"); private static final int END_COLOR = ResourceHelper.getColor("#03000000"); private static final float PERPENDICULAR_ANGLE = 90f; public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) { float shadowSize = elevationToShadow(elevation); int saved = modifyCanvas(canvas, shadowSize); if (saved == -1) { return; } try { Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); cornerPaint.setStyle(Style.FILL); Paint edgePaint = new Paint(cornerPaint); edgePaint.setAntiAlias(false); Rect outline = viewOutline.mRect; float radius = viewOutline.mRadius; float outerArcRadius = radius + shadowSize; int[] colors = {START_COLOR, START_COLOR, 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, START_COLOR, 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 sideShadow(canvas, edgePaint, edgeShadowRect, outline.left, inset.top, 0); // Right shadow sideShadow(canvas, edgePaint, edgeShadowRect, outline.right, inset.bottom, 2); // Top shadow edgeShadowRect.set(-shadowSize, 0, 0, inset.width()); sideShadow(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)); sideShadow(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); } finally { canvas.restoreToCount(saved); } } private static float elevationToShadow(float elevation) { // The factor is chosen by eyeballing the shadow size on device and preview. return elevation * 0.5f; } /** * Translate canvas by half of shadow size up, so that it appears that light is coming * slightly from above. Also, remove clipping, so that shadow is not clipped. */ private static int modifyCanvas(Canvas canvas, float shadowSize) { Rect clipBounds = canvas.getClipBounds(); if (clipBounds.isEmpty()) { return -1; } int saved = canvas.save(); // Usually canvas has been translated to the top left corner of the view when this is // called. So, setting a clip rect at 0,0 will clip the top left part of the shadow. // Thus, we just expand in each direction by width and height of the canvas. canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(), canvas.getHeight(), Op.REPLACE); canvas.translate(0, shadowSize / 2f); return saved; } private static void sideShadow(Canvas canvas, Paint edgePaint, RectF edgeShadowRect, float dx, float dy, int rotations) { int saved = canvas.save(); canvas.translate(dx, dy); canvas.rotate(rotations * PERPENDICULAR_ANGLE); canvas.drawRect(edgeShadowRect, 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(Canvas canvas, Paint paint, 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); } }
tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java +21 −69 Original line number Diff line number Diff line Loading @@ -16,12 +16,9 @@ package android.view; import com.android.annotations.NonNull; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.resources.Density; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap_Delegate; import android.graphics.Canvas; Loading @@ -29,8 +26,6 @@ import android.graphics.Outline; import android.graphics.Path_Delegate; import android.graphics.Rect; import android.graphics.Region.Op; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.animation.Transformation; import java.awt.Graphics2D; Loading @@ -50,33 +45,36 @@ public class ViewGroup_Delegate { @LayoutlibDelegate /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child, long drawingTime) { boolean retVal = thisVG.drawChild_Original(canvas, child, drawingTime); if (child.getZ() > thisVG.getZ()) { ViewOutlineProvider outlineProvider = child.getOutlineProvider(); Outline outline = new Outline(); outlineProvider.getOutline(child, outline); if (outline.mPath == null && outline.mRect == null) { // Sometimes, the bounds of the background drawable are not set until View.draw() // is called. So, we set the bounds manually and try to get the outline again. child.getBackground().setBounds(0, 0, child.mRight - child.mLeft, child.mBottom - child.mTop); outlineProvider.getOutline(child, outline); } if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) { int restoreTo = transformCanvas(thisVG, canvas, child); drawShadow(thisVG, canvas, child, outline); canvas.restoreToCount(restoreTo); } } return retVal; return thisVG.drawChild_Original(canvas, child, drawingTime); } private static void drawShadow(ViewGroup parent, Canvas canvas, View child, Outline outline) { BufferedImage shadow = null; int x = 0; float elevation = getElevation(child, parent); if(outline.mRect != null) { Shadow s = getRectShadow(parent, canvas, child, outline); if (s != null) { shadow = s.mShadow; x = -s.mShadowWidth; RectShadowPainter.paintShadow(outline, elevation, canvas); return; } } else if (outline.mPath != null) { shadow = getPathShadow(child, outline, canvas); BufferedImage shadow = null; if (outline.mPath != null) { shadow = getPathShadow(outline, canvas, elevation); } if (shadow == null) { return; Loading @@ -85,52 +83,17 @@ public class ViewGroup_Delegate { Density.getEnum(canvas.getDensity())); Rect clipBounds = canvas.getClipBounds(); Rect newBounds = new Rect(clipBounds); newBounds.left = newBounds.left + x; newBounds.inset((int)-elevation, (int)-elevation); canvas.clipRect(newBounds, Op.REPLACE); canvas.drawBitmap(bitmap, x, 0, null); canvas.drawBitmap(bitmap, 0, 0, null); canvas.clipRect(clipBounds, Op.REPLACE); } private static Shadow getRectShadow(ViewGroup parent, Canvas canvas, View child, Outline outline) { BufferedImage shadow; Rect clipBounds = canvas.getClipBounds(); if (clipBounds.isEmpty()) { return null; } float height = child.getZ() - parent.getZ(); // Draw large shadow if difference in z index is more than 10dp float largeShadowThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, getMetrics(child)); boolean largeShadow = height > largeShadowThreshold; int shadowSize = largeShadow ? ShadowPainter.SHADOW_SIZE : ShadowPainter.SMALL_SHADOW_SIZE; shadow = new BufferedImage(clipBounds.width() + shadowSize, clipBounds.height(), BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = shadow.createGraphics(); Rect rect = outline.mRect; if (largeShadow) { ShadowPainter.drawRectangleShadow(graphics, rect.left + shadowSize, rect.top, rect.width(), rect.height()); } else { ShadowPainter.drawSmallRectangleShadow(graphics, rect.left + shadowSize, rect.top, rect.width(), rect.height()); } graphics.dispose(); return new Shadow(shadow, shadowSize); } @NonNull private static DisplayMetrics getMetrics(View view) { Context context = view.getContext(); context = BridgeContext.getBaseContext(context); if (context instanceof BridgeContext) { return ((BridgeContext) context).getMetrics(); } throw new RuntimeException("View " + view.getClass().getName() + " not created with the " + "right context"); private static float getElevation(View child, ViewGroup parent) { return child.getZ() - parent.getZ(); } private static BufferedImage getPathShadow(View child, Outline outline, Canvas canvas) { private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) { Rect clipBounds = canvas.getClipBounds(); if (clipBounds.isEmpty()) { return null; Loading @@ -140,7 +103,7 @@ public class ViewGroup_Delegate { Graphics2D graphics = image.createGraphics(); graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape()); graphics.dispose(); return ShadowPainter.createDropShadow(image, ((int) child.getZ())); return ShadowPainter.createDropShadow(image, (int) elevation); } // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths Loading Loading @@ -194,15 +157,4 @@ public class ViewGroup_Delegate { } return restoreTo; } private static class Shadow { public BufferedImage mShadow; public int mShadowWidth; public Shadow(BufferedImage shadow, int shadowWidth) { mShadow = shadow; mShadowWidth = shadowWidth; } } }