diff options
author | Deepanshu Gupta <deepanshu@google.com> | 2014-11-10 22:05:23 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-11-10 22:05:23 +0000 |
commit | 828db35568459638e09f86be40340d281804858d (patch) | |
tree | 0b63ed1026cf47320a9ac17af5038071aae526c8 | |
parent | a09dda8162ca48bea0fea0e2804af38e216996d8 (diff) | |
parent | 3cf0640d944a0718b9e29a2134a577e72a9432b4 (diff) | |
download | frameworks_base-828db35568459638e09f86be40340d281804858d.zip frameworks_base-828db35568459638e09f86be40340d281804858d.tar.gz frameworks_base-828db35568459638e09f86be40340d281804858d.tar.bz2 |
am 3cf0640d: Merge "Add primitive shadows support to LayoutLib" into lmp-dev automerge: 97c04ac
* commit '3cf0640d944a0718b9e29a2134a577e72a9432b4':
Add primitive shadows support to LayoutLib
20 files changed, 798 insertions, 59 deletions
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-b.png b/tools/layoutlib/bridge/resources/icons/shadow-b.png Binary files differnew file mode 100644 index 0000000..68f4f4b --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow-b.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow-bl.png b/tools/layoutlib/bridge/resources/icons/shadow-bl.png Binary files differnew file mode 100644 index 0000000..ee7dbe8 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow-bl.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow-br.png b/tools/layoutlib/bridge/resources/icons/shadow-br.png Binary files differnew file mode 100644 index 0000000..c45ad77 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow-br.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow-l.png b/tools/layoutlib/bridge/resources/icons/shadow-l.png Binary files differnew file mode 100644 index 0000000..77d0bd0 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow-l.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow-r.png b/tools/layoutlib/bridge/resources/icons/shadow-r.png Binary files differnew file mode 100644 index 0000000..4af7a33 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow-r.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tl.png b/tools/layoutlib/bridge/resources/icons/shadow-tl.png Binary files differnew file mode 100644 index 0000000..424fb36 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow-tl.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tr.png b/tools/layoutlib/bridge/resources/icons/shadow-tr.png Binary files differnew file mode 100644 index 0000000..1fd0c77 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow-tr.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-b.png b/tools/layoutlib/bridge/resources/icons/shadow2-b.png Binary files differnew file mode 100644 index 0000000..963973e --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow2-b.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-bl.png b/tools/layoutlib/bridge/resources/icons/shadow2-bl.png Binary files differnew file mode 100644 index 0000000..7612487 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow2-bl.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-br.png b/tools/layoutlib/bridge/resources/icons/shadow2-br.png Binary files differnew file mode 100644 index 0000000..8e20252 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow2-br.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-l.png b/tools/layoutlib/bridge/resources/icons/shadow2-l.png Binary files differnew file mode 100644 index 0000000..2db18a0 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow2-l.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-r.png b/tools/layoutlib/bridge/resources/icons/shadow2-r.png Binary files differnew file mode 100644 index 0000000..8e026f1 --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow2-r.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tl.png b/tools/layoutlib/bridge/resources/icons/shadow2-tl.png Binary files differnew file mode 100644 index 0000000..a8045ed --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow2-tl.png diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tr.png b/tools/layoutlib/bridge/resources/icons/shadow2-tr.png Binary files differnew file mode 100644 index 0000000..590373c --- /dev/null +++ b/tools/layoutlib/bridge/resources/icons/shadow2-tr.png diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java new file mode 100644 index 0000000..6c949d9 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 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.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of {@link RenderNode} + * <p/> + * Through the layoutlib_create tool, some native methods of RenderNode have been replaced by calls + * to methods of the same name in this delegate class. + * + * @see DelegateManager + */ +public class RenderNode_Delegate { + + + // ---- delegate manager ---- + private static final DelegateManager<RenderNode_Delegate> sManager = + new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class); + + + private float mLift; + @SuppressWarnings("UnusedDeclaration") + private String mName; + + @LayoutlibDelegate + /*package*/ static long nCreate(String name) { + RenderNode_Delegate renderNodeDelegate = new RenderNode_Delegate(); + renderNodeDelegate.mName = name; + return sManager.addNewDelegate(renderNodeDelegate); + } + + @LayoutlibDelegate + /*package*/ static void nDestroyRenderNode(long renderNode) { + sManager.removeJavaReferenceFor(renderNode); + } + + @LayoutlibDelegate + /*package*/ static boolean nSetElevation(long renderNode, float lift) { + RenderNode_Delegate delegate = sManager.getDelegate(renderNode); + if (delegate != null && delegate.mLift != lift) { + delegate.mLift = lift; + return true; + } + return false; + } + + @LayoutlibDelegate + /*package*/ static float nGetElevation(long renderNode) { + RenderNode_Delegate delegate = sManager.getDelegate(renderNode); + if (delegate != null) { + return delegate.mLift; + } + return 0f; + } +} diff --git a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java new file mode 100644 index 0000000..38846bd --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2014 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.annotations.NonNull; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +public class ShadowPainter { + + /** + * Adds a drop shadow to a semi-transparent image (of an arbitrary shape) and returns it as a + * new image. This method attempts to mimic the same visual characteristics as the rectangular + * shadow painting methods in this class, {@link #createRectangularDropShadow(java.awt.image.BufferedImage)} + * and {@link #createSmallRectangularDropShadow(java.awt.image.BufferedImage)}. + * + * @param source the source image + * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link + * #SMALL_SHADOW_SIZE}} + * + * @return a new image with the shadow painted in + */ + @NonNull + public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) { + shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class + + return createDropShadow(source, shadowSize, 0.7f, 0); + } + + /** + * Creates a drop shadow of a given image and returns a new image which shows the input image on + * top of its drop shadow. + * <p/> + * <b>NOTE: If the shape is rectangular and opaque, consider using {@link + * #drawRectangleShadow(Graphics2D, int, int, int, int)} instead.</b> + * + * @param source the source image to be shadowed + * @param shadowSize the size of the shadow in pixels + * @param shadowOpacity the opacity of the shadow, with 0=transparent and 1=opaque + * @param shadowRgb the RGB int to use for the shadow color + * + * @return a new image with the source image on top of its shadow + */ + @SuppressWarnings({"SuspiciousNameCombination", "UnnecessaryLocalVariable"}) // Imported code + public static BufferedImage createDropShadow(BufferedImage source, int shadowSize, + float shadowOpacity, int shadowRgb) { + + // This code is based on + // http://www.jroller.com/gfx/entry/non_rectangular_shadow + + BufferedImage image; + int width = source.getWidth(); + int height = source.getHeight(); + image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE, + BufferedImage.TYPE_INT_ARGB); + + Graphics2D g2 = image.createGraphics(); + g2.drawImage(image, shadowSize, shadowSize, null); + + int dstWidth = image.getWidth(); + int dstHeight = image.getHeight(); + + int left = (shadowSize - 1) >> 1; + int right = shadowSize - left; + int xStart = left; + int xStop = dstWidth - right; + int yStart = left; + int yStop = dstHeight - right; + + shadowRgb &= 0x00FFFFFF; + + int[] aHistory = new int[shadowSize]; + int historyIdx; + + int aSum; + + int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + int lastPixelOffset = right * dstWidth; + float sumDivider = shadowOpacity / shadowSize; + + // horizontal pass + for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) { + aSum = 0; + historyIdx = 0; + for (int x = 0; x < shadowSize; x++, bufferOffset++) { + int a = dataBuffer[bufferOffset] >>> 24; + aHistory[x] = a; + aSum += a; + } + + bufferOffset -= right; + + for (int x = xStart; x < xStop; x++, bufferOffset++) { + int a = (int) (aSum * sumDivider); + dataBuffer[bufferOffset] = a << 24 | shadowRgb; + + // subtract the oldest pixel from the sum + aSum -= aHistory[historyIdx]; + + // get the latest pixel + a = dataBuffer[bufferOffset + right] >>> 24; + aHistory[historyIdx] = a; + aSum += a; + + if (++historyIdx >= shadowSize) { + historyIdx -= shadowSize; + } + } + } + // vertical pass + for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) { + aSum = 0; + historyIdx = 0; + for (int y = 0; y < shadowSize; y++, bufferOffset += dstWidth) { + int a = dataBuffer[bufferOffset] >>> 24; + aHistory[y] = a; + aSum += a; + } + + bufferOffset -= lastPixelOffset; + + for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) { + int a = (int) (aSum * sumDivider); + dataBuffer[bufferOffset] = a << 24 | shadowRgb; + + // subtract the oldest pixel from the sum + aSum -= aHistory[historyIdx]; + + // get the latest pixel + a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24; + aHistory[historyIdx] = a; + aSum += a; + + if (++historyIdx >= shadowSize) { + historyIdx -= shadowSize; + } + } + } + + g2.drawImage(source, null, 0, 0); + g2.dispose(); + + return image; + } + + /** + * Draws a rectangular drop shadow (of size {@link #SHADOW_SIZE} by {@link #SHADOW_SIZE} around + * the given source and returns a new image with both combined + * + * @param source the source image + * + * @return the source image with a drop shadow on the bottom and right + */ + @SuppressWarnings("UnusedDeclaration") + public static BufferedImage createRectangularDropShadow(BufferedImage source) { + int type = source.getType(); + if (type == BufferedImage.TYPE_CUSTOM) { + type = BufferedImage.TYPE_INT_ARGB; + } + + int width = source.getWidth(); + int height = source.getHeight(); + BufferedImage image; + image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE, type); + Graphics2D g = image.createGraphics(); + g.drawImage(source, 0, 0, null); + drawRectangleShadow(image, 0, 0, width, height); + g.dispose(); + + return image; + } + + /** + * Draws a small rectangular drop shadow (of size {@link #SMALL_SHADOW_SIZE} by {@link + * #SMALL_SHADOW_SIZE} around the given source and returns a new image with both combined + * + * @param source the source image + * + * @return the source image with a drop shadow on the bottom and right + */ + @SuppressWarnings("UnusedDeclaration") + public static BufferedImage createSmallRectangularDropShadow(BufferedImage source) { + int type = source.getType(); + if (type == BufferedImage.TYPE_CUSTOM) { + type = BufferedImage.TYPE_INT_ARGB; + } + + int width = source.getWidth(); + int height = source.getHeight(); + + BufferedImage image; + image = new BufferedImage(width + SMALL_SHADOW_SIZE, height + SMALL_SHADOW_SIZE, type); + + Graphics2D g = image.createGraphics(); + g.drawImage(source, 0, 0, null); + drawSmallRectangleShadow(image, 0, 0, width, height); + g.dispose(); + + return image; + } + + /** + * Draws a drop shadow for the given rectangle into the given context. It will not draw anything + * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow + * graphics. The size of the shadow is {@link #SHADOW_SIZE}. + * + * @param image the image to draw the shadow into + * @param x the left coordinate of the left hand side of the rectangle + * @param y the top coordinate of the top of the rectangle + * @param width the width of the rectangle + * @param height the height of the rectangle + */ + public static void drawRectangleShadow(BufferedImage image, + int x, int y, int width, int height) { + Graphics2D gc = image.createGraphics(); + try { + drawRectangleShadow(gc, x, y, width, height); + } finally { + gc.dispose(); + } + } + + /** + * Draws a small drop shadow for the given rectangle into the given context. It will not draw + * anything if the rectangle is smaller than a minimum determined by the assets used to draw the + * shadow graphics. The size of the shadow is {@link #SMALL_SHADOW_SIZE}. + * + * @param image the image to draw the shadow into + * @param x the left coordinate of the left hand side of the rectangle + * @param y the top coordinate of the top of the rectangle + * @param width the width of the rectangle + * @param height the height of the rectangle + */ + public static void drawSmallRectangleShadow(BufferedImage image, + int x, int y, int width, int height) { + Graphics2D gc = image.createGraphics(); + try { + drawSmallRectangleShadow(gc, x, y, width, height); + } finally { + gc.dispose(); + } + } + + /** + * The width and height of the drop shadow painted by + * {@link #drawRectangleShadow(Graphics2D, int, int, int, int)} + */ + public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics + + /** + * The width and height of the drop shadow painted by + * {@link #drawSmallRectangleShadow(Graphics2D, int, int, int, int)} + */ + public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics + + /** + * Draws a drop shadow for the given rectangle into the given context. It will not draw anything + * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow + * graphics. + * + * @param gc the graphics context to draw into + * @param x the left coordinate of the left hand side of the rectangle + * @param y the top coordinate of the top of the rectangle + * @param width the width of the rectangle + * @param height the height of the rectangle + */ + public static void drawRectangleShadow(Graphics2D gc, int x, int y, int width, int height) { + assert ShadowBottomLeft != null; + assert ShadowBottomRight.getWidth(null) == SHADOW_SIZE; + assert ShadowBottomRight.getHeight(null) == SHADOW_SIZE; + + int blWidth = ShadowBottomLeft.getWidth(null); + int trHeight = ShadowTopRight.getHeight(null); + if (width < blWidth) { + return; + } + if (height < trHeight) { + return; + } + + gc.drawImage(ShadowBottomLeft, x - ShadowBottomLeft.getWidth(null), y + height, null); + gc.drawImage(ShadowBottomRight, x + width, y + height, null); + gc.drawImage(ShadowTopRight, x + width, y, null); + gc.drawImage(ShadowTopLeft, x - ShadowTopLeft.getWidth(null), y, null); + gc.drawImage(ShadowBottom, + x, y + height, x + width, y + height + ShadowBottom.getHeight(null), + 0, 0, ShadowBottom.getWidth(null), ShadowBottom.getHeight(null), null); + gc.drawImage(ShadowRight, + x + width, y + ShadowTopRight.getHeight(null), x + width + ShadowRight.getWidth(null), y + height, + 0, 0, ShadowRight.getWidth(null), ShadowRight.getHeight(null), null); + gc.drawImage(ShadowLeft, + x - ShadowLeft.getWidth(null), y + ShadowTopLeft.getHeight(null), x, y + height, + 0, 0, ShadowLeft.getWidth(null), ShadowLeft.getHeight(null), null); + } + + /** + * Draws a small drop shadow for the given rectangle into the given context. It will not draw + * anything if the rectangle is smaller than a minimum determined by the assets used to draw the + * shadow graphics. + * <p/> + * + * @param gc the graphics context to draw into + * @param x the left coordinate of the left hand side of the rectangle + * @param y the top coordinate of the top of the rectangle + * @param width the width of the rectangle + * @param height the height of the rectangle + */ + public static void drawSmallRectangleShadow(Graphics2D gc, int x, int y, int width, + int height) { + assert Shadow2BottomLeft != null; + assert Shadow2TopRight != null; + assert Shadow2BottomRight.getWidth(null) == SMALL_SHADOW_SIZE; + assert Shadow2BottomRight.getHeight(null) == SMALL_SHADOW_SIZE; + + int blWidth = Shadow2BottomLeft.getWidth(null); + int trHeight = Shadow2TopRight.getHeight(null); + if (width < blWidth) { + return; + } + if (height < trHeight) { + return; + } + + gc.drawImage(Shadow2BottomLeft, x - Shadow2BottomLeft.getWidth(null), y + height, null); + gc.drawImage(Shadow2BottomRight, x + width, y + height, null); + gc.drawImage(Shadow2TopRight, x + width, y, null); + gc.drawImage(Shadow2TopLeft, x - Shadow2TopLeft.getWidth(null), y, null); + gc.drawImage(Shadow2Bottom, + x, y + height, x + width, y + height + Shadow2Bottom.getHeight(null), + 0, 0, Shadow2Bottom.getWidth(null), Shadow2Bottom.getHeight(null), null); + gc.drawImage(Shadow2Right, + x + width, y + Shadow2TopRight.getHeight(null), x + width + Shadow2Right.getWidth(null), y + height, + 0, 0, Shadow2Right.getWidth(null), Shadow2Right.getHeight(null), null); + gc.drawImage(Shadow2Left, + x - Shadow2Left.getWidth(null), y + Shadow2TopLeft.getHeight(null), x, y + height, + 0, 0, Shadow2Left.getWidth(null), Shadow2Left.getHeight(null), null); + } + + private static Image loadIcon(String name) { + InputStream inputStream = ShadowPainter.class.getResourceAsStream(name); + if (inputStream == null) { + throw new RuntimeException("Unable to load image for shadow: " + name); + } + try { + return ImageIO.read(inputStream); + } catch (IOException e) { + throw new RuntimeException("Unable to load image for shadow:" + name, e); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + // ignore. + } + } + } + + // Shadow graphics. This was generated by creating a drop shadow in + // Gimp, using the parameters x offset=10, y offset=10, blur radius=10, + // (for the small drop shadows x offset=10, y offset=10, blur radius=10) + // color=black, and opacity=51. These values attempt to make a shadow + // that is legible both for dark and light themes, on top of the + // canvas background (rgb(150,150,150). Darker shadows would tend to + // blend into the foreground for a dark holo screen, and lighter shadows + // would be hard to spot on the canvas background. If you make adjustments, + // make sure to check the shadow with both dark and light themes. + // + // After making the graphics, I cut out the top right, bottom left + // and bottom right corners as 20x20 images, and these are reproduced by + // painting them in the corresponding places in the target graphics context. + // I then grabbed a single horizontal gradient line from the middle of the + // right edge,and a single vertical gradient line from the bottom. These + // are then painted scaled/stretched in the target to fill the gaps between + // the three corner images. + // + // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right + + // Normal Drop Shadow + private static final Image ShadowBottom = loadIcon("/icons/shadow-b.png"); + private static final Image ShadowBottomLeft = loadIcon("/icons/shadow-bl.png"); + private static final Image ShadowBottomRight = loadIcon("/icons/shadow-br.png"); + private static final Image ShadowRight = loadIcon("/icons/shadow-r.png"); + private static final Image ShadowTopRight = loadIcon("/icons/shadow-tr.png"); + private static final Image ShadowTopLeft = loadIcon("/icons/shadow-tl.png"); + private static final Image ShadowLeft = loadIcon("/icons/shadow-l.png"); + + // Small Drop Shadow + private static final Image Shadow2Bottom = loadIcon("/icons/shadow2-b.png"); + private static final Image Shadow2BottomLeft = loadIcon("/icons/shadow2-bl.png"); + private static final Image Shadow2BottomRight = loadIcon("/icons/shadow2-br.png"); + private static final Image Shadow2Right = loadIcon("/icons/shadow2-r.png"); + private static final Image Shadow2TopRight = loadIcon("/icons/shadow2-tr.png"); + private static final Image Shadow2TopLeft = loadIcon("/icons/shadow2-tl.png"); + private static final Image Shadow2Left = loadIcon("/icons/shadow2-l.png"); +} diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java new file mode 100644 index 0000000..a6c00f7 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2014 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.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; +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; +import java.awt.image.BufferedImage; + +/** + * Delegate used to provide new implementation of a select few methods of {@link ViewGroup} + * <p/> + * Through the layoutlib_create tool, the original methods of ViewGroup have been replaced by calls + * to methods of the same name in this delegate class. + */ +public class ViewGroup_Delegate { + + /** + * Overrides the original drawChild call in ViewGroup to draw the shadow. + */ + @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 && !outline.mRect.isEmpty())) { + int restoreTo = transformCanvas(thisVG, canvas, child); + drawShadow(thisVG, canvas, child, outline); + canvas.restoreToCount(restoreTo); + } + } + return retVal; + } + + private static void drawShadow(ViewGroup parent, Canvas canvas, View child, + Outline outline) { + BufferedImage shadow = null; + int x = 0; + if (outline.mRect != null) { + Shadow s = getRectShadow(parent, canvas, child, outline); + shadow = s.mShadow; + x = -s.mShadowWidth; + } else if (outline.mPath != null) { + shadow = getPathShadow(child, outline, canvas); + } + if (shadow == null) { + return; + } + Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false, + Density.getEnum(canvas.getDensity())); + Rect clipBounds = canvas.getClipBounds(); + Rect newBounds = new Rect(clipBounds); + newBounds.left = newBounds.left + x; + canvas.clipRect(newBounds, Op.REPLACE); + canvas.drawBitmap(bitmap, x, 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(); + while (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } + if (context instanceof BridgeContext) { + return ((BridgeContext) context).getMetrics(); + } + throw new RuntimeException("View " + view.getClass().getName() + " not created with the " + + "right context"); + } + + private static BufferedImage getPathShadow(View child, Outline outline, Canvas canvas) { + Rect clipBounds = canvas.getClipBounds(); + BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(), + BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = image.createGraphics(); + graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape()); + graphics.dispose(); + return ShadowPainter.createDropShadow(image, ((int) child.getZ())); + } + + // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths + // which were never taken. Ideally, we should hook up the shadow code in the same method so + // that we don't have to transform the canvas twice. + private static int transformCanvas(ViewGroup thisVG, Canvas canvas, View child) { + final int restoreTo = canvas.save(); + final boolean childHasIdentityMatrix = child.hasIdentityMatrix(); + int flags = thisVG.mGroupFlags; + Transformation transformToApply = null; + boolean concatMatrix = false; + if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { + final Transformation t = thisVG.getChildTransformation(); + final boolean hasTransform = thisVG.getChildStaticTransformation(child, t); + if (hasTransform) { + final int transformType = t.getTransformationType(); + transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null; + concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0; + } + } + concatMatrix |= childHasIdentityMatrix; + + child.computeScroll(); + int sx = child.mScrollX; + int sy = child.mScrollY; + + canvas.translate(child.mLeft - sx, child.mTop - sy); + float alpha = child.getAlpha() * child.getTransitionAlpha(); + + if (transformToApply != null || alpha < 1 || !childHasIdentityMatrix) { + if (transformToApply != null || !childHasIdentityMatrix) { + int transX = -sx; + int transY = -sy; + + if (transformToApply != null) { + if (concatMatrix) { + // Undo the scroll translation, apply the transformation matrix, + // then redo the scroll translate to get the correct result. + canvas.translate(-transX, -transY); + canvas.concat(transformToApply.getMatrix()); + canvas.translate(transX, transY); + } + if (!childHasIdentityMatrix) { + canvas.translate(-transX, -transY); + canvas.concat(child.getMatrix()); + canvas.translate(transX, transY); + } + } + + } + } + return restoreTo; + } + + private static class Shadow { + public BufferedImage mShadow; + public int mShadowWidth; + + public Shadow(BufferedImage shadow, int shadowWidth) { + mShadow = shadow; + mShadowWidth = shadowWidth; + } + + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java index 57fd68e..2ff8d37 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java @@ -18,6 +18,7 @@ package com.android.layoutlib.bridge.bars; import com.android.annotations.NonNull; import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.SessionParams; import com.android.internal.R; @@ -37,7 +38,6 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.ActionMenuPresenter; import android.widget.FrameLayout; -import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; @@ -49,15 +49,23 @@ public class ActionBarLayout { private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout"; // The Action Bar - @NonNull private CustomActionBarWrapper mActionBar; + @NonNull + private CustomActionBarWrapper mActionBar; // Store another reference to the context so that we don't have to cast it repeatedly. - @NonNull private final BridgeContext mBridgeContext; + @NonNull + private final BridgeContext mBridgeContext; - @NonNull private FrameLayout mContentRoot; + @NonNull + private FrameLayout mContentRoot; // A fake parent for measuring views. - @Nullable private ViewGroup mMeasureParent; + @Nullable + private ViewGroup mMeasureParent; + + // A Layout that contains the inflated action bar. The menu popup is added to this layout. + @NonNull + private final RelativeLayout mEnclosingLayout; /** * Inflate the action bar and attach it to {@code parentView} @@ -90,20 +98,25 @@ public class ActionBarLayout { if (layoutId == 0) { throw new RuntimeException(error); } + // Create a RelativeLayout to hold the action bar. The layout is needed so that we may + // add the menu popup to it. + mEnclosingLayout = new RelativeLayout(mBridgeContext); + setMatchParent(mEnclosingLayout); + parentView.addView(mEnclosingLayout); + // Inflate action bar layout. - View decorContent = LayoutInflater.from(context).inflate(layoutId, parentView, true); + View decorContent = LayoutInflater.from(context).inflate(layoutId, mEnclosingLayout, true); mActionBar = CustomActionBarWrapper.getActionBarWrapper(context, params, decorContent); - FrameLayout contentRoot = (FrameLayout) parentView.findViewById(android.R.id.content); + FrameLayout contentRoot = (FrameLayout) mEnclosingLayout.findViewById(android.R.id.content); // If something went wrong and we were not able to initialize the content root, // just add a frame layout inside this and return. if (contentRoot == null) { contentRoot = new FrameLayout(context); - contentRoot.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT)); - parentView.addView(contentRoot); + setMatchParent(contentRoot); + mEnclosingLayout.addView(contentRoot); mContentRoot = contentRoot; } else { mContentRoot = contentRoot; @@ -112,70 +125,49 @@ public class ActionBarLayout { } } + private void setMatchParent(View view) { + view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT)); + } + /** * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to * the content frame which shall serve as the new content root. */ public void createMenuPopup() { - assert mContentRoot.getId() == android.R.id.content + assert mEnclosingLayout.getChildCount() == 1 : "Action Bar Menus have already been created."; if (!isOverflowPopupNeeded()) { return; } - // Create a layout to hold the menus and the user's content. - RelativeLayout layout = new RelativeLayout(mActionBar.getPopupContext()); - layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT)); - mContentRoot.addView(layout); - // Create a layout for the user's content. - FrameLayout contentRoot = new FrameLayout(mBridgeContext); - contentRoot.setLayoutParams(new LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - // Add contentRoot and menus to the layout. - layout.addView(contentRoot); - layout.addView(createMenuView()); - // ContentRoot is now the view we just created. - mContentRoot = contentRoot; - } - - /** - * Returns a {@link LinearLayout} containing the menu list view to be embedded in a - * {@link RelativeLayout} - */ - @NonNull - private View createMenuView() { DisplayMetrics metrics = mBridgeContext.getMetrics(); MenuBuilder menu = mActionBar.getMenuBuilder(); OverflowMenuAdapter adapter = new OverflowMenuAdapter(menu, mActionBar.getPopupContext()); - LinearLayout layout = new LinearLayout(mActionBar.getPopupContext()); + ListView listView = new ListView(mActionBar.getPopupContext(), null, + R.attr.dropDownListViewStyle); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( measureContentWidth(adapter), LayoutParams.WRAP_CONTENT); layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END); if (mActionBar.isSplit()) { layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); - // TODO: Find correct value instead of hardcoded 10dp. - layoutParams.bottomMargin = getPixelValue("-10dp", metrics); + layoutParams.bottomMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin(); } else { - layoutParams.topMargin = getPixelValue("-10dp", metrics); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + layoutParams.topMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin(); } - layout.setLayoutParams(layoutParams); + layoutParams.setMarginEnd(getPixelValue("5dp", metrics)); + listView.setLayoutParams(layoutParams); + listView.setAdapter(adapter); final TypedArray a = mActionBar.getPopupContext().obtainStyledAttributes(null, R.styleable.PopupWindow, R.attr.popupMenuStyle, 0); - layout.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground)); - layout.setDividerDrawable(a.getDrawable(R.attr.actionBarDivider)); + listView.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground)); + listView.setDivider(a.getDrawable(R.attr.actionBarDivider)); a.recycle(); - layout.setOrientation(LinearLayout.VERTICAL); - layout.setDividerPadding(getPixelValue("12dp", metrics)); - layout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); - - ListView listView = new ListView(mActionBar.getPopupContext(), null, - R.attr.dropDownListViewStyle); - listView.setAdapter(adapter); - layout.addView(listView); - return layout; + listView.setElevation(mActionBar.getMenuPopupElevation()); + mEnclosingLayout.addView(listView); } private boolean isOverflowPopupNeeded() { @@ -244,9 +236,30 @@ public class ActionBarLayout { return maxWidth; } - private int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) { + static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) { TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/); return (int) typedValue.getDimension(metrics); } + // TODO: This is duplicated from RenderSessionImpl. + private int getActionBarHeight() { + RenderResources resources = mBridgeContext.getRenderResources(); + DisplayMetrics metrics = mBridgeContext.getMetrics(); + ResourceValue value = resources.findItemInTheme("actionBarSize", true); + + // resolve it + value = resources.resolveResValue(value); + + if (value != null) { + // get the numerical value, if available + TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(), + true); + if (typedValue != null) { + // compute the pixel value based on the display metrics + return (int) typedValue.getDimension(metrics); + + } + } + return 0; + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java index 70b9cc3..6db722e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java @@ -65,18 +65,17 @@ public abstract class CustomActionBarWrapper { * Returns a wrapper around different implementations of the Action Bar to provide a common API. * * @param decorContent the top level view returned by inflating - * ?attr/windowActionBarFullscreenDecorLayout + * ?attr/windowActionBarFullscreenDecorLayout */ @NonNull public static CustomActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params, @NonNull View decorContent) { View view = decorContent.findViewById(R.id.action_bar); if (view instanceof Toolbar) { - return new ToolbarWrapper(context, params, ((Toolbar) view) - ); + return new ToolbarWrapper(context, params, ((Toolbar) view)); } else if (view instanceof ActionBarView) { - return new WindowActionBarWrapper(context, params, decorContent, ((ActionBarView) view) - ); + return new WindowActionBarWrapper(context, params, decorContent, + ((ActionBarView) view)); } else { throw new IllegalStateException("Can't make an action bar out of " + view.getClass().getSimpleName()); @@ -174,6 +173,13 @@ public abstract class CustomActionBarWrapper { @NonNull abstract DecorToolbar getDecorToolbar(); + abstract int getMenuPopupElevation(); + + /** + * Margin between the menu popup and the action bar. + */ + abstract int getMenuPopupMargin(); + // ---- The implementations ---- /** @@ -226,25 +232,38 @@ public abstract class CustomActionBarWrapper { DecorToolbar getDecorToolbar() { return mToolbar.getWrapper(); } + + @Override + int getMenuPopupElevation() { + return 10; + } + + @Override + int getMenuPopupMargin() { + return 0; + } } /** * Holo theme uses {@link WindowDecorActionBar} as the action bar. This wrapper provides * access to it using a common API. */ - private static class WindowActionBarWrapper extends CustomActionBarWrapper{ + private static class WindowActionBarWrapper extends CustomActionBarWrapper { @NonNull private final WindowDecorActionBar mActionBar; + @NonNull private final ActionBarView mActionBarView; + @NonNull + private final View mDecorContentRoot; private MenuBuilder mMenuBuilder; public WindowActionBarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params, @NonNull View decorContentRoot, @NonNull ActionBarView actionBarView) { - super(context, params, new WindowDecorActionBar(decorContentRoot) - ); + super(context, params, new WindowDecorActionBar(decorContentRoot)); mActionBarView = actionBarView; mActionBar = ((WindowDecorActionBar) super.mActionBar); + mDecorContentRoot = decorContentRoot; } @Override @@ -270,7 +289,7 @@ public abstract class CustomActionBarWrapper { } // Set action bar to be split, if needed. - ViewGroup splitView = (ViewGroup) mActionBarView.findViewById(R.id.split_action_bar); + ViewGroup splitView = (ViewGroup) mDecorContentRoot.findViewById(R.id.split_action_bar); if (splitView != null) { mActionBarView.setSplitView(splitView); Resources res = mContext.getResources(); @@ -314,6 +333,16 @@ public abstract class CustomActionBarWrapper { return mActionBarView; } + @Override + int getMenuPopupElevation() { + return 0; + } + + @Override + int getMenuPopupMargin() { + return -ActionBarLayout.getPixelValue("10dp", mContext.getMetrics()); + } + // TODO: Use an adapter, like List View to set up tabs. @SuppressWarnings("deprecation") // For Tab private void setupTabs(int num) { diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 2fcdf34..4e6f456 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -162,6 +162,11 @@ public final class CreateInfo implements ICreateInfo { "android.view.WindowManagerGlobal#getWindowManagerService", "android.view.inputmethod.InputMethodManager#getInstance", "android.view.MenuInflater#registerMenu", + "android.view.RenderNode#nCreate", + "android.view.RenderNode#nDestroyRenderNode", + "android.view.RenderNode#nSetElevation", + "android.view.RenderNode#nGetElevation", + "android.view.ViewGroup#drawChild", "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", "com.android.internal.textservice.ITextServicesManager$Stub#asInterface", |