diff options
author | Deepanshu Gupta <deepanshu@google.com> | 2015-03-25 00:01:49 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2015-03-25 00:01:49 +0000 |
commit | 6fa9d5546fa27665de3f64564897a77735b2a1b0 (patch) | |
tree | 60d8d19b6675361e84c166dc3a6c4754240cffde /tools | |
parent | 5bc625d83d60fbb27ab2297051971be91fb3e415 (diff) | |
parent | 0b76cf6ff56c59a04783714d40163576e3081cf1 (diff) | |
download | frameworks_base-6fa9d5546fa27665de3f64564897a77735b2a1b0.zip frameworks_base-6fa9d5546fa27665de3f64564897a77735b2a1b0.tar.gz frameworks_base-6fa9d5546fa27665de3f64564897a77735b2a1b0.tar.bz2 |
am 0b76cf6f: am 34751c79: Merge "Better shadows." into lmp-dev
* commit '0b76cf6ff56c59a04783714d40163576e3081cf1':
Better shadows.
Diffstat (limited to 'tools')
-rw-r--r-- | tools/layoutlib/bridge/src/android/view/RectShadowPainter.java | 156 | ||||
-rw-r--r-- | tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java | 90 |
2 files changed, 177 insertions, 69 deletions
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java new file mode 100644 index 0000000..ec3a8d6 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java @@ -0,0 +1,156 @@ +/* + * 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); + } +} diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java index 82ae1df..e72a0db 100644 --- a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java @@ -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; @@ -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; @@ -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) { + float elevation = getElevation(child, parent); + if(outline.mRect != null) { + RectShadowPainter.paintShadow(outline, elevation, canvas); + return; + } BufferedImage shadow = null; - int x = 0; - if (outline.mRect != null) { - Shadow s = getRectShadow(parent, canvas, child, outline); - if (s != null) { - shadow = s.mShadow; - x = -s.mShadowWidth; - } - } else if (outline.mPath != null) { - shadow = getPathShadow(child, outline, canvas); + if (outline.mPath != null) { + shadow = getPathShadow(outline, canvas, elevation); } if (shadow == null) { return; @@ -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; @@ -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 @@ -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; - } - - } } |