diff --git a/packages/core/platforms/android/widgets-release.aar b/packages/core/platforms/android/widgets-release.aar index 2877a2a4c..137060cee 100644 Binary files a/packages/core/platforms/android/widgets-release.aar and b/packages/core/platforms/android/widgets-release.aar differ diff --git a/packages/core/ui/layouts/layout-base.android.ts b/packages/core/ui/layouts/layout-base.android.ts index 7edfcd929..9fa969fe8 100644 --- a/packages/core/ui/layouts/layout-base.android.ts +++ b/packages/core/ui/layouts/layout-base.android.ts @@ -9,8 +9,18 @@ export class LayoutBase extends LayoutBaseCommon { return true; } [clipToBoundsProperty.setNative](value: boolean) { - (this.nativeViewProtected).setClipToBounds(value); + // TODO: Use ClipRectangle if API > 16! + // We can't implement this without calling setClipChildren(false) on every ancestor up in the visual tree, + // which will kill performance. It will also lead to unwanted side effects such as other totally unrelated + // views being affected by setting the parents' setClipChildren to false. + // The problem in Android is that a ViewGroup either clips ALL of its children or it does not. Unlike iOS, the clipping + // cannot be controlled on a per view basis. So clipToBounds=false will have to be somehow achieved with stacking different + // views on top of one another in an AbsoluteLayout or GridLayout. There is always a workaround when playing with layouts. + // + // The following article explains this in detail: + // http://stackoverflow.com/questions/25044085/when-drawing-outside-the-view-clip-bounds-with-android-how-do-i-prevent-underli + console.warn(`clipToBounds with value false is not supported on Android. You can use this.android.getParent().setClipChildren(false) as an alternative`); } [isPassThroughParentEnabledProperty.setNative](value: boolean) { diff --git a/packages/ui-mobile-base/android/widgets/build.gradle b/packages/ui-mobile-base/android/widgets/build.gradle index b01d50b23..f81817404 100644 --- a/packages/ui-mobile-base/android/widgets/build.gradle +++ b/packages/ui-mobile-base/android/widgets/build.gradle @@ -51,12 +51,7 @@ def computeTargetSdkVersion() { } } - android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } compileSdkVersion computeCompileSdkVersion() buildToolsVersion computeBuildToolsVersion() diff --git a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java index d511c4521..2f7853a5a 100644 --- a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java +++ b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java @@ -18,20 +18,13 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.Shader; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import org.nativescript.widgets.image.BitmapOwner; import org.nativescript.widgets.image.Fetcher; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; import java.util.Locale; import java.util.regex.Pattern; -import android.os.Build; -import android.util.Log; - /** * Created by hristov on 6/15/2016. */ @@ -56,20 +49,6 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { private String clipPath; - private Path clipPathPath = null; - private Path backgroundPath = null; - private Path backgroundOutlinePath = null; - private Path unifiedColorBorderPath = null; - private Path innerBorderPath = null; - private Path topBorderPath = null; - private Path rightBorderPath = null; - private Path bottomBorderPath = null; - private Path leftBorderPath = null; - private Path clippingPath = null; - private Rect lastBounds = null; - - Paint borderPaint = null; - private int backgroundColor; private String backgroundImage; private Bitmap backgroundBitmap; @@ -162,65 +141,6 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { return clipPath; } - public Path getClipPathPath() { - return clipPathPath; - } - - @RequiresApi(api = Build.VERSION_CODES.KITKAT) - public Path getClippingPath() { - Path toClip = null; - if (this.clipPath != null) { - generateClipPath(this.clipPath, new RectF(getBounds()), density); - toClip = this.clipPathPath; - } else if (hasBorderWidth()) { - generateInnerBorderPath(getBounds()); - toClip = this.innerBorderPath; - } else if (android.os.Build.VERSION.SDK_INT < 21 || !this.hasUniformBorder()) { - generateBackgroundOutlinePath(getBounds()); - toClip = this.backgroundOutlinePath; - } - if (toClip != null) { - if (clippingPath != null && hasCache("clippingPath")) { - return clippingPath; - } - setHasCache("clippingPath"); - - if (clippingPath == null) { - clippingPath = new Path(); - } else { - clippingPath.reset(); - } - clippingPath.addRect(new RectF(lastBounds), Path.Direction.CW); - clippingPath.op(toClip, Path.Op.DIFFERENCE); - return clippingPath; - } - // if uniform borders and no border width and >= 21 then the oultine can clip - // no need to return a clippingPath - return null; - } - - HashSet cacheKeys = new HashSet(); - - private boolean hasCache(String key) { - return (cacheKeys.contains(key)); - } - - private void setHasCache(String key) { - cacheKeys.add(key); - } - - protected void onBoundsChange(Rect bounds) { - if (lastBounds != null && lastBounds.equals(bounds)) { - return; - } - - if (lastBounds == null) { - lastBounds = new Rect(); - } - lastBounds.set(bounds); - cacheKeys.clear(); - } - public int getBackgroundColor() { return backgroundColor; } @@ -233,9 +153,7 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { return backgroundBitmap; } - public LinearGradientDefinition getBackgroundGradient() { - return backgroundGradient; - } + public LinearGradientDefinition getBackgroundGradient() { return backgroundGradient; } public String getBackgroundRepeat() { return backgroundRepeat; @@ -250,33 +168,34 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { } public boolean hasBorderWidth() { - return this.borderTopWidth != 0 || this.borderRightWidth != 0 || this.borderBottomWidth != 0 - || this.borderLeftWidth != 0; + return this.borderTopWidth != 0 + || this.borderRightWidth != 0 + || this.borderBottomWidth != 0 + || this.borderLeftWidth != 0; } public boolean hasUniformBorderColor() { - return this.borderTopColor == this.borderRightColor && this.borderTopColor == this.borderBottomColor - && this.borderTopColor == this.borderLeftColor; + return this.borderTopColor == this.borderRightColor && + this.borderTopColor == this.borderBottomColor && + this.borderTopColor == this.borderLeftColor; } public boolean hasUniformBorderWidth() { - return this.borderTopWidth == this.borderRightWidth && this.borderTopWidth == this.borderBottomWidth - && this.borderTopWidth == this.borderLeftWidth; + return this.borderTopWidth == this.borderRightWidth && + this.borderTopWidth == this.borderBottomWidth && + this.borderTopWidth == this.borderLeftWidth; } public boolean hasUniformBorderRadius() { - return this.borderTopLeftRadius == this.borderTopRightRadius - && this.borderTopLeftRadius == this.borderBottomRightRadius - && this.borderTopLeftRadius == this.borderBottomLeftRadius; - } - - public boolean hasBorderRadius() { - return borderBottomLeftRadius > 0 || borderTopLeftRadius > 0 || borderBottomRightRadius > 0 - || borderTopRightRadius > 0; + return this.borderTopLeftRadius == this.borderTopRightRadius && + this.borderTopLeftRadius == this.borderBottomRightRadius && + this.borderTopLeftRadius == this.borderBottomLeftRadius; } public boolean hasUniformBorder() { - return this.hasUniformBorderColor() && this.hasUniformBorderWidth() && this.hasUniformBorderRadius(); + return this.hasUniformBorderColor() && + this.hasUniformBorderWidth() && + this.hasUniformBorderRadius(); } public BorderDrawable(float density) { @@ -290,19 +209,33 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { this.id = id; } - public void refresh(int borderTopColor, int borderRightColor, int borderBottomColor, int borderLeftColor, + public void refresh(int borderTopColor, + int borderRightColor, + int borderBottomColor, + int borderLeftColor, - float borderTopWidth, float borderRightWidth, float borderBottomWidth, float borderLeftWidth, + float borderTopWidth, + float borderRightWidth, + float borderBottomWidth, + float borderLeftWidth, - float borderTopLeftRadius, float borderTopRightRadius, float borderBottomRightRadius, - float borderBottomLeftRadius, + float borderTopLeftRadius, + float borderTopRightRadius, + float borderBottomRightRadius, + float borderBottomLeftRadius, - String clipPath, + String clipPath, - int backgroundColor, String backgroundImageUri, Bitmap backgroundBitmap, - LinearGradientDefinition backgroundGradient, Context context, String backgroundRepeat, - String backgroundPosition, CSSValue[] backgroundPositionParsedCSSValues, String backgroundSize, - CSSValue[] backgroundSizeParsedCSSValues) { + int backgroundColor, + String backgroundImageUri, + Bitmap backgroundBitmap, + LinearGradientDefinition backgroundGradient, + Context context, + String backgroundRepeat, + String backgroundPosition, + CSSValue[] backgroundPositionParsedCSSValues, + String backgroundSize, + CSSValue[] backgroundSizeParsedCSSValues) { this.borderTopColor = borderTopColor; this.borderRightColor = borderRightColor; @@ -321,9 +254,6 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { this.clipPath = clipPath; - // clear all cached paths - cacheKeys.clear(); - this.backgroundColor = backgroundColor; this.backgroundImage = backgroundImageUri; this.backgroundBitmap = backgroundBitmap; @@ -343,67 +273,39 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { } } - private void generateBackgroundPath(Rect bounds) { + @Override + public void draw(Canvas canvas) { + Rect bounds = this.getBounds(); + float width = (float)bounds.width(); + float height = (float)bounds.height(); - if (backgroundPath != null && hasCache("backgroundPath")) { + if (width <= 0 || height <= 0) { + // When the view is off-screen the bounds might be empty and we don't have anything to draw. return; } - setHasCache("backgroundPath"); - if (backgroundPath == null) { - backgroundPath = new Path(); - } else { - backgroundPath.reset(); - } - float[] backgroundRadii = { Math.max(0, borderTopLeftRadius), Math.max(0, borderTopLeftRadius), - Math.max(0, borderTopRightRadius), Math.max(0, borderTopRightRadius), - Math.max(0, borderBottomRightRadius), Math.max(0, borderBottomRightRadius), - Math.max(0, borderBottomLeftRadius), Math.max(0, borderBottomLeftRadius) }; + RectF backgroundBoundsF = new RectF(bounds.left, bounds.top, bounds.right, bounds.bottom); + float topBackoffAntialias = calculateBackoffAntialias(this.borderTopColor, this.borderTopWidth); float rightBackoffAntialias = calculateBackoffAntialias(this.borderRightColor, this.borderRightWidth); float bottomBackoffAntialias = calculateBackoffAntialias(this.borderBottomColor, this.borderBottomWidth); float leftBackoffAntialias = calculateBackoffAntialias(this.borderLeftColor, this.borderLeftWidth); - float width = (float) bounds.width(); - float height = (float) bounds.height(); + float[] backgroundRadii = { + Math.max(0, borderTopLeftRadius + leftBackoffAntialias), Math.max(0, borderTopLeftRadius + topBackoffAntialias), + Math.max(0, borderTopRightRadius + rightBackoffAntialias), Math.max(0, borderTopRightRadius + topBackoffAntialias), + Math.max(0, borderBottomRightRadius + rightBackoffAntialias), Math.max(0, borderBottomRightRadius + bottomBackoffAntialias), + Math.max(0, borderBottomLeftRadius + leftBackoffAntialias), Math.max(0, borderBottomLeftRadius + bottomBackoffAntialias) + }; - RectF backgroundRect = new RectF(leftBackoffAntialias, topBackoffAntialias, width - rightBackoffAntialias, - height - bottomBackoffAntialias); + Path backgroundPath = new Path(); + RectF backgroundRect = new RectF( + leftBackoffAntialias, + topBackoffAntialias, + width - rightBackoffAntialias, + height - bottomBackoffAntialias + ); backgroundPath.addRoundRect(backgroundRect, backgroundRadii, Path.Direction.CW); - } - - private void generateUniformedColorBorderPath(Rect bounds) { - if (unifiedColorBorderPath != null && hasCache("unifiedColorBorderPath")) { - return; - } - setHasCache("unifiedColorBorderPath"); - - if (unifiedColorBorderPath == null) { - unifiedColorBorderPath = new Path(); - } else { - unifiedColorBorderPath.reset(); - } - // the path used for outer border is the same as outline - generateBackgroundOutlinePath(bounds); - unifiedColorBorderPath.addPath(backgroundOutlinePath); - - generateInnerBorderPath(bounds); - unifiedColorBorderPath.addPath(innerBorderPath); - } - - @Override - public void draw(Canvas canvas) { - Rect bounds = this.getBounds(); - float width = (float) bounds.width(); - float height = (float) bounds.height(); - - if (width <= 0 || height <= 0) { - // When the view is off-screen the bounds might be empty and we don't have - // anything to draw. - return; - } - - RectF backgroundBoundsF = new RectF(lastBounds); // draw background if (this.backgroundColor != 0) { @@ -415,7 +317,6 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { if (this.clipPath != null && !this.clipPath.isEmpty()) { drawClipPath(this.clipPath, canvas, backgroundColorPaint, backgroundBoundsF, this.density); } else { - generateBackgroundPath(bounds); canvas.drawPath(backgroundPath, backgroundColorPaint); } } @@ -434,9 +335,11 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { transform.postTranslate(params.posX, params.posY); Paint backgroundImagePaint = new Paint(); - BitmapShader shader = new BitmapShader(this.backgroundBitmap, - params.repeatX ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP, - params.repeatY ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP); + BitmapShader shader = new BitmapShader( + this.backgroundBitmap, + params.repeatX ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP, + params.repeatY ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP + ); shader.setLocalMatrix(transform); backgroundImagePaint.setAntiAlias(true); backgroundImagePaint.setFilterBitmap(true); @@ -451,16 +354,13 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { drawClipPath(this.clipPath, canvas, backgroundImagePaint, backgroundBoundsF, this.density); } else { boolean supportsPathOp = android.os.Build.VERSION.SDK_INT >= 19; - generateBackgroundPath(bounds); if (supportsPathOp) { Path backgroundNoRepeatPath = new Path(); - backgroundNoRepeatPath.addRect(params.posX, params.posY, params.posX + imageWidth, - params.posY + imageHeight, Path.Direction.CCW); + backgroundNoRepeatPath.addRect(params.posX, params.posY, params.posX + imageWidth, params.posY + imageHeight, Path.Direction.CCW); intersect(backgroundNoRepeatPath, backgroundPath); canvas.drawPath(backgroundNoRepeatPath, backgroundImagePaint); } else { - // Clipping here will not be anti-aliased but at least it won't shine through - // the rounded corners. + // Clipping here will not be anti-aliased but at least it won't shine through the rounded corners. canvas.save(); canvas.clipRect(params.posX, params.posY, params.posX + imageWidth, params.posY + imageHeight); canvas.drawPath(backgroundPath, backgroundImagePaint); @@ -472,9 +372,10 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { if (this.backgroundGradient != null) { LinearGradientDefinition def = this.backgroundGradient; Paint backgroundGradientPaint = new Paint(); - LinearGradient shader = new LinearGradient(def.getStartX() * width, def.getStartY() * height, - def.getEndX() * width, def.getEndY() * height, def.getColors(), def.getStops(), - Shader.TileMode.MIRROR); + LinearGradient shader = new LinearGradient( + def.getStartX() * width, def.getStartY() * height, + def.getEndX() * width, def.getEndY() * height, + def.getColors(), def.getStops(), Shader.TileMode.MIRROR); backgroundGradientPaint.setAntiAlias(true); backgroundGradientPaint.setFilterBitmap(true); backgroundGradientPaint.setShader(shader); @@ -482,7 +383,6 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { if (this.clipPath != null && !this.clipPath.isEmpty()) { drawClipPath(this.clipPath, canvas, backgroundGradientPaint, backgroundBoundsF, this.density); } else { - generateBackgroundPath(bounds); canvas.drawPath(backgroundPath, backgroundGradientPaint); } } @@ -491,10 +391,7 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { if (this.clipPath != null && !this.clipPath.isEmpty()) { float borderWidth = this.getUniformBorderWidth(); if (borderWidth > 0) { - if (borderPaint == null) { - borderPaint = new Paint(); - borderPaint.setAntiAlias(true); - } + Paint borderPaint = new Paint(); borderPaint.setColor(this.getUniformBorderColor()); borderPaint.setStyle(Paint.Style.STROKE); borderPaint.setStrokeWidth(borderWidth); @@ -505,15 +402,36 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { } else if (this.hasUniformBorderColor()) { // iOS and browsers use black when no color is specified. if (borderLeftWidth > 0 || borderTopWidth > 0 || borderRightWidth > 0 || borderBottomWidth > 0) { - if (borderPaint == null) { - borderPaint = new Paint(); - borderPaint.setAntiAlias(true); - } - borderPaint.setStyle(Paint.Style.FILL); + Paint borderPaint = new Paint(); borderPaint.setColor(this.getUniformBorderColor()); + borderPaint.setStyle(Paint.Style.FILL); + borderPaint.setAntiAlias(true); + Path borderPath = new Path(); - generateUniformedColorBorderPath(bounds); - canvas.drawPath(unifiedColorBorderPath, borderPaint); + RectF borderOuterRect = new RectF(0, 0, width, height); + float[] borderOuterRadii = { + borderTopLeftRadius, borderTopLeftRadius, + borderTopRightRadius, borderTopRightRadius, + borderBottomRightRadius, borderBottomRightRadius, + borderBottomLeftRadius, borderBottomLeftRadius + }; + borderPath.addRoundRect(borderOuterRect, borderOuterRadii, Path.Direction.CW); + + RectF borderInnerRect = new RectF( + borderLeftWidth, + borderTopWidth, + width - borderRightWidth, + height - borderBottomWidth + ); + float[] borderInnerRadii = { + Math.max(0, borderTopLeftRadius - borderLeftWidth), Math.max(0, borderTopLeftRadius - borderTopWidth), + Math.max(0, borderTopRightRadius - borderRightWidth), Math.max(0, borderTopRightRadius - borderTopWidth), + Math.max(0, borderBottomRightRadius - borderRightWidth), Math.max(0, borderBottomRightRadius - borderBottomWidth), + Math.max(0, borderBottomLeftRadius - borderLeftWidth), Math.max(0, borderBottomLeftRadius - borderBottomWidth) + }; + borderPath.addRoundRect(borderInnerRect, borderInnerRadii, Path.Direction.CCW); + + canvas.drawPath(borderPath, borderPaint); } } else { float top = this.borderTopWidth; @@ -521,16 +439,16 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { float bottom = this.borderBottomWidth; float left = this.borderLeftWidth; - // lto rto - // +---------------------+ - // |lti rti| - // | | - // | | - // | | - // | | - // |lbi rbi| - // +---------------------+ - // lbo rbo + //lto rto + // +---------------------+ + // |lti rti| + // | | + // | | + // | | + // | | + // |lbi rbi| + // +---------------------+ + //lbo rbo PointF lto = new PointF(0, 0); // left-top-outside PointF lti = new PointF(left, top); // left-top-inside @@ -545,109 +463,66 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { PointF lbi = new PointF(left, bounds.bottom - bottom); // left-bottom-inside if (this.borderTopWidth > 0) { - - if (topBorderPath == null || !hasCache("topBorderPath")) { - setHasCache("topBorderPath"); - if (topBorderPath == null) { - topBorderPath = new Path(); - } else { - topBorderPath.reset(); - } - topBorderPath.setFillType(Path.FillType.EVEN_ODD); - topBorderPath.moveTo(lto.x, lto.y); - topBorderPath.lineTo(rto.x, rto.y); - topBorderPath.lineTo(rti.x, rti.y); - topBorderPath.lineTo(lti.x, lti.y); - topBorderPath.close(); - } - if (borderPaint == null) { - borderPaint = new Paint(); - borderPaint.setAntiAlias(true); - } - borderPaint.setColor(this.borderTopColor); - canvas.drawPath(topBorderPath, borderPaint); + Paint topBorderPaint = new Paint(); + topBorderPaint.setColor(this.borderTopColor); + topBorderPaint.setAntiAlias(true); + Path topBorderPath = new Path(); + topBorderPath.setFillType(Path.FillType.EVEN_ODD); + topBorderPath.moveTo(lto.x, lto.y); + topBorderPath.lineTo(rto.x, rto.y); + topBorderPath.lineTo(rti.x, rti.y); + topBorderPath.lineTo(lti.x, lti.y); + topBorderPath.close(); + canvas.drawPath(topBorderPath, topBorderPaint); } if (this.borderRightWidth > 0) { - if (rightBorderPath == null || !hasCache("rightBorderPath")) { - setHasCache("rightBorderPath"); - if (rightBorderPath == null) { - rightBorderPath = new Path(); - } else { - rightBorderPath.reset(); - } - rightBorderPath.setFillType(Path.FillType.EVEN_ODD); - rightBorderPath.moveTo(rto.x, rto.y); - rightBorderPath.lineTo(rbo.x, rbo.y); - rightBorderPath.lineTo(rbi.x, rbi.y); - rightBorderPath.lineTo(rti.x, rti.y); - rightBorderPath.close(); - } - if (borderPaint == null) { - borderPaint = new Paint(); - borderPaint.setAntiAlias(true); - } - - borderPaint.setColor(this.borderRightColor); - canvas.drawPath(rightBorderPath, borderPaint); + Paint rightBorderPaint = new Paint(); + rightBorderPaint.setColor(this.borderRightColor); + rightBorderPaint.setAntiAlias(true); + Path rightBorderPath = new Path(); + rightBorderPath.setFillType(Path.FillType.EVEN_ODD); + rightBorderPath.moveTo(rto.x, rto.y); + rightBorderPath.lineTo(rbo.x, rbo.y); + rightBorderPath.lineTo(rbi.x, rbi.y); + rightBorderPath.lineTo(rti.x, rti.y); + rightBorderPath.close(); + canvas.drawPath(rightBorderPath, rightBorderPaint); } if (this.borderBottomWidth > 0) { - if (bottomBorderPath == null || !hasCache("bottomBorderPath")) { - setHasCache("bottomBorderPath"); - if (bottomBorderPath == null) { - bottomBorderPath = new Path(); - } else { - bottomBorderPath.reset(); - } - bottomBorderPath.setFillType(Path.FillType.EVEN_ODD); - bottomBorderPath.moveTo(rbo.x, rbo.y); - bottomBorderPath.lineTo(lbo.x, lbo.y); - bottomBorderPath.lineTo(lbi.x, lbi.y); - bottomBorderPath.lineTo(rbi.x, rbi.y); - bottomBorderPath.close(); - } - if (borderPaint == null) { - borderPaint = new Paint(); - borderPaint.setAntiAlias(true); - } - - borderPaint.setColor(this.borderBottomColor); - canvas.drawPath(bottomBorderPath, borderPaint); + Paint bottomBorderPaint = new Paint(); + bottomBorderPaint.setColor(this.borderBottomColor); + bottomBorderPaint.setAntiAlias(true); + Path bottomBorderPath = new Path(); + bottomBorderPath.setFillType(Path.FillType.EVEN_ODD); + bottomBorderPath.moveTo(rbo.x, rbo.y); + bottomBorderPath.lineTo(lbo.x, lbo.y); + bottomBorderPath.lineTo(lbi.x, lbi.y); + bottomBorderPath.lineTo(rbi.x, rbi.y); + bottomBorderPath.close(); + canvas.drawPath(bottomBorderPath, bottomBorderPaint); } if (this.borderLeftWidth > 0) { - - if (leftBorderPath == null || !hasCache("leftBorderPath")) { - setHasCache("leftBorderPath"); - if (leftBorderPath == null) { - leftBorderPath = new Path(); - } else { - leftBorderPath.reset(); - } - leftBorderPath.setFillType(Path.FillType.EVEN_ODD); - leftBorderPath.moveTo(lbo.x, lbo.y); - leftBorderPath.lineTo(lto.x, lto.y); - leftBorderPath.lineTo(lti.x, lti.y); - leftBorderPath.lineTo(lbi.x, lbi.y); - leftBorderPath.close(); - } - if (borderPaint == null) { - borderPaint = new Paint(); - borderPaint.setAntiAlias(true); - } - - borderPaint.setColor(this.borderLeftColor); - canvas.drawPath(leftBorderPath, borderPaint); + Paint leftBorderPaint = new Paint(); + leftBorderPaint.setColor(this.borderLeftColor); + leftBorderPaint.setAntiAlias(true); + Path leftBorderPath = new Path(); + leftBorderPath.setFillType(Path.FillType.EVEN_ODD); + leftBorderPath.moveTo(lbo.x, lbo.y); + leftBorderPath.lineTo(lto.x, lto.y); + leftBorderPath.lineTo(lti.x, lti.y); + leftBorderPath.lineTo(lbi.x, lbi.y); + leftBorderPath.close(); + canvas.drawPath(leftBorderPath, leftBorderPaint); } } } private static float calculateBackoffAntialias(int borderColor, float borderWidth) { - // We will inset background colors and images so antialiasing will not color - // pixels outside the border. - // If the border is transparent we will backoff less, and we will not backoff - // more than half a pixel or half the border width. + // We will inset background colors and images so antialiasing will not color pixels outside the border. + // If the border is transparent we will backoff less, and we will not backoff more than half a pixel or half the border width. float halfBorderWidth = borderWidth / 2.0f; float normalizedBorderAlpha = ((float) Color.alpha(borderColor)) / 255.0f; return Math.min(1f, halfBorderWidth) * normalizedBorderAlpha; @@ -661,22 +536,11 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { private static Pattern spaceAndComma = Pattern.compile("[\\s,]+"); private static Pattern space = Pattern.compile("\\s+"); - private void generateClipPath(String clipPath, RectF bounds, float density) { - if (clipPathPath != null && hasCache("clipPathPath")) { - return; - } - setHasCache("clipPathPath"); - - // Sample string is polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, - // 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%); + private static void drawClipPath(String clipPath, Canvas canvas, Paint paint, RectF bounds, float density) { + // Sample string is polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%); String functionName = clipPath.substring(0, clipPath.indexOf("(")); String value = clipPath.substring(clipPath.indexOf("(") + 1, clipPath.indexOf(")")); - if (clipPathPath == null) { - clipPathPath = new Path(); - } else { - clipPathPath.reset(); - } String[] arr; float top; float right; @@ -690,7 +554,8 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { right = cssValueToDevicePixels(arr[1], bounds.right, density); bottom = cssValueToDevicePixels(arr[2], bounds.bottom, density); left = cssValueToDevicePixels(arr[3], bounds.right, density); - clipPathPath.addRect(new RectF(left, top, right, bottom), Path.Direction.CW); + + canvas.drawRect(left, top, right, bottom, paint); break; case "inset": arr = spaceAndComma.split(value); @@ -715,21 +580,18 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { } top = cssValueToDevicePixels(topString, bounds.bottom, density); - right = cssValueToDevicePixels("100%", bounds.right, density) - - cssValueToDevicePixels(rightString, bounds.right, density); - bottom = cssValueToDevicePixels("100%", bounds.bottom, density) - - cssValueToDevicePixels(bottomString, bounds.bottom, density); + right = cssValueToDevicePixels("100%", bounds.right, density) - cssValueToDevicePixels(rightString, bounds.right, density); + bottom = cssValueToDevicePixels("100%", bounds.bottom, density) - cssValueToDevicePixels(bottomString, bounds.bottom, density); left = cssValueToDevicePixels(leftString, bounds.right, density); - clipPathPath.addRect(new RectF(left, top, right, bottom), Path.Direction.CW); + canvas.drawRect(left, top, right, bottom, paint); break; case "circle": arr = space.split(value); - float radius = cssValueToDevicePixels(arr[0], - (bounds.width() > bounds.height() ? bounds.height() : bounds.width()) / 2, density); + float radius = cssValueToDevicePixels(arr[0], (bounds.width() > bounds.height() ? bounds.height() : bounds.width()) / 2, density); float y = cssValueToDevicePixels(arr[2], bounds.height(), density); float x = cssValueToDevicePixels(arr[3], bounds.width(), density); - clipPathPath.addCircle(x, y, radius, Path.Direction.CW); + canvas.drawCircle(x, y, radius, paint); break; case "ellipse": arr = space.split(value); @@ -741,35 +603,31 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { top = cY - rY; right = (rX * 2) + left; bottom = (rY * 2) + top; - clipPathPath.addOval(new RectF(left, top, right, bottom), Path.Direction.CW); + canvas.drawOval(new RectF(left, top, right, bottom), paint); break; case "polygon": + Path path = new Path(); PointF firstPoint = null; arr = value.split(","); for (String s : arr) { String[] xy = space.split(s.trim()); - PointF point = new PointF(cssValueToDevicePixels(xy[0], bounds.width(), density), - cssValueToDevicePixels(xy[1], bounds.height(), density)); + PointF point = new PointF(cssValueToDevicePixels(xy[0], bounds.width(), density), cssValueToDevicePixels(xy[1], bounds.height(), density)); if (firstPoint == null) { firstPoint = point; - clipPathPath.moveTo(point.x, point.y); + path.moveTo(point.x, point.y); } - clipPathPath.lineTo(point.x, point.y); + path.lineTo(point.x, point.y); } if (firstPoint != null) { - clipPathPath.lineTo(firstPoint.x, firstPoint.y); + path.lineTo(firstPoint.x, firstPoint.y); } + canvas.drawPath(path, paint); break; } } - private void drawClipPath(String clipPath, Canvas canvas, Paint paint, RectF bounds, float density) { - generateClipPath(clipPath, bounds, density); - canvas.drawPath(clipPathPath, paint); - } - private BackgroundDrawParams getDrawParams(float width, float height) { BackgroundDrawParams res = new BackgroundDrawParams(); @@ -805,18 +663,15 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { res.sizeX = imageWidth; res.sizeY = imageHeight; - } else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) - && (("px".equals(vx.getUnit()) && "px".equals(vy.getUnit())) - || ((vx.getUnit() == null || vx.getUnit().isEmpty()) - && (vy.getUnit() == null || vy.getUnit().isEmpty())))) { + } else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) && + (("px".equals(vx.getUnit()) && "px".equals(vy.getUnit())) || ((vx.getUnit() == null || vx.getUnit().isEmpty()) && (vy.getUnit() == null || vy.getUnit().isEmpty())))) { imageWidth = vx.getValue(); imageHeight = vy.getValue(); res.sizeX = imageWidth; res.sizeY = imageHeight; } - } else if (this.backgroundSizeParsedCSSValues.length == 1 - && "ident".equals(this.backgroundSizeParsedCSSValues[0].getType())) { + } else if (this.backgroundSizeParsedCSSValues.length == 1 && "ident".equals(this.backgroundSizeParsedCSSValues[0].getType())) { float scale = 0; if ("cover".equals(this.backgroundSizeParsedCSSValues[0].getString())) { @@ -847,10 +702,8 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { if ("%".equals(vx.getUnit()) && "%".equals(vy.getUnit())) { res.posX = spaceX * vx.getValue() / 100; res.posY = spaceY * vy.getValue() / 100; - } else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) - && (("px".equals(vx.getUnit()) && "px".equals(vy.getUnit())) - || ((vx.getUnit() == null || vx.getUnit().isEmpty()) - && (vy.getUnit() == null || vy.getUnit().isEmpty())))) { + } else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) && + (("px".equals(vx.getUnit()) && "px".equals(vy.getUnit())) || ((vx.getUnit() == null || vx.getUnit().isEmpty()) && (vy.getUnit() == null || vy.getUnit().isEmpty())))) { res.posX = vx.getValue(); res.posY = vy.getValue(); } else if ("ident".equals(vx.getType()) && "ident".equals(vy.getType())) { @@ -898,14 +751,14 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { String val = values[0].getString().toLowerCase(Locale.ENGLISH); if ("left".equals(val) || "right".equals(val)) { - result = new CSSValue[] { values[0], center }; + result = new CSSValue[]{values[0], center}; } else if ("top".equals(val) || "bottom".equals(val)) { - result = new CSSValue[] { center, values[0] }; + result = new CSSValue[]{center, values[0]}; } else if ("center".equals(val)) { - result = new CSSValue[] { center, center }; + result = new CSSValue[]{center, center}; } } else if ("number".equals(values[0].getType())) { - result = new CSSValue[] { values[0], center }; + result = new CSSValue[]{values[0], center}; } } @@ -928,22 +781,29 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { "id: " + this.id + "; " + - "borderTopColor: " + this.borderTopColor + "; " + "borderRightColor: " + this.borderRightColor + "; " - + "borderBottomColor: " + this.borderBottomColor + "; " + "borderLeftColor: " + this.borderLeftColor - + "; " + + "borderTopColor: " + this.borderTopColor + "; " + + "borderRightColor: " + this.borderRightColor + "; " + + "borderBottomColor: " + this.borderBottomColor + "; " + + "borderLeftColor: " + this.borderLeftColor + "; " + - "borderTopWidth: " + this.borderTopWidth + "; " + "borderRightWidth: " + this.borderRightWidth + "; " - + "borderBottomWidth: " + this.borderBottomWidth + "; " + "borderLeftWidth: " + this.borderLeftWidth - + "; " + + "borderTopWidth: " + this.borderTopWidth + "; " + + "borderRightWidth: " + this.borderRightWidth + "; " + + "borderBottomWidth: " + this.borderBottomWidth + "; " + + "borderLeftWidth: " + this.borderLeftWidth + "; " + - "borderTopLeftRadius: " + this.borderTopLeftRadius + "; " + "borderTopRightRadius: " - + this.borderTopRightRadius + "; " + "borderBottomRightRadius: " + this.borderBottomRightRadius + "; " - + "borderBottomLeftRadius: " + this.borderBottomLeftRadius + "; " + + "borderTopLeftRadius: " + this.borderTopLeftRadius + "; " + + "borderTopRightRadius: " + this.borderTopRightRadius + "; " + + "borderBottomRightRadius: " + this.borderBottomRightRadius + "; " + + "borderBottomLeftRadius: " + this.borderBottomLeftRadius + "; " + - "clipPath: " + this.clipPath + "; " + "backgroundColor: " + this.backgroundColor + "; " - + "backgroundImage: " + this.backgroundImage + "; " + "backgroundBitmap: " + this.backgroundBitmap - + "; " + "backgroundRepeat: " + this.backgroundRepeat + "; " + "backgroundPosition: " - + this.backgroundPosition + "; " + "backgroundSize: " + this.backgroundSize + "; "; + "clipPath: " + this.clipPath + "; " + + "backgroundColor: " + this.backgroundColor + "; " + + "backgroundImage: " + this.backgroundImage + "; " + + "backgroundBitmap: " + this.backgroundBitmap + "; " + + "backgroundRepeat: " + this.backgroundRepeat + "; " + + "backgroundPosition: " + this.backgroundPosition + "; " + + "backgroundSize: " + this.backgroundSize + "; " + ; } @Override @@ -963,72 +823,18 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { return drawable; } - private void generateBackgroundOutlinePath(Rect bounds) { - if (backgroundOutlinePath != null && hasCache("backgroundOutlinePath")) { - return; - } - setHasCache("backgroundOutlinePath"); - - if (backgroundOutlinePath == null) { - backgroundOutlinePath = new Path(); - } else { - backgroundOutlinePath.reset(); - } - float[] backgroundRadii = { Math.max(0, borderTopLeftRadius), Math.max(0, borderTopLeftRadius), - Math.max(0, borderTopRightRadius), Math.max(0, borderTopRightRadius), - Math.max(0, borderBottomRightRadius), Math.max(0, borderBottomRightRadius), - Math.max(0, borderBottomLeftRadius), Math.max(0, borderBottomLeftRadius) }; - backgroundOutlinePath.addRoundRect(new RectF(bounds), backgroundRadii, Path.Direction.CW); - } - - private void generateInnerBorderPath(Rect bounds) { - if (innerBorderPath != null && hasCache("innerBorderPath")) { - return; - } - setHasCache("innerBorderPath"); - - if (innerBorderPath == null) { - innerBorderPath = new Path(); - } else { - innerBorderPath.reset(); - } - - float width = (float) bounds.width(); - float height = (float) bounds.height(); - - RectF borderInnerRect = new RectF(borderLeftWidth, borderTopWidth, width - borderRightWidth, - height - borderBottomWidth); - float[] borderInnerRadii = { Math.max(0, borderTopLeftRadius - borderLeftWidth), - Math.max(0, borderTopLeftRadius - borderTopWidth), Math.max(0, borderTopRightRadius - borderRightWidth), - Math.max(0, borderTopRightRadius - borderTopWidth), - Math.max(0, borderBottomRightRadius - borderRightWidth), - Math.max(0, borderBottomRightRadius - borderBottomWidth), - Math.max(0, borderBottomLeftRadius - borderLeftWidth), - Math.max(0, borderBottomLeftRadius - borderBottomWidth) }; - innerBorderPath.addRoundRect(borderInnerRect, borderInnerRadii, Path.Direction.CCW); - } - - public boolean shouldOutline() { - return (android.os.Build.VERSION.SDK_INT >= 21 && getUniformBorderRadius() > 0 && !hasBorderWidth()); - } - @Override public void getOutline(@NonNull Outline outline) { if (android.os.Build.VERSION.SDK_INT >= 21) { - if (this.clipPath != null) { - // no clip! - generateClipPath(this.clipPath, new RectF(getBounds()), density); - outline.setConvexPath(this.clipPathPath); - } else if (hasUniformBorder()) { - // clip! - outline.setRoundRect(getBounds(), Math.max(0, borderTopLeftRadius)); - } else { - // no clip! - generateBackgroundOutlinePath(getBounds()); - if (backgroundOutlinePath != null) { - outline.setConvexPath(backgroundOutlinePath); - } - } + Path backgroundPath = new Path(); + float[] backgroundRadii = { + Math.max(0, borderTopLeftRadius), Math.max(0, borderTopLeftRadius), + Math.max(0, borderTopRightRadius), Math.max(0, borderTopRightRadius), + Math.max(0, borderBottomRightRadius), Math.max(0, borderBottomRightRadius), + Math.max(0, borderBottomLeftRadius), Math.max(0, borderBottomLeftRadius) + }; + backgroundPath.addRoundRect(new RectF(getBounds()), backgroundRadii, Path.Direction.CW); + outline.setConvexPath(backgroundPath); } else { throw new IllegalStateException("Method supported on API 21 or higher"); } @@ -1042,4 +848,4 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { private float sizeX; private float sizeY; } -} +} \ No newline at end of file diff --git a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java index c79922717..7a3abe8ac 100644 --- a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java +++ b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java @@ -1,17 +1,9 @@ /** - * + * */ package org.nativescript.widgets; import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.drawable.Drawable; -import android.os.Build; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; @@ -19,58 +11,32 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import android.util.Log; - -import androidx.annotation.RequiresApi; - /** * @author hhristov * */ public abstract class LayoutBase extends ViewGroup { private boolean passThroughParent; - private boolean clipEnabled = true; - private static Paint clipPaint; public LayoutBase(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - init(); - } - - private void init() { - setLayerType(View.LAYER_TYPE_HARDWARE, null); - setClipToBounds(clipEnabled); - } - - public LayoutBase(Context context) { - super(context); - init(); - } - - public void setClipToBounds(boolean value) { - clipEnabled = value; - if (value) { - // TODO: does it cost to enable it even if we actually - // will still need to clip? - if (android.os.Build.VERSION.SDK_INT >= 21) { - setClipToOutline(true); - } - } else if (android.os.Build.VERSION.SDK_INT >= 21) { - setClipToOutline(false); - } } + public LayoutBase(Context context) { + super(context); + } + @Override protected LayoutParams generateDefaultLayoutParams() { return new CommonLayoutParams(); } - + /** * {@inheritDoc} */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { - return new CommonLayoutParams(); + return new CommonLayoutParams(); } /** @@ -84,21 +50,21 @@ public abstract class LayoutBase extends ViewGroup { @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams from) { if (from instanceof CommonLayoutParams) - return new CommonLayoutParams((CommonLayoutParams) from); + return new CommonLayoutParams((CommonLayoutParams)from); if (from instanceof FrameLayout.LayoutParams) - return new CommonLayoutParams((FrameLayout.LayoutParams) from); + return new CommonLayoutParams((FrameLayout.LayoutParams)from); if (from instanceof ViewGroup.MarginLayoutParams) - return new CommonLayoutParams((ViewGroup.MarginLayoutParams) from); + return new CommonLayoutParams((ViewGroup.MarginLayoutParams)from); return new CommonLayoutParams(from); } - @Override - public boolean shouldDelayChildPressedState() { - return false; - } + @Override + public boolean shouldDelayChildPressedState() { + return false; + } @Override public boolean onTouchEvent(MotionEvent event) { @@ -107,25 +73,24 @@ public abstract class LayoutBase extends ViewGroup { } // LayoutBase.onTouchEvent(ev) execution means no interactive child view handled - // the event so we let the event pass through to parent view of the layout - // container + // the event so we let the event pass through to parent view of the layout container // because passThroughParent is set to true return false; } - - protected static int getGravity(View view) { - int gravity = -1; - LayoutParams params = view.getLayoutParams(); - if (params instanceof FrameLayout.LayoutParams) { - gravity = ((FrameLayout.LayoutParams) params).gravity; - } - + + protected static int getGravity(View view) { + int gravity = -1; + LayoutParams params = view.getLayoutParams(); + if (params instanceof FrameLayout.LayoutParams) { + gravity = ((FrameLayout.LayoutParams)params).gravity; + } + if (gravity == -1) { gravity = Gravity.FILL; } - + return gravity; - } + } public boolean getPassThroughParent() { return this.passThroughParent; @@ -134,45 +99,4 @@ public abstract class LayoutBase extends ViewGroup { public void setPassThroughParent(boolean value) { this.passThroughParent = value; } - - public boolean getClipEnabled() { - return this.clipEnabled; - } - - public void setClipEnabled(boolean value) { - this.clipEnabled = value; - } - - @RequiresApi(api = Build.VERSION_CODES.KITKAT) - @Override - protected void dispatchDraw(Canvas canvas) { - if (clipEnabled) { - Drawable drawable = getBackground(); - if (drawable instanceof BorderDrawable) { - Path clippingPath = ((BorderDrawable) drawable).getClippingPath(); - // if no clippingPath either it is unnecessary or handled by outline - if (clippingPath != null) { - if (LayoutBase.clipPaint == null) { - LayoutBase.clipPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - // LayoutBase.clipPaint.setColor(Color.WHITE); - LayoutBase.clipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - } - int saveCount; - int width = getWidth(); - int height = getHeight(); - if (android.os.Build.VERSION.SDK_INT >= 21) { - saveCount = canvas.saveLayer(new android.graphics.RectF(0.0f, 0.0f, width, height), null); - } else { - saveCount = canvas.saveLayer(0.0f, 0.0f, width, height, null, Canvas.ALL_SAVE_FLAG); - } - super.dispatchDraw(canvas); - // we dont use clipPath as it is not antialiased - canvas.drawPath(clippingPath, LayoutBase.clipPaint); - canvas.restoreToCount(saveCount); - return; - } - } - } - super.dispatchDraw(canvas); - } -} +} \ No newline at end of file