Repository: incubator-weex Updated Branches: refs/heads/master 76c9140c4 -> 7f79916c8
* [android] support multi box-shadow close #915 * [android] support multi box-shadow update android border image Project: http://git-wip-us.apache.org/repos/asf/incubator-weex/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-weex/commit/7f79916c Tree: http://git-wip-us.apache.org/repos/asf/incubator-weex/tree/7f79916c Diff: http://git-wip-us.apache.org/repos/asf/incubator-weex/diff/7f79916c Branch: refs/heads/master Commit: 7f79916c8d7541a5df2c509acf494027f14f7f66 Parents: 76c9140 Author: misakuo <misa...@apache.org> Authored: Mon Nov 13 11:37:08 2017 +0800 Committer: acton393 <zhangxing610...@gmail.com> Committed: Tue Nov 28 11:52:44 2017 +0800 ---------------------------------------------------------------------- .../com/taobao/weex/utils/BoxShadowUtil.java | 252 +++++++++++++------ test/screenshot/border-android.png | Bin 160024 -> 159136 bytes 2 files changed, 172 insertions(+), 80 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/7f79916c/android/sdk/src/main/java/com/taobao/weex/utils/BoxShadowUtil.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/utils/BoxShadowUtil.java b/android/sdk/src/main/java/com/taobao/weex/utils/BoxShadowUtil.java index 229e30d..bc7036d 100644 --- a/android/sdk/src/main/java/com/taobao/weex/utils/BoxShadowUtil.java +++ b/android/sdk/src/main/java/com/taobao/weex/utils/BoxShadowUtil.java @@ -28,6 +28,7 @@ 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; @@ -35,6 +36,7 @@ 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.support.annotation.IntRange; import android.support.annotation.Nullable; @@ -49,6 +51,8 @@ import com.taobao.weex.WXEnvironment; 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. @@ -62,6 +66,8 @@ public class BoxShadowUtil { private static final String TAG = "BoxShadowUtil"; private static boolean sBoxShadowEnabled = true; + private static Pattern sColorPattern; + public static void setBoxShadowEnabled(boolean enabled) { sBoxShadowEnabled = enabled; WXLogUtils.w(TAG, "Switch box-shadow status: " + enabled); @@ -71,29 +77,40 @@ public class BoxShadowUtil { return sBoxShadowEnabled; } - public static void setBoxShadow(final View target, String style, float[] radii, int viewPort, final float quality) { + 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; } - final BoxShadowOptions options = parseBoxShadow(style, viewPort); - if (options == null) { - WXLogUtils.w(TAG, "Failed to parse box-shadow: " + style); - return; - } - if (target == null) { WXLogUtils.w(TAG, "Target view is null!"); return; } - if (options.isClear && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + if (TextUtils.isEmpty(style) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { target.getOverlay().clear(); - WXLogUtils.d(TAG, "Remove box-shadow"); + 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"); @@ -102,86 +119,78 @@ public class BoxShadowUtil { float realRadius = WXViewUtils.getRealSubPxByWidth(radii[i], viewPort); radii[i] = realRadius; } - options.radii = radii; } } - WXLogUtils.d(TAG, "Set box-shadow: " + options.toString()); - target.post(new Runnable() { @Override public void run() { - if (options.isInset) { - setInsetBoxShadow(target, options, quality); - } else { - setNormalBoxShadow(target, options, quality); + 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 Bitmap createShadowBitmap(int viewWidth, int viewHeight, - float[] radii, float shadowRadius, - float shadowSpread, - float dx, float dy, int shadowColor) { - int canvasWidth = viewWidth + 2 * (int) (shadowRadius + shadowSpread + Math.abs(dx)); - int canvasHeight = viewHeight + 2 * (int) (shadowRadius + shadowSpread + Math.abs(dy)); - - 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); - } - + private static void drawShadow(Canvas canvas, BoxShadowOptions options) { RectF shadowRect = new RectF( 0f, 0f, - viewWidth + 2f * shadowSpread, viewHeight + 2f * shadowSpread + options.viewWidth + 2f * options.spread, options.viewHeight + 2f * options.spread ); - float shadowDx = shadowRadius; - float shadowDy = shadowRadius; - if (dx > 0) { - shadowDx = shadowDx + 2f * dx; + 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 (dy > 0) { - shadowDy = shadowDy + 2f * dy; + if (options.vShadow > 0) { + shadowDy = shadowDy + 2f * options.vShadow; } shadowRect.offset(shadowDx, shadowDy); Paint shadowPaint = new Paint(); shadowPaint.setAntiAlias(true); - shadowPaint.setColor(shadowColor); + shadowPaint.setColor(options.color); shadowPaint.setStyle(Paint.Style.FILL); - if (shadowRadius > 0) { - shadowPaint.setMaskFilter(new BlurMaskFilter(shadowRadius, BlurMaskFilter.Blur.NORMAL)); + 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 < radii.length; i++) { - float contentRadius = radii[i]; + for (int i = 0; i < options.radii.length; i++) { + float contentRadius = options.radii[i]; if (contentRadius == 0f) { shadowRadii[i] = 0f; } else { - shadowRadii[i] = radii[i] + shadowSpread; + shadowRadii[i] = options.radii[i] + options.spread; } } shadowPath.addRoundRect(shadowRect, shadowRadii, Path.Direction.CCW); canvas.drawPath(shadowPath, shadowPaint); - return output; + + 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, BoxShadowOptions options, float quality) { + private static void setNormalBoxShadow(View target, List<BoxShadowOptions> options, float quality, float[] radii) { int h = target.getHeight(); int w = target.getWidth(); @@ -191,16 +200,55 @@ public class BoxShadowUtil { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - options.viewWidth = w; - options.viewHeight = h; + 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 scaleOptions = options.scale(quality); - Bitmap shadowBitmap = createShadowBitmap(scaleOptions.viewWidth, scaleOptions.viewHeight, scaleOptions.radii, scaleOptions.blur, scaleOptions.spread, scaleOptions.hShadow, scaleOptions.vShadow, scaleOptions.color); + BoxShadowOptions scaledOption = option.scale(quality); + drawShadow(canvas, scaledOption); + } //Drawable's bounds must match the bitmap size, otherwise the shadows will be scaled - OverflowBitmapDrawable shadowDrawable = new OverflowBitmapDrawable(target.getResources(), shadowBitmap, options); + 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().clear(); target.getOverlay().add(shadowDrawable); //Relayout to ensure the shadows are fully drawn ViewParent parent = target.getParent(); @@ -216,7 +264,7 @@ public class BoxShadowUtil { } } - private static void setInsetBoxShadow(View target, BoxShadowOptions options, float quality) { + private static void setInsetBoxShadow(View target, List<BoxShadowOptions> options, float quality, float[] radii) { if (target == null || options == null) { WXLogUtils.w(TAG, "Illegal arguments"); return; @@ -228,23 +276,53 @@ public class BoxShadowUtil { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - Drawable shadow = new InsetShadowDrawable(target.getWidth(), target.getHeight(), - options.hShadow, options.vShadow, - options.blur, options.spread, - options.color, options.radii); - target.getOverlay().clear(); - target.getOverlay().add(shadow); + 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 parseBoxShadow(String boxShadow, int viewport) { + 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, "#" + Integer.toHexString(WXResourceUtils.getColor(color, Color.BLACK))); + } + + 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)) { - result.isClear = true; - return result; + return null; } String boxShadowCopy = boxShadow; @@ -255,9 +333,10 @@ public class BoxShadowUtil { // match inset first if (boxShadowCopy.contains("inset")) { result.isInset = true; - boxShadowCopy = boxShadowCopy.replace("inset", "").trim(); + boxShadowCopy = boxShadowCopy.replace("inset", ""); } + boxShadowCopy = boxShadowCopy.trim(); List<String> params = new ArrayList<>(Arrays.asList(boxShadowCopy.split("\\s+"))); // match color @@ -299,15 +378,17 @@ public class BoxShadowUtil { private static class OverflowBitmapDrawable extends BitmapDrawable { private int paddingX; private int paddingY; - private BoxShadowOptions options; + private Rect viewRect; + private float[] radii; - private OverflowBitmapDrawable(Resources resources, Bitmap bitmap, BoxShadowOptions options) { + private OverflowBitmapDrawable(Resources resources, Bitmap bitmap, Point topLeft, Rect viewRect, float[] radii) { super(resources, bitmap); - this.paddingX = (int) (options.blur + Math.abs(options.hShadow) + options.spread); - this.paddingY = (int) (options.blur + Math.abs(options.vShadow) + options.spread); - this.options = options; + this.paddingX = topLeft.x; + this.paddingY = topLeft.y; + this.viewRect = viewRect; + this.radii = radii; - setBounds(-paddingX, -paddingY, options.viewWidth + paddingX, options.viewHeight + paddingY); + setBounds(-paddingX, -paddingY, viewRect.width() + paddingX, viewRect.height() + paddingY); } @Override @@ -318,8 +399,8 @@ public class BoxShadowUtil { canvas.clipRect(newRect, Region.Op.REPLACE); Path contentPath = new Path(); - RectF rectF = new RectF(0f, 0f, options.viewWidth, options.viewHeight); - contentPath.addRoundRect(rectF, options.radii, Path.Direction.CCW); + RectF rectF = new RectF(0f, 0f, viewRect.width(), viewRect.height()); + contentPath.addRoundRect(rectF, radii, Path.Direction.CCW); // can not antialias canvas.clipPath(contentPath, Region.Op.DIFFERENCE); @@ -477,9 +558,9 @@ public class BoxShadowUtil { public int color = Color.BLACK; public boolean isInset = false; - public boolean isClear = false; public int viewWidth = 0; public int viewHeight = 0; + public PointF topLeft = null; private BoxShadowOptions(int vp) { if (viewport != 0) { @@ -525,15 +606,26 @@ public class BoxShadowUtil { 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; - scaledOptions.isClear = isClear; 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() { http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/7f79916c/test/screenshot/border-android.png ---------------------------------------------------------------------- diff --git a/test/screenshot/border-android.png b/test/screenshot/border-android.png index b2bcd5d..4f41f3a 100644 Binary files a/test/screenshot/border-android.png and b/test/screenshot/border-android.png differ