| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.weex.utils; |
| |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.BlurMaskFilter; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.ColorFilter; |
| import android.graphics.LinearGradient; |
| import android.graphics.Paint; |
| import android.graphics.Path; |
| import android.graphics.PixelFormat; |
| import android.graphics.Point; |
| import android.graphics.PointF; |
| import android.graphics.Rect; |
| import android.graphics.RectF; |
| import android.graphics.Region; |
| import android.graphics.Shader; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.LayerDrawable; |
| import android.os.Build; |
| import android.os.Build.VERSION_CODES; |
| import android.support.annotation.IntRange; |
| import android.support.annotation.Nullable; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewParent; |
| import org.apache.weex.WXEnvironment; |
| import org.apache.weex.common.WXThread; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Created by moxun on 2017/9/4. |
| * Utils for create shadow layer on view |
| * Requires Android 4.3 and higher |
| * |
| * @see <a href="https://www.w3schools.com/cssref/css3_pr_box-shadow.asp">CSS3 box-shadow Property</> |
| */ |
| |
| public class BoxShadowUtil { |
| private static final String TAG = "BoxShadowUtil"; |
| private static boolean sBoxShadowEnabled = true /*disable box-shadow temporary*/; |
| |
| private static Pattern sColorPattern; |
| |
| public static void setBoxShadowEnabled(boolean enabled) { |
| sBoxShadowEnabled = enabled; |
| WXLogUtils.w(TAG, "Switch box-shadow status: " + enabled); |
| } |
| |
| public static boolean isBoxShadowEnabled() { |
| return sBoxShadowEnabled; |
| } |
| |
| public static void setBoxShadow(final View target, String style, final float[] radii, int viewPort, final float quality) { |
| if (!sBoxShadowEnabled) { |
| WXLogUtils.w(TAG, "box-shadow was disabled by config"); |
| return; |
| } |
| |
| if (target == null) { |
| WXLogUtils.w(TAG, "Target view is null!"); |
| return; |
| } |
| |
| if (TextUtils.isEmpty(style) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { |
| target.getOverlay().clear(); |
| WXLogUtils.d(TAG, "Remove all box-shadow"); |
| return; |
| } |
| |
| final BoxShadowOptions[] shadows = parseBoxShadows(style, viewPort); |
| if (shadows == null || shadows.length == 0) { |
| WXLogUtils.w(TAG, "Failed to parse box-shadow: " + style); |
| return; |
| } |
| |
| final List<BoxShadowOptions> normalShadows = new ArrayList<>(), insetShadows = new ArrayList<>(); |
| for (BoxShadowOptions shadow : shadows) { |
| if (shadow != null) { |
| if (shadow.isInset) { |
| insetShadows.add(0, shadow); |
| } else { |
| normalShadows.add(0, shadow); |
| } |
| } |
| } |
| |
| if (radii != null) { |
| if (radii.length != 8) { |
| WXLogUtils.w(TAG, "Length of radii must be 8"); |
| } else { |
| for (int i = 0; i < radii.length; i++) { |
| float realRadius = WXViewUtils.getRealSubPxByWidth(radii[i], viewPort); |
| radii[i] = realRadius; |
| } |
| } |
| } |
| |
| target.post(WXThread.secure(new Runnable() { |
| @Override |
| public void run() { |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { |
| target.getOverlay().clear(); |
| if (normalShadows.size() > 0) { |
| setNormalBoxShadow(target, normalShadows, quality, radii); |
| } |
| |
| if (insetShadows.size() > 0) { |
| setInsetBoxShadow(target, insetShadows, quality, radii); |
| } |
| } |
| } |
| })); |
| } |
| |
| private static void drawShadow(Canvas canvas, BoxShadowOptions options) { |
| RectF shadowRect = new RectF( |
| 0f, 0f, |
| options.viewWidth + 2f * options.spread, options.viewHeight + 2f * options.spread |
| ); |
| |
| if (options.topLeft != null) { |
| shadowRect.offset(options.topLeft.x, options.topLeft.y); |
| } |
| |
| float shadowDx = options.blur; |
| float shadowDy = options.blur; |
| if (options.hShadow > 0) { |
| shadowDx = shadowDx + 2f * options.hShadow; |
| } |
| if (options.vShadow > 0) { |
| shadowDy = shadowDy + 2f * options.vShadow; |
| } |
| shadowRect.offset(shadowDx, shadowDy); |
| |
| Paint shadowPaint = new Paint(); |
| shadowPaint.setAntiAlias(true); |
| shadowPaint.setColor(options.color); |
| shadowPaint.setStyle(Paint.Style.FILL); |
| |
| if (options.blur > 0) { |
| shadowPaint.setMaskFilter(new BlurMaskFilter(options.blur, BlurMaskFilter.Blur.NORMAL)); |
| } |
| |
| Path shadowPath = new Path(); |
| float[] shadowRadii = new float[8]; |
| for (int i = 0; i < options.radii.length; i++) { |
| float contentRadius = options.radii[i]; |
| if (contentRadius == 0f) { |
| shadowRadii[i] = 0f; |
| } else { |
| shadowRadii[i] = options.radii[i] + options.spread; |
| } |
| } |
| shadowPath.addRoundRect(shadowRect, shadowRadii, Path.Direction.CCW); |
| canvas.drawPath(shadowPath, shadowPaint); |
| |
| if (false && WXEnvironment.isApkDebugable()) { |
| Paint paint = new Paint(); |
| paint.setAntiAlias(true); |
| paint.setColor(Color.BLACK); |
| paint.setStyle(Paint.Style.STROKE); |
| canvas.drawRect(shadowRect, paint); |
| } |
| } |
| |
| private static void setNormalBoxShadow(View target, List<BoxShadowOptions> options, float quality, float[] radii) { |
| int h = target.getHeight(); |
| int w = target.getWidth(); |
| |
| ViewGroup.LayoutParams p = target.getLayoutParams(); |
| |
| if (h == 0 || w == 0) { |
| Log.w(TAG, "Target view is invisible, ignore set shadow."); |
| return; |
| } |
| |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { |
| int maxWidth = 0, maxHeight = 0; |
| for (BoxShadowOptions option : options) { |
| option.viewWidth = w; |
| option.viewHeight = h; |
| option.radii = radii; |
| |
| Rect rect = option.getTargetCanvasRect(); |
| if (maxWidth < rect.width()) { |
| maxWidth = rect.width(); |
| } |
| |
| if (maxHeight < rect.height()) { |
| maxHeight = rect.height(); |
| } |
| } |
| |
| int canvasWidth = (int) (maxWidth * quality); |
| int canvasHeight = (int) (maxHeight * quality); |
| Bitmap output = Bitmap.createBitmap(canvasWidth, canvasHeight, Bitmap.Config.ARGB_4444); |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
| WXLogUtils.d(TAG, "Allocation memory for box-shadow: " + (output.getAllocationByteCount() / 1024) + " KB"); |
| } |
| Canvas canvas = new Canvas(output); |
| |
| if (false && WXEnvironment.isApkDebugable()) { |
| // Using for debug |
| Paint strokePaint = new Paint(); |
| strokePaint.setColor(Color.BLACK); |
| strokePaint.setStrokeWidth(2); |
| strokePaint.setStyle(Paint.Style.STROKE); |
| canvas.drawRect(canvas.getClipBounds(), strokePaint); |
| } |
| |
| for (BoxShadowOptions option : options) { |
| Rect rect = option.getTargetCanvasRect(); |
| float left = (maxWidth - rect.width()) / 2f; |
| float top = (maxHeight - rect.height()) / 2f; |
| option.topLeft = new PointF(left, top); |
| |
| BoxShadowOptions scaledOption = option.scale(quality); |
| drawShadow(canvas, scaledOption); |
| } |
| |
| //Drawable's bounds must match the bitmap size, otherwise the shadows will be scaled |
| int paddingX = (maxWidth - w) / 2; |
| int paddingY = (maxHeight - h) / 2; |
| OverflowBitmapDrawable shadowDrawable = new OverflowBitmapDrawable(target.getResources(), |
| output, new Point(paddingX, paddingY), new Rect(0, 0, w, h), radii); |
| |
| target.getOverlay().add(shadowDrawable); |
| //Relayout to ensure the shadows are fully drawn |
| ViewParent parent = target.getParent(); |
| if (parent != null) { |
| parent.requestLayout(); |
| if (parent instanceof ViewGroup) { |
| ((ViewGroup) parent).invalidate(shadowDrawable.getBounds()); |
| } |
| } |
| } else { |
| // I have a dream that one day our minSdkVersion will equals or higher than 21 |
| Log.w("BoxShadowUtil", "Call setNormalBoxShadow() requires API level 18 or higher."); |
| } |
| } |
| |
| private static void setInsetBoxShadow(View target, List<BoxShadowOptions> options, float quality, float[] radii) { |
| if (target == null || options == null) { |
| WXLogUtils.w(TAG, "Illegal arguments"); |
| return; |
| } |
| |
| if (target.getWidth() == 0 || target.getHeight() == 0) { |
| WXLogUtils.w(TAG, "Target view is invisible, ignore set shadow."); |
| return; |
| } |
| |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { |
| Drawable[] drawables = new Drawable[options.size()]; |
| for (int i = 0; i < options.size(); i++) { |
| BoxShadowOptions option = options.get(i); |
| Drawable shadow = new InsetShadowDrawable(target.getWidth(), target.getHeight(), |
| option.hShadow, option.vShadow, |
| option.blur, option.spread, |
| option.color, radii); |
| drawables[i] = shadow; |
| } |
| |
| LayerDrawable layerDrawable = new LayerDrawable(drawables); |
| target.getOverlay().add(layerDrawable); |
| target.invalidate(); |
| } else { |
| Log.w(TAG, "Call setInsetBoxShadow() requires API level 18 or higher."); |
| } |
| } |
| |
| public static BoxShadowOptions[] parseBoxShadows(String boxShadowStyle, int viewport) { |
| // normalization color expression to #AARRGGBB |
| if (sColorPattern == null) { |
| sColorPattern = Pattern.compile("([rR][gG][bB][aA]?)\\((\\d+\\s*),\\s*(\\d+\\s*),\\s*(\\d+\\s*)(?:,\\s*(\\d+(?:\\.\\d+)?))?\\)"); |
| } |
| |
| Matcher matcher = sColorPattern.matcher(boxShadowStyle); |
| |
| String processedStyle = boxShadowStyle; |
| while (matcher.find()) { |
| String color = matcher.group(); |
| processedStyle = processedStyle.replace(color, "#" + String.format("%8s", Integer.toHexString(WXResourceUtils.getColor(color, Color.BLACK))).replaceAll("\\s", "0")); |
| } |
| |
| String[] styles = processedStyle.split(","); |
| if (styles != null && styles.length > 0) { |
| BoxShadowOptions[] result = new BoxShadowOptions[styles.length]; |
| for (int i = 0; i < styles.length; i++) { |
| result[i] = parseBoxShadow(styles[i], viewport); |
| } |
| return result; |
| } |
| return null; |
| } |
| |
| private static BoxShadowOptions parseBoxShadow(String boxShadow, int viewport) { |
| BoxShadowOptions result = new BoxShadowOptions(viewport); |
| if (TextUtils.isEmpty(boxShadow)) { |
| return null; |
| } |
| |
| String boxShadowCopy = boxShadow; |
| |
| // trim rgb() & rgba() |
| boxShadowCopy = boxShadowCopy.replaceAll("\\s*,\\s+", ","); |
| |
| // match inset first |
| if (boxShadowCopy.contains("inset")) { |
| result.isInset = true; |
| boxShadowCopy = boxShadowCopy.replace("inset", ""); |
| } |
| |
| boxShadowCopy = boxShadowCopy.trim(); |
| List<String> params = new ArrayList<>(Arrays.asList(boxShadowCopy.split("\\s+"))); |
| |
| // match color |
| String maybeColor = params.get(params.size() - 1); |
| if (!TextUtils.isEmpty(maybeColor)) { |
| if (maybeColor.startsWith("#") || maybeColor.startsWith("rgb") || WXResourceUtils.isNamedColor(maybeColor)) { |
| result.color = WXResourceUtils.getColor(maybeColor, Color.BLACK); |
| params.remove(params.size() - 1); |
| } |
| } |
| |
| try { |
| if (params.size() < 2) { |
| // Missing required param |
| return null; |
| } else { |
| if (!TextUtils.isEmpty(params.get(0))) { |
| float px = WXUtils.getFloat(params.get(0).trim(), 0f); |
| result.hShadow = WXViewUtils.getRealSubPxByWidth(px, viewport); |
| } |
| |
| if (!TextUtils.isEmpty(params.get(1))) { |
| float px = WXUtils.getFloat(params.get(1).trim(), 0f); |
| result.vShadow = WXViewUtils.getRealPxByWidth(px, viewport); |
| } |
| |
| for (int i = 2; i < params.size(); i++) { |
| int parserIndex = i - 2; |
| BoxShadowOptions.IParser parser = result.optionParamParsers.get(parserIndex); |
| parser.parse(params.get(i)); |
| } |
| } |
| } catch (Throwable t) { |
| t.printStackTrace(); |
| } |
| return result; |
| } |
| |
| private static class OverflowBitmapDrawable extends BitmapDrawable { |
| private int paddingX; |
| private int paddingY; |
| private Rect viewRect; |
| private float[] radii; |
| |
| private OverflowBitmapDrawable(Resources resources, Bitmap bitmap, Point topLeft, Rect viewRect, float[] radii) { |
| super(resources, bitmap); |
| this.paddingX = topLeft.x; |
| this.paddingY = topLeft.y; |
| this.viewRect = viewRect; |
| this.radii = radii; |
| |
| setBounds(-paddingX, -paddingY, viewRect.width() + paddingX, viewRect.height() + paddingY); |
| } |
| |
| public void draw(Canvas canvas) { |
| Rect bounds = canvas.getClipBounds(); |
| Rect newRect = new Rect(bounds); |
| // Make the Canvas Rect bigger according to the padding. |
| newRect.inset(-paddingX * 2, -paddingY * 2); |
| try { |
| if(WXEnvironment.sApplication.getApplicationInfo().targetSdkVersion > VERSION_CODES.O){ |
| canvas.clipRect(newRect); |
| } |
| else{ |
| canvas.clipRect(newRect, Region.Op.REPLACE); |
| } |
| }catch (NullPointerException e) { |
| canvas.clipRect(newRect); |
| } |
| |
| Path contentPath = new Path(); |
| // the content area map must be aligned with bounds |
| RectF rectF = new RectF(bounds); |
| contentPath.addRoundRect(rectF, radii, Path.Direction.CCW); |
| // can not antialias |
| canvas.clipPath(contentPath, Region.Op.DIFFERENCE); |
| |
| // translate the canvas to a suitable position and then draw the bitmap, otherwise draw from the origin(0, 0) |
| canvas.translate(bounds.left, bounds.top); |
| |
| super.draw(canvas); |
| } |
| } |
| |
| private static class InsetShadowDrawable extends Drawable { |
| private static final int LEFT_TO_RIGHT = 0; |
| private static final int TOP_TO_BOTTOM = 1; |
| private static final int RIGHT_TO_LEFT = 2; |
| private static final int BOTTOM_TO_TOP = 3; |
| |
| private float blurRadius; |
| private int shadowColor; |
| |
| private float[] radii; |
| |
| private float width, height; |
| |
| private float shadowXSize, shadowYSize; |
| |
| private Shader[] shades = new Shader[4]; |
| private Path[] paths = new Path[4]; |
| |
| private Paint paint; |
| |
| private InsetShadowDrawable(int viewWidth, int viewHeight, float dx, float dy, float blurRadius, float spread, int shadowColor, float[] radii) { |
| this.blurRadius = blurRadius; |
| this.shadowColor = shadowColor; |
| |
| this.width = viewWidth + 2 * dx; |
| this.height = viewHeight + 2 * dy; |
| |
| this.shadowXSize = dx + spread; |
| this.shadowYSize = dy + spread; |
| |
| this.radii = radii; |
| |
| setBounds(0, 0, viewWidth, viewHeight); |
| prepare(); |
| } |
| |
| private void prepare() { |
| /* |
| * A +++++++++++++++++++++++++ B |
| * + E ------------------- F + |
| * + | | + |
| * + | | + |
| * + | | + |
| * + H ------------------- G + |
| * D +++++++++++++++++++++++++ C |
| */ |
| |
| PointF a = new PointF(0, 0); |
| PointF b = new PointF(width, 0); |
| PointF c = new PointF(b.x, height); |
| PointF d = new PointF(a.x, c.y); |
| |
| PointF e = new PointF(shadowXSize, shadowYSize); |
| PointF f = new PointF(b.x - shadowXSize, e.y); |
| PointF g = new PointF(f.x, c.y - shadowYSize); |
| PointF h = new PointF(e.x, g.y); |
| |
| Shader ltr = new LinearGradient(e.x - blurRadius, e.y, e.x, e.y, shadowColor, Color.TRANSPARENT, Shader.TileMode.CLAMP); |
| Shader ttb = new LinearGradient(e.x, e.y - blurRadius, e.x, e.y, shadowColor, Color.TRANSPARENT, Shader.TileMode.CLAMP); |
| Shader rtl = new LinearGradient(g.x + blurRadius, g.y, g.x, g.y, shadowColor, Color.TRANSPARENT, Shader.TileMode.CLAMP); |
| Shader btt = new LinearGradient(g.x, g.y + blurRadius, g.x, g.y, shadowColor, Color.TRANSPARENT, Shader.TileMode.CLAMP); |
| |
| shades[LEFT_TO_RIGHT] = ltr; |
| shades[TOP_TO_BOTTOM] = ttb; |
| shades[RIGHT_TO_LEFT] = rtl; |
| shades[BOTTOM_TO_TOP] = btt; |
| |
| Path ltrPath = new Path(); |
| ltrPath.moveTo(a.x, a.y); |
| ltrPath.lineTo(e.x, e.y); |
| ltrPath.lineTo(h.x, h.y); |
| ltrPath.lineTo(d.x, d.y); |
| ltrPath.close(); |
| |
| Path ttbPath = new Path(); |
| ttbPath.moveTo(a.x, a.y); |
| ttbPath.lineTo(b.x, b.y); |
| ttbPath.lineTo(f.x, f.y); |
| ttbPath.lineTo(e.x, e.y); |
| ttbPath.close(); |
| |
| Path rtlPath = new Path(); |
| rtlPath.moveTo(b.x, b.y); |
| rtlPath.lineTo(c.x, c.y); |
| rtlPath.lineTo(g.x, g.y); |
| rtlPath.lineTo(f.x, f.y); |
| rtlPath.close(); |
| |
| Path bttPath = new Path(); |
| bttPath.moveTo(d.x, d.y); |
| bttPath.lineTo(c.x, c.y); |
| bttPath.lineTo(g.x, g.y); |
| bttPath.lineTo(h.x, h.y); |
| bttPath.close(); |
| |
| paths[LEFT_TO_RIGHT] = ltrPath; |
| paths[TOP_TO_BOTTOM] = ttbPath; |
| paths[RIGHT_TO_LEFT] = rtlPath; |
| paths[BOTTOM_TO_TOP] = bttPath; |
| |
| paint = new Paint(); |
| paint.setAntiAlias(true); |
| paint.setStyle(Paint.Style.FILL); |
| paint.setColor(shadowColor); |
| } |
| |
| @Override |
| public void draw(Canvas canvas) { |
| Rect bounds = canvas.getClipBounds(); |
| Path border = new Path(); |
| RectF rectF = new RectF(bounds); |
| border.addRoundRect(rectF, radii, Path.Direction.CCW); |
| canvas.clipPath(border); |
| //TODO: we need clip border-width too |
| |
| // translate the canvas to the right place and then draw the inner shadow |
| canvas.translate(bounds.left, bounds.top); |
| |
| for (int i = 0; i < 4; i++) { |
| Shader shader = shades[i]; |
| Path path = paths[i]; |
| paint.setShader(shader); |
| canvas.drawPath(path, paint); |
| } |
| } |
| |
| @Override |
| public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { |
| |
| } |
| |
| @Override |
| public void setColorFilter(@Nullable ColorFilter colorFilter) { |
| |
| } |
| |
| @Override |
| public int getOpacity() { |
| return PixelFormat.OPAQUE; |
| } |
| } |
| |
| public static class BoxShadowOptions { |
| private List<IParser> optionParamParsers; |
| private int viewport = 750; |
| |
| public float hShadow; |
| public float vShadow; |
| public float blur = 0f; |
| public float spread = 0f; |
| public float[] radii = new float[]{0, 0, 0, 0, 0, 0, 0, 0}; |
| public int color = Color.BLACK; |
| public boolean isInset = false; |
| |
| public int viewWidth = 0; |
| public int viewHeight = 0; |
| public PointF topLeft = null; |
| |
| private BoxShadowOptions(int vp) { |
| if (viewport != 0) { |
| this.viewport = vp; |
| } |
| optionParamParsers = new ArrayList<>(); |
| |
| IParser spreadParser = new IParser() { |
| @Override |
| public void parse(String param) { |
| if (!TextUtils.isEmpty(param)) { |
| float px = WXUtils.getFloat(param, 0f); |
| spread = WXViewUtils.getRealSubPxByWidth(px, viewport); |
| WXLogUtils.w(TAG, "Experimental box-shadow attribute: spread"); |
| } |
| } |
| }; |
| |
| IParser blurParser = new IParser() { |
| @Override |
| public void parse(String param) { |
| if (!TextUtils.isEmpty(param)) { |
| float px = WXUtils.getFloat(param, 0f); |
| blur = WXViewUtils.getRealSubPxByWidth(px, viewport); |
| } |
| } |
| }; |
| |
| optionParamParsers.add(blurParser); |
| optionParamParsers.add(spreadParser); |
| } |
| |
| public BoxShadowOptions scale(float scale) { |
| if (scale > 0f && scale <= 1f) { |
| BoxShadowOptions scaledOptions = new BoxShadowOptions(viewport); |
| scaledOptions.hShadow = hShadow * scale; |
| scaledOptions.vShadow = vShadow * scale; |
| scaledOptions.blur = blur * scale; |
| scaledOptions.spread = spread * scale; |
| for (int i = 0; i < radii.length ; i++) { |
| scaledOptions.radii[i] = radii[i] * scale; |
| } |
| scaledOptions.viewHeight = (int) (viewHeight * scale); |
| scaledOptions.viewWidth = (int) (viewWidth * scale); |
| |
| if (topLeft != null) { |
| scaledOptions.topLeft = new PointF(); |
| scaledOptions.topLeft.x = topLeft.x * scale; |
| scaledOptions.topLeft.y = topLeft.y * scale; |
| } |
| |
| scaledOptions.color = color; |
| scaledOptions.isInset = isInset; |
| WXLogUtils.d(TAG, "Scaled BoxShadowOptions: [" + scale + "] " + scaledOptions); |
| return scaledOptions; |
| } |
| return null; |
| } |
| |
| public Rect getTargetCanvasRect() { |
| int canvasWidth = viewWidth + 2 * (int) (blur + spread + Math.abs(hShadow)); |
| int canvasHeight = viewHeight + 2 * (int) (blur + spread + Math.abs(vShadow)); |
| return new Rect(0, 0, canvasWidth, canvasHeight); |
| } |
| |
| @Override |
| public String toString() { |
| |
| String r = "[" + radii[0] + "," + radii[2] + "," + radii[4] + "," + radii[6] + "]"; |
| |
| final StringBuffer sb = new StringBuffer("BoxShadowOptions{"); |
| sb.append("h-shadow=").append(hShadow); |
| sb.append(", v-shadow=").append(vShadow); |
| sb.append(", blur=").append(blur); |
| sb.append(", spread=").append(spread); |
| sb.append(", corner-radius=").append(r); |
| sb.append(", color=#").append(Integer.toHexString(color)); |
| sb.append(", inset=").append(isInset); |
| sb.append('}'); |
| return sb.toString(); |
| } |
| |
| private interface IParser { |
| void parse(String param); |
| } |
| } |
| } |