Merge "Add primitive shadows support to LayoutLib" into lmp-dev

This commit is contained in:
Deepanshu Gupta
2014-11-10 18:58:27 +00:00
committed by Android (Google) Code Review
20 changed files with 798 additions and 59 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

View File

@ -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;
}
}

View File

@ -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");
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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",