1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.view; 18 19import com.android.annotations.NonNull; 20import com.android.layoutlib.bridge.android.BridgeContext; 21import com.android.resources.Density; 22import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 23 24import android.content.Context; 25import android.graphics.Bitmap; 26import android.graphics.Bitmap_Delegate; 27import android.graphics.Canvas; 28import android.graphics.Outline; 29import android.graphics.Path_Delegate; 30import android.graphics.Rect; 31import android.graphics.Region.Op; 32import android.util.DisplayMetrics; 33import android.util.TypedValue; 34import android.view.animation.Transformation; 35 36import java.awt.Graphics2D; 37import java.awt.image.BufferedImage; 38 39/** 40 * Delegate used to provide new implementation of a select few methods of {@link ViewGroup} 41 * <p/> 42 * Through the layoutlib_create tool, the original methods of ViewGroup have been replaced by calls 43 * to methods of the same name in this delegate class. 44 */ 45public class ViewGroup_Delegate { 46 47 /** 48 * Overrides the original drawChild call in ViewGroup to draw the shadow. 49 */ 50 @LayoutlibDelegate 51 /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child, 52 long drawingTime) { 53 boolean retVal = thisVG.drawChild_Original(canvas, child, drawingTime); 54 if (child.getZ() > thisVG.getZ()) { 55 ViewOutlineProvider outlineProvider = child.getOutlineProvider(); 56 Outline outline = new Outline(); 57 outlineProvider.getOutline(child, outline); 58 59 if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) { 60 int restoreTo = transformCanvas(thisVG, canvas, child); 61 drawShadow(thisVG, canvas, child, outline); 62 canvas.restoreToCount(restoreTo); 63 } 64 } 65 return retVal; 66 } 67 68 private static void drawShadow(ViewGroup parent, Canvas canvas, View child, 69 Outline outline) { 70 BufferedImage shadow = null; 71 int x = 0; 72 if (outline.mRect != null) { 73 Shadow s = getRectShadow(parent, canvas, child, outline); 74 shadow = s.mShadow; 75 x = -s.mShadowWidth; 76 } else if (outline.mPath != null) { 77 shadow = getPathShadow(child, outline, canvas); 78 } 79 if (shadow == null) { 80 return; 81 } 82 Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false, 83 Density.getEnum(canvas.getDensity())); 84 Rect clipBounds = canvas.getClipBounds(); 85 Rect newBounds = new Rect(clipBounds); 86 newBounds.left = newBounds.left + x; 87 canvas.clipRect(newBounds, Op.REPLACE); 88 canvas.drawBitmap(bitmap, x, 0, null); 89 canvas.clipRect(clipBounds, Op.REPLACE); 90 } 91 92 private static Shadow getRectShadow(ViewGroup parent, Canvas canvas, View child, 93 Outline outline) { 94 BufferedImage shadow; 95 Rect clipBounds = canvas.getClipBounds(); 96 if (clipBounds.isEmpty()) { 97 return null; 98 } 99 float height = child.getZ() - parent.getZ(); 100 // Draw large shadow if difference in z index is more than 10dp 101 float largeShadowThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, 102 getMetrics(child)); 103 boolean largeShadow = height > largeShadowThreshold; 104 int shadowSize = largeShadow ? ShadowPainter.SHADOW_SIZE : ShadowPainter.SMALL_SHADOW_SIZE; 105 shadow = new BufferedImage(clipBounds.width() + shadowSize, clipBounds.height(), 106 BufferedImage.TYPE_INT_ARGB); 107 Graphics2D graphics = shadow.createGraphics(); 108 Rect rect = outline.mRect; 109 if (largeShadow) { 110 ShadowPainter.drawRectangleShadow(graphics, 111 rect.left + shadowSize, rect.top, rect.width(), rect.height()); 112 } else { 113 ShadowPainter.drawSmallRectangleShadow(graphics, 114 rect.left + shadowSize, rect.top, rect.width(), rect.height()); 115 } 116 graphics.dispose(); 117 return new Shadow(shadow, shadowSize); 118 } 119 120 @NonNull 121 private static DisplayMetrics getMetrics(View view) { 122 Context context = view.getContext(); 123 while (context instanceof ContextThemeWrapper) { 124 context = ((ContextThemeWrapper) context).getBaseContext(); 125 } 126 if (context instanceof BridgeContext) { 127 return ((BridgeContext) context).getMetrics(); 128 } 129 throw new RuntimeException("View " + view.getClass().getName() + " not created with the " + 130 "right context"); 131 } 132 133 private static BufferedImage getPathShadow(View child, Outline outline, Canvas canvas) { 134 Rect clipBounds = canvas.getClipBounds(); 135 BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(), 136 BufferedImage.TYPE_INT_ARGB); 137 Graphics2D graphics = image.createGraphics(); 138 graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape()); 139 graphics.dispose(); 140 return ShadowPainter.createDropShadow(image, ((int) child.getZ())); 141 } 142 143 // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths 144 // which were never taken. Ideally, we should hook up the shadow code in the same method so 145 // that we don't have to transform the canvas twice. 146 private static int transformCanvas(ViewGroup thisVG, Canvas canvas, View child) { 147 final int restoreTo = canvas.save(); 148 final boolean childHasIdentityMatrix = child.hasIdentityMatrix(); 149 int flags = thisVG.mGroupFlags; 150 Transformation transformToApply = null; 151 boolean concatMatrix = false; 152 if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { 153 final Transformation t = thisVG.getChildTransformation(); 154 final boolean hasTransform = thisVG.getChildStaticTransformation(child, t); 155 if (hasTransform) { 156 final int transformType = t.getTransformationType(); 157 transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null; 158 concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0; 159 } 160 } 161 concatMatrix |= childHasIdentityMatrix; 162 163 child.computeScroll(); 164 int sx = child.mScrollX; 165 int sy = child.mScrollY; 166 167 canvas.translate(child.mLeft - sx, child.mTop - sy); 168 float alpha = child.getAlpha() * child.getTransitionAlpha(); 169 170 if (transformToApply != null || alpha < 1 || !childHasIdentityMatrix) { 171 if (transformToApply != null || !childHasIdentityMatrix) { 172 int transX = -sx; 173 int transY = -sy; 174 175 if (transformToApply != null) { 176 if (concatMatrix) { 177 // Undo the scroll translation, apply the transformation matrix, 178 // then redo the scroll translate to get the correct result. 179 canvas.translate(-transX, -transY); 180 canvas.concat(transformToApply.getMatrix()); 181 canvas.translate(transX, transY); 182 } 183 if (!childHasIdentityMatrix) { 184 canvas.translate(-transX, -transY); 185 canvas.concat(child.getMatrix()); 186 canvas.translate(transX, transY); 187 } 188 } 189 190 } 191 } 192 return restoreTo; 193 } 194 195 private static class Shadow { 196 public BufferedImage mShadow; 197 public int mShadowWidth; 198 199 public Shadow(BufferedImage shadow, int shadowWidth) { 200 mShadow = shadow; 201 mShadowWidth = shadowWidth; 202 } 203 204 } 205} 206