feat(android): support clipToBounds (#9508)

* performance improvements around border handling

BREAKING CHANGE:

* if you have broder-radius or clip-path, it will clip by default
This commit is contained in:
farfromrefuge
2021-08-11 21:06:36 +02:00
committed by Nathan Walker
parent 4f5f0aae77
commit 1ffc1628d0
4 changed files with 514 additions and 250 deletions

View File

@ -9,18 +9,8 @@ export class LayoutBase extends LayoutBaseCommon {
return true; return true;
} }
[clipToBoundsProperty.setNative](value: boolean) { [clipToBoundsProperty.setNative](value: boolean) {
// TODO: Use ClipRectangle if API > 16! (<any>this.nativeViewProtected).setClipToBounds(value);
// 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) { [isPassThroughParentEnabledProperty.setNative](value: boolean) {

View File

@ -51,7 +51,12 @@ def computeTargetSdkVersion() {
} }
} }
android { android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
compileSdkVersion computeCompileSdkVersion() compileSdkVersion computeCompileSdkVersion()
buildToolsVersion computeBuildToolsVersion() buildToolsVersion computeBuildToolsVersion()

View File

@ -18,13 +18,20 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.Shader; import android.graphics.Shader;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import org.nativescript.widgets.image.BitmapOwner; import org.nativescript.widgets.image.BitmapOwner;
import org.nativescript.widgets.image.Fetcher; 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.Locale;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import android.os.Build;
import android.util.Log;
/** /**
* Created by hristov on 6/15/2016. * Created by hristov on 6/15/2016.
*/ */
@ -49,6 +56,20 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
private String clipPath; 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 int backgroundColor;
private String backgroundImage; private String backgroundImage;
private Bitmap backgroundBitmap; private Bitmap backgroundBitmap;
@ -141,6 +162,65 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
return clipPath; 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<String> cacheKeys = new HashSet<String>();
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() { public int getBackgroundColor() {
return backgroundColor; return backgroundColor;
} }
@ -153,7 +233,9 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
return backgroundBitmap; return backgroundBitmap;
} }
public LinearGradientDefinition getBackgroundGradient() { return backgroundGradient; } public LinearGradientDefinition getBackgroundGradient() {
return backgroundGradient;
}
public String getBackgroundRepeat() { public String getBackgroundRepeat() {
return backgroundRepeat; return backgroundRepeat;
@ -168,34 +250,33 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
} }
public boolean hasBorderWidth() { public boolean hasBorderWidth() {
return this.borderTopWidth != 0 return this.borderTopWidth != 0 || this.borderRightWidth != 0 || this.borderBottomWidth != 0
|| this.borderRightWidth != 0 || this.borderLeftWidth != 0;
|| this.borderBottomWidth != 0
|| this.borderLeftWidth != 0;
} }
public boolean hasUniformBorderColor() { public boolean hasUniformBorderColor() {
return this.borderTopColor == this.borderRightColor && return this.borderTopColor == this.borderRightColor && this.borderTopColor == this.borderBottomColor
this.borderTopColor == this.borderBottomColor && && this.borderTopColor == this.borderLeftColor;
this.borderTopColor == this.borderLeftColor;
} }
public boolean hasUniformBorderWidth() { public boolean hasUniformBorderWidth() {
return this.borderTopWidth == this.borderRightWidth && return this.borderTopWidth == this.borderRightWidth && this.borderTopWidth == this.borderBottomWidth
this.borderTopWidth == this.borderBottomWidth && && this.borderTopWidth == this.borderLeftWidth;
this.borderTopWidth == this.borderLeftWidth;
} }
public boolean hasUniformBorderRadius() { public boolean hasUniformBorderRadius() {
return this.borderTopLeftRadius == this.borderTopRightRadius && return this.borderTopLeftRadius == this.borderTopRightRadius
this.borderTopLeftRadius == this.borderBottomRightRadius && && this.borderTopLeftRadius == this.borderBottomRightRadius
this.borderTopLeftRadius == this.borderBottomLeftRadius; && this.borderTopLeftRadius == this.borderBottomLeftRadius;
}
public boolean hasBorderRadius() {
return borderBottomLeftRadius > 0 || borderTopLeftRadius > 0 || borderBottomRightRadius > 0
|| borderTopRightRadius > 0;
} }
public boolean hasUniformBorder() { public boolean hasUniformBorder() {
return this.hasUniformBorderColor() && return this.hasUniformBorderColor() && this.hasUniformBorderWidth() && this.hasUniformBorderRadius();
this.hasUniformBorderWidth() &&
this.hasUniformBorderRadius();
} }
public BorderDrawable(float density) { public BorderDrawable(float density) {
@ -209,33 +290,19 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
this.id = id; this.id = id;
} }
public void refresh(int borderTopColor, public void refresh(int borderTopColor, int borderRightColor, int borderBottomColor, int borderLeftColor,
int borderRightColor,
int borderBottomColor,
int borderLeftColor,
float borderTopWidth, float borderTopWidth, float borderRightWidth, float borderBottomWidth, float borderLeftWidth,
float borderRightWidth,
float borderBottomWidth,
float borderLeftWidth,
float borderTopLeftRadius, float borderTopLeftRadius, float borderTopRightRadius, float borderBottomRightRadius,
float borderTopRightRadius, float borderBottomLeftRadius,
float borderBottomRightRadius,
float borderBottomLeftRadius,
String clipPath, String clipPath,
int backgroundColor, int backgroundColor, String backgroundImageUri, Bitmap backgroundBitmap,
String backgroundImageUri, LinearGradientDefinition backgroundGradient, Context context, String backgroundRepeat,
Bitmap backgroundBitmap, String backgroundPosition, CSSValue[] backgroundPositionParsedCSSValues, String backgroundSize,
LinearGradientDefinition backgroundGradient, CSSValue[] backgroundSizeParsedCSSValues) {
Context context,
String backgroundRepeat,
String backgroundPosition,
CSSValue[] backgroundPositionParsedCSSValues,
String backgroundSize,
CSSValue[] backgroundSizeParsedCSSValues) {
this.borderTopColor = borderTopColor; this.borderTopColor = borderTopColor;
this.borderRightColor = borderRightColor; this.borderRightColor = borderRightColor;
@ -254,6 +321,9 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
this.clipPath = clipPath; this.clipPath = clipPath;
// clear all cached paths
cacheKeys.clear();
this.backgroundColor = backgroundColor; this.backgroundColor = backgroundColor;
this.backgroundImage = backgroundImageUri; this.backgroundImage = backgroundImageUri;
this.backgroundBitmap = backgroundBitmap; this.backgroundBitmap = backgroundBitmap;
@ -273,39 +343,67 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
} }
} }
@Override private void generateBackgroundPath(Rect bounds) {
public void draw(Canvas canvas) {
Rect bounds = this.getBounds();
float width = (float)bounds.width();
float height = (float)bounds.height();
if (width <= 0 || height <= 0) { if (backgroundPath != null && hasCache("backgroundPath")) {
// When the view is off-screen the bounds might be empty and we don't have anything to draw.
return; return;
} }
setHasCache("backgroundPath");
RectF backgroundBoundsF = new RectF(bounds.left, bounds.top, bounds.right, bounds.bottom); 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) };
float topBackoffAntialias = calculateBackoffAntialias(this.borderTopColor, this.borderTopWidth); float topBackoffAntialias = calculateBackoffAntialias(this.borderTopColor, this.borderTopWidth);
float rightBackoffAntialias = calculateBackoffAntialias(this.borderRightColor, this.borderRightWidth); float rightBackoffAntialias = calculateBackoffAntialias(this.borderRightColor, this.borderRightWidth);
float bottomBackoffAntialias = calculateBackoffAntialias(this.borderBottomColor, this.borderBottomWidth); float bottomBackoffAntialias = calculateBackoffAntialias(this.borderBottomColor, this.borderBottomWidth);
float leftBackoffAntialias = calculateBackoffAntialias(this.borderLeftColor, this.borderLeftWidth); float leftBackoffAntialias = calculateBackoffAntialias(this.borderLeftColor, this.borderLeftWidth);
float[] backgroundRadii = { float width = (float) bounds.width();
Math.max(0, borderTopLeftRadius + leftBackoffAntialias), Math.max(0, borderTopLeftRadius + topBackoffAntialias), float height = (float) bounds.height();
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)
};
Path backgroundPath = new Path(); RectF backgroundRect = new RectF(leftBackoffAntialias, topBackoffAntialias, width - rightBackoffAntialias,
RectF backgroundRect = new RectF( height - bottomBackoffAntialias);
leftBackoffAntialias,
topBackoffAntialias,
width - rightBackoffAntialias,
height - bottomBackoffAntialias
);
backgroundPath.addRoundRect(backgroundRect, backgroundRadii, Path.Direction.CW); 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 // draw background
if (this.backgroundColor != 0) { if (this.backgroundColor != 0) {
@ -317,6 +415,7 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
if (this.clipPath != null && !this.clipPath.isEmpty()) { if (this.clipPath != null && !this.clipPath.isEmpty()) {
drawClipPath(this.clipPath, canvas, backgroundColorPaint, backgroundBoundsF, this.density); drawClipPath(this.clipPath, canvas, backgroundColorPaint, backgroundBoundsF, this.density);
} else { } else {
generateBackgroundPath(bounds);
canvas.drawPath(backgroundPath, backgroundColorPaint); canvas.drawPath(backgroundPath, backgroundColorPaint);
} }
} }
@ -335,11 +434,9 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
transform.postTranslate(params.posX, params.posY); transform.postTranslate(params.posX, params.posY);
Paint backgroundImagePaint = new Paint(); Paint backgroundImagePaint = new Paint();
BitmapShader shader = new BitmapShader( BitmapShader shader = new BitmapShader(this.backgroundBitmap,
this.backgroundBitmap, params.repeatX ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP,
params.repeatX ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP, params.repeatY ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP);
params.repeatY ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP
);
shader.setLocalMatrix(transform); shader.setLocalMatrix(transform);
backgroundImagePaint.setAntiAlias(true); backgroundImagePaint.setAntiAlias(true);
backgroundImagePaint.setFilterBitmap(true); backgroundImagePaint.setFilterBitmap(true);
@ -354,13 +451,16 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
drawClipPath(this.clipPath, canvas, backgroundImagePaint, backgroundBoundsF, this.density); drawClipPath(this.clipPath, canvas, backgroundImagePaint, backgroundBoundsF, this.density);
} else { } else {
boolean supportsPathOp = android.os.Build.VERSION.SDK_INT >= 19; boolean supportsPathOp = android.os.Build.VERSION.SDK_INT >= 19;
generateBackgroundPath(bounds);
if (supportsPathOp) { if (supportsPathOp) {
Path backgroundNoRepeatPath = new Path(); 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); intersect(backgroundNoRepeatPath, backgroundPath);
canvas.drawPath(backgroundNoRepeatPath, backgroundImagePaint); canvas.drawPath(backgroundNoRepeatPath, backgroundImagePaint);
} else { } 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.save();
canvas.clipRect(params.posX, params.posY, params.posX + imageWidth, params.posY + imageHeight); canvas.clipRect(params.posX, params.posY, params.posX + imageWidth, params.posY + imageHeight);
canvas.drawPath(backgroundPath, backgroundImagePaint); canvas.drawPath(backgroundPath, backgroundImagePaint);
@ -372,10 +472,9 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
if (this.backgroundGradient != null) { if (this.backgroundGradient != null) {
LinearGradientDefinition def = this.backgroundGradient; LinearGradientDefinition def = this.backgroundGradient;
Paint backgroundGradientPaint = new Paint(); Paint backgroundGradientPaint = new Paint();
LinearGradient shader = new LinearGradient( LinearGradient shader = new LinearGradient(def.getStartX() * width, def.getStartY() * height,
def.getStartX() * width, def.getStartY() * height, def.getEndX() * width, def.getEndY() * height, def.getColors(), def.getStops(),
def.getEndX() * width, def.getEndY() * height, Shader.TileMode.MIRROR);
def.getColors(), def.getStops(), Shader.TileMode.MIRROR);
backgroundGradientPaint.setAntiAlias(true); backgroundGradientPaint.setAntiAlias(true);
backgroundGradientPaint.setFilterBitmap(true); backgroundGradientPaint.setFilterBitmap(true);
backgroundGradientPaint.setShader(shader); backgroundGradientPaint.setShader(shader);
@ -391,7 +490,10 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
if (this.clipPath != null && !this.clipPath.isEmpty()) { if (this.clipPath != null && !this.clipPath.isEmpty()) {
float borderWidth = this.getUniformBorderWidth(); float borderWidth = this.getUniformBorderWidth();
if (borderWidth > 0) { if (borderWidth > 0) {
Paint borderPaint = new Paint(); if (borderPaint == null) {
borderPaint = new Paint();
borderPaint.setAntiAlias(true);
}
borderPaint.setColor(this.getUniformBorderColor()); borderPaint.setColor(this.getUniformBorderColor());
borderPaint.setStyle(Paint.Style.STROKE); borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(borderWidth); borderPaint.setStrokeWidth(borderWidth);
@ -402,36 +504,15 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
} else if (this.hasUniformBorderColor()) { } else if (this.hasUniformBorderColor()) {
// iOS and browsers use black when no color is specified. // iOS and browsers use black when no color is specified.
if (borderLeftWidth > 0 || borderTopWidth > 0 || borderRightWidth > 0 || borderBottomWidth > 0) { if (borderLeftWidth > 0 || borderTopWidth > 0 || borderRightWidth > 0 || borderBottomWidth > 0) {
Paint borderPaint = new Paint(); if (borderPaint == null) {
borderPaint.setColor(this.getUniformBorderColor()); borderPaint = new Paint();
borderPaint.setAntiAlias(true);
}
borderPaint.setStyle(Paint.Style.FILL); borderPaint.setStyle(Paint.Style.FILL);
borderPaint.setAntiAlias(true); borderPaint.setColor(this.getUniformBorderColor());
Path borderPath = new Path();
RectF borderOuterRect = new RectF(0, 0, width, height); generateUniformedColorBorderPath(bounds);
float[] borderOuterRadii = { canvas.drawPath(unifiedColorBorderPath, borderPaint);
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 { } else {
float top = this.borderTopWidth; float top = this.borderTopWidth;
@ -439,16 +520,16 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
float bottom = this.borderBottomWidth; float bottom = this.borderBottomWidth;
float left = this.borderLeftWidth; float left = this.borderLeftWidth;
//lto rto // lto rto
// +---------------------+ // +---------------------+
// |lti rti| // |lti rti|
// | | // | |
// | | // | |
// | | // | |
// | | // | |
// |lbi rbi| // |lbi rbi|
// +---------------------+ // +---------------------+
//lbo rbo // lbo rbo
PointF lto = new PointF(0, 0); // left-top-outside PointF lto = new PointF(0, 0); // left-top-outside
PointF lti = new PointF(left, top); // left-top-inside PointF lti = new PointF(left, top); // left-top-inside
@ -463,66 +544,109 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
PointF lbi = new PointF(left, bounds.bottom - bottom); // left-bottom-inside PointF lbi = new PointF(left, bounds.bottom - bottom); // left-bottom-inside
if (this.borderTopWidth > 0) { if (this.borderTopWidth > 0) {
Paint topBorderPaint = new Paint();
topBorderPaint.setColor(this.borderTopColor); if (topBorderPath == null || !hasCache("topBorderPath")) {
topBorderPaint.setAntiAlias(true); setHasCache("topBorderPath");
Path topBorderPath = new Path(); if (topBorderPath == null) {
topBorderPath.setFillType(Path.FillType.EVEN_ODD); topBorderPath = new Path();
topBorderPath.moveTo(lto.x, lto.y); } else {
topBorderPath.lineTo(rto.x, rto.y); topBorderPath.reset();
topBorderPath.lineTo(rti.x, rti.y); }
topBorderPath.lineTo(lti.x, lti.y); topBorderPath.setFillType(Path.FillType.EVEN_ODD);
topBorderPath.close(); topBorderPath.moveTo(lto.x, lto.y);
canvas.drawPath(topBorderPath, topBorderPaint); 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);
} }
if (this.borderRightWidth > 0) { if (this.borderRightWidth > 0) {
Paint rightBorderPaint = new Paint(); if (rightBorderPath == null || !hasCache("rightBorderPath")) {
rightBorderPaint.setColor(this.borderRightColor); setHasCache("rightBorderPath");
rightBorderPaint.setAntiAlias(true); if (rightBorderPath == null) {
Path rightBorderPath = new Path(); rightBorderPath = new Path();
rightBorderPath.setFillType(Path.FillType.EVEN_ODD); } else {
rightBorderPath.moveTo(rto.x, rto.y); rightBorderPath.reset();
rightBorderPath.lineTo(rbo.x, rbo.y); }
rightBorderPath.lineTo(rbi.x, rbi.y); rightBorderPath.setFillType(Path.FillType.EVEN_ODD);
rightBorderPath.lineTo(rti.x, rti.y); rightBorderPath.moveTo(rto.x, rto.y);
rightBorderPath.close(); rightBorderPath.lineTo(rbo.x, rbo.y);
canvas.drawPath(rightBorderPath, rightBorderPaint); 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);
} }
if (this.borderBottomWidth > 0) { if (this.borderBottomWidth > 0) {
Paint bottomBorderPaint = new Paint(); if (bottomBorderPath == null || !hasCache("bottomBorderPath")) {
bottomBorderPaint.setColor(this.borderBottomColor); setHasCache("bottomBorderPath");
bottomBorderPaint.setAntiAlias(true); if (bottomBorderPath == null) {
Path bottomBorderPath = new Path(); bottomBorderPath = new Path();
bottomBorderPath.setFillType(Path.FillType.EVEN_ODD); } else {
bottomBorderPath.moveTo(rbo.x, rbo.y); bottomBorderPath.reset();
bottomBorderPath.lineTo(lbo.x, lbo.y); }
bottomBorderPath.lineTo(lbi.x, lbi.y); bottomBorderPath.setFillType(Path.FillType.EVEN_ODD);
bottomBorderPath.lineTo(rbi.x, rbi.y); bottomBorderPath.moveTo(rbo.x, rbo.y);
bottomBorderPath.close(); bottomBorderPath.lineTo(lbo.x, lbo.y);
canvas.drawPath(bottomBorderPath, bottomBorderPaint); 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);
} }
if (this.borderLeftWidth > 0) { if (this.borderLeftWidth > 0) {
Paint leftBorderPaint = new Paint();
leftBorderPaint.setColor(this.borderLeftColor); if (leftBorderPath == null || !hasCache("leftBorderPath")) {
leftBorderPaint.setAntiAlias(true); setHasCache("leftBorderPath");
Path leftBorderPath = new Path(); if (leftBorderPath == null) {
leftBorderPath.setFillType(Path.FillType.EVEN_ODD); leftBorderPath = new Path();
leftBorderPath.moveTo(lbo.x, lbo.y); } else {
leftBorderPath.lineTo(lto.x, lto.y); leftBorderPath.reset();
leftBorderPath.lineTo(lti.x, lti.y); }
leftBorderPath.lineTo(lbi.x, lbi.y); leftBorderPath.setFillType(Path.FillType.EVEN_ODD);
leftBorderPath.close(); leftBorderPath.moveTo(lbo.x, lbo.y);
canvas.drawPath(leftBorderPath, leftBorderPaint); 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);
} }
} }
} }
private static float calculateBackoffAntialias(int borderColor, float borderWidth) { private static float calculateBackoffAntialias(int borderColor, float borderWidth) {
// We will inset background colors and images so antialiasing will not color pixels outside the border. // We will inset background colors and images so antialiasing will not color
// If the border is transparent we will backoff less, and we will not backoff more than half a pixel or half the border width. // 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 halfBorderWidth = borderWidth / 2.0f;
float normalizedBorderAlpha = ((float) Color.alpha(borderColor)) / 255.0f; float normalizedBorderAlpha = ((float) Color.alpha(borderColor)) / 255.0f;
return Math.min(1f, halfBorderWidth) * normalizedBorderAlpha; return Math.min(1f, halfBorderWidth) * normalizedBorderAlpha;
@ -536,11 +660,22 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
private static Pattern spaceAndComma = Pattern.compile("[\\s,]+"); private static Pattern spaceAndComma = Pattern.compile("[\\s,]+");
private static Pattern space = Pattern.compile("\\s+"); private static Pattern space = Pattern.compile("\\s+");
private static void drawClipPath(String clipPath, Canvas canvas, Paint paint, RectF bounds, float density) { private void generateClipPath(String clipPath, 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%); 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%);
String functionName = clipPath.substring(0, clipPath.indexOf("(")); String functionName = clipPath.substring(0, clipPath.indexOf("("));
String value = clipPath.substring(clipPath.indexOf("(") + 1, clipPath.indexOf(")")); String value = clipPath.substring(clipPath.indexOf("(") + 1, clipPath.indexOf(")"));
if (clipPathPath == null) {
clipPathPath = new Path();
} else {
clipPathPath.reset();
}
String[] arr; String[] arr;
float top; float top;
float right; float right;
@ -554,8 +689,7 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
right = cssValueToDevicePixels(arr[1], bounds.right, density); right = cssValueToDevicePixels(arr[1], bounds.right, density);
bottom = cssValueToDevicePixels(arr[2], bounds.bottom, density); bottom = cssValueToDevicePixels(arr[2], bounds.bottom, density);
left = cssValueToDevicePixels(arr[3], bounds.right, 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; break;
case "inset": case "inset":
arr = spaceAndComma.split(value); arr = spaceAndComma.split(value);
@ -580,18 +714,21 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
} }
top = cssValueToDevicePixels(topString, bounds.bottom, density); top = cssValueToDevicePixels(topString, bounds.bottom, density);
right = cssValueToDevicePixels("100%", bounds.right, density) - cssValueToDevicePixels(rightString, bounds.right, density); right = cssValueToDevicePixels("100%", bounds.right, density)
bottom = cssValueToDevicePixels("100%", bounds.bottom, density) - cssValueToDevicePixels(bottomString, bounds.bottom, density); - cssValueToDevicePixels(rightString, bounds.right, density);
bottom = cssValueToDevicePixels("100%", bounds.bottom, density)
- cssValueToDevicePixels(bottomString, bounds.bottom, density);
left = cssValueToDevicePixels(leftString, bounds.right, density); left = cssValueToDevicePixels(leftString, bounds.right, density);
canvas.drawRect(left, top, right, bottom, paint); clipPathPath.addRect(new RectF(left, top, right, bottom), Path.Direction.CW);
break; break;
case "circle": case "circle":
arr = space.split(value); 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 y = cssValueToDevicePixels(arr[2], bounds.height(), density);
float x = cssValueToDevicePixels(arr[3], bounds.width(), density); float x = cssValueToDevicePixels(arr[3], bounds.width(), density);
canvas.drawCircle(x, y, radius, paint); clipPathPath.addCircle(x, y, radius, Path.Direction.CW);
break; break;
case "ellipse": case "ellipse":
arr = space.split(value); arr = space.split(value);
@ -603,31 +740,35 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
top = cY - rY; top = cY - rY;
right = (rX * 2) + left; right = (rX * 2) + left;
bottom = (rY * 2) + top; bottom = (rY * 2) + top;
canvas.drawOval(new RectF(left, top, right, bottom), paint); clipPathPath.addOval(new RectF(left, top, right, bottom), Path.Direction.CW);
break; break;
case "polygon": case "polygon":
Path path = new Path();
PointF firstPoint = null; PointF firstPoint = null;
arr = value.split(","); arr = value.split(",");
for (String s : arr) { for (String s : arr) {
String[] xy = space.split(s.trim()); 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) { if (firstPoint == null) {
firstPoint = point; firstPoint = point;
path.moveTo(point.x, point.y); clipPathPath.moveTo(point.x, point.y);
} }
path.lineTo(point.x, point.y); clipPathPath.lineTo(point.x, point.y);
} }
if (firstPoint != null) { if (firstPoint != null) {
path.lineTo(firstPoint.x, firstPoint.y); clipPathPath.lineTo(firstPoint.x, firstPoint.y);
} }
canvas.drawPath(path, paint);
break; 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) { private BackgroundDrawParams getDrawParams(float width, float height) {
BackgroundDrawParams res = new BackgroundDrawParams(); BackgroundDrawParams res = new BackgroundDrawParams();
@ -663,15 +804,18 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
res.sizeX = imageWidth; res.sizeX = imageWidth;
res.sizeY = imageHeight; res.sizeY = imageHeight;
} else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) && } 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())))) { && (("px".equals(vx.getUnit()) && "px".equals(vy.getUnit()))
|| ((vx.getUnit() == null || vx.getUnit().isEmpty())
&& (vy.getUnit() == null || vy.getUnit().isEmpty())))) {
imageWidth = vx.getValue(); imageWidth = vx.getValue();
imageHeight = vy.getValue(); imageHeight = vy.getValue();
res.sizeX = imageWidth; res.sizeX = imageWidth;
res.sizeY = imageHeight; 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; float scale = 0;
if ("cover".equals(this.backgroundSizeParsedCSSValues[0].getString())) { if ("cover".equals(this.backgroundSizeParsedCSSValues[0].getString())) {
@ -702,8 +846,10 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
if ("%".equals(vx.getUnit()) && "%".equals(vy.getUnit())) { if ("%".equals(vx.getUnit()) && "%".equals(vy.getUnit())) {
res.posX = spaceX * vx.getValue() / 100; res.posX = spaceX * vx.getValue() / 100;
res.posY = spaceY * vy.getValue() / 100; res.posY = spaceY * vy.getValue() / 100;
} else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) && } 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())))) { && (("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.posX = vx.getValue();
res.posY = vy.getValue(); res.posY = vy.getValue();
} else if ("ident".equals(vx.getType()) && "ident".equals(vy.getType())) { } else if ("ident".equals(vx.getType()) && "ident".equals(vy.getType())) {
@ -751,14 +897,14 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
String val = values[0].getString().toLowerCase(Locale.ENGLISH); String val = values[0].getString().toLowerCase(Locale.ENGLISH);
if ("left".equals(val) || "right".equals(val)) { 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)) { } 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)) { } else if ("center".equals(val)) {
result = new CSSValue[]{center, center}; result = new CSSValue[] { center, center };
} }
} else if ("number".equals(values[0].getType())) { } else if ("number".equals(values[0].getType())) {
result = new CSSValue[]{values[0], center}; result = new CSSValue[] { values[0], center };
} }
} }
@ -781,29 +927,22 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
"id: " + this.id + "; " + "id: " + this.id + "; " +
"borderTopColor: " + this.borderTopColor + "; " + "borderTopColor: " + this.borderTopColor + "; " + "borderRightColor: " + this.borderRightColor + "; "
"borderRightColor: " + this.borderRightColor + "; " + + "borderBottomColor: " + this.borderBottomColor + "; " + "borderLeftColor: " + this.borderLeftColor
"borderBottomColor: " + this.borderBottomColor + "; " + + "; " +
"borderLeftColor: " + this.borderLeftColor + "; " +
"borderTopWidth: " + this.borderTopWidth + "; " + "borderTopWidth: " + this.borderTopWidth + "; " + "borderRightWidth: " + this.borderRightWidth + "; "
"borderRightWidth: " + this.borderRightWidth + "; " + + "borderBottomWidth: " + this.borderBottomWidth + "; " + "borderLeftWidth: " + this.borderLeftWidth
"borderBottomWidth: " + this.borderBottomWidth + "; " + + "; " +
"borderLeftWidth: " + this.borderLeftWidth + "; " +
"borderTopLeftRadius: " + this.borderTopLeftRadius + "; " + "borderTopLeftRadius: " + this.borderTopLeftRadius + "; " + "borderTopRightRadius: "
"borderTopRightRadius: " + this.borderTopRightRadius + "; " + + this.borderTopRightRadius + "; " + "borderBottomRightRadius: " + this.borderBottomRightRadius + "; "
"borderBottomRightRadius: " + this.borderBottomRightRadius + "; " + + "borderBottomLeftRadius: " + this.borderBottomLeftRadius + "; " +
"borderBottomLeftRadius: " + this.borderBottomLeftRadius + "; " +
"clipPath: " + this.clipPath + "; " + "clipPath: " + this.clipPath + "; " + "backgroundColor: " + this.backgroundColor + "; "
"backgroundColor: " + this.backgroundColor + "; " + + "backgroundImage: " + this.backgroundImage + "; " + "backgroundBitmap: " + this.backgroundBitmap
"backgroundImage: " + this.backgroundImage + "; " + + "; " + "backgroundRepeat: " + this.backgroundRepeat + "; " + "backgroundPosition: "
"backgroundBitmap: " + this.backgroundBitmap + "; " + + this.backgroundPosition + "; " + "backgroundSize: " + this.backgroundSize + "; ";
"backgroundRepeat: " + this.backgroundRepeat + "; " +
"backgroundPosition: " + this.backgroundPosition + "; " +
"backgroundSize: " + this.backgroundSize + "; "
;
} }
@Override @Override
@ -823,18 +962,72 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
return drawable; 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 @Override
public void getOutline(@NonNull Outline outline) { public void getOutline(@NonNull Outline outline) {
if (android.os.Build.VERSION.SDK_INT >= 21) { if (android.os.Build.VERSION.SDK_INT >= 21) {
Path backgroundPath = new Path(); if (this.clipPath != null) {
float[] backgroundRadii = { // no clip!
Math.max(0, borderTopLeftRadius), Math.max(0, borderTopLeftRadius), generateClipPath(this.clipPath, new RectF(getBounds()), density);
Math.max(0, borderTopRightRadius), Math.max(0, borderTopRightRadius), outline.setConvexPath(this.clipPathPath);
Math.max(0, borderBottomRightRadius), Math.max(0, borderBottomRightRadius), } else if (hasUniformBorder()) {
Math.max(0, borderBottomLeftRadius), Math.max(0, borderBottomLeftRadius) // clip!
}; outline.setRoundRect(getBounds(), Math.max(0, borderTopLeftRadius));
backgroundPath.addRoundRect(new RectF(getBounds()), backgroundRadii, Path.Direction.CW); } else {
outline.setConvexPath(backgroundPath); // no clip!
generateBackgroundOutlinePath(getBounds());
if (backgroundOutlinePath != null) {
outline.setConvexPath(backgroundOutlinePath);
}
}
} else { } else {
throw new IllegalStateException("Method supported on API 21 or higher"); throw new IllegalStateException("Method supported on API 21 or higher");
} }
@ -848,4 +1041,4 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
private float sizeX; private float sizeX;
private float sizeY; private float sizeY;
} }
} }

View File

@ -1,9 +1,17 @@
/** /**
* *
*/ */
package org.nativescript.widgets; package org.nativescript.widgets;
import android.content.Context; 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.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -11,32 +19,58 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.util.Log;
import androidx.annotation.RequiresApi;
/** /**
* @author hhristov * @author hhristov
* *
*/ */
public abstract class LayoutBase extends ViewGroup { public abstract class LayoutBase extends ViewGroup {
private boolean passThroughParent; private boolean passThroughParent;
private boolean clipEnabled = true;
private static Paint clipPaint;
public LayoutBase(Context context, AttributeSet attrs, int defStyleAttr) { public LayoutBase(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, 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 @Override
protected LayoutParams generateDefaultLayoutParams() { protected LayoutParams generateDefaultLayoutParams() {
return new CommonLayoutParams(); return new CommonLayoutParams();
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public LayoutParams generateLayoutParams(AttributeSet attrs) { public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new CommonLayoutParams(); return new CommonLayoutParams();
} }
/** /**
@ -50,21 +84,21 @@ public abstract class LayoutBase extends ViewGroup {
@Override @Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams from) { protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams from) {
if (from instanceof CommonLayoutParams) if (from instanceof CommonLayoutParams)
return new CommonLayoutParams((CommonLayoutParams)from); return new CommonLayoutParams((CommonLayoutParams) from);
if (from instanceof FrameLayout.LayoutParams) if (from instanceof FrameLayout.LayoutParams)
return new CommonLayoutParams((FrameLayout.LayoutParams)from); return new CommonLayoutParams((FrameLayout.LayoutParams) from);
if (from instanceof ViewGroup.MarginLayoutParams) if (from instanceof ViewGroup.MarginLayoutParams)
return new CommonLayoutParams((ViewGroup.MarginLayoutParams)from); return new CommonLayoutParams((ViewGroup.MarginLayoutParams) from);
return new CommonLayoutParams(from); return new CommonLayoutParams(from);
} }
@Override @Override
public boolean shouldDelayChildPressedState() { public boolean shouldDelayChildPressedState() {
return false; return false;
} }
@Override @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(MotionEvent event) {
@ -73,24 +107,25 @@ public abstract class LayoutBase extends ViewGroup {
} }
// LayoutBase.onTouchEvent(ev) execution means no interactive child view handled // 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 // because passThroughParent is set to true
return false; return false;
} }
protected static int getGravity(View view) { protected static int getGravity(View view) {
int gravity = -1; int gravity = -1;
LayoutParams params = view.getLayoutParams(); LayoutParams params = view.getLayoutParams();
if (params instanceof FrameLayout.LayoutParams) { if (params instanceof FrameLayout.LayoutParams) {
gravity = ((FrameLayout.LayoutParams)params).gravity; gravity = ((FrameLayout.LayoutParams) params).gravity;
} }
if (gravity == -1) { if (gravity == -1) {
gravity = Gravity.FILL; gravity = Gravity.FILL;
} }
return gravity; return gravity;
} }
public boolean getPassThroughParent() { public boolean getPassThroughParent() {
return this.passThroughParent; return this.passThroughParent;
@ -99,4 +134,45 @@ public abstract class LayoutBase extends ViewGroup {
public void setPassThroughParent(boolean value) { public void setPassThroughParent(boolean value) {
this.passThroughParent = 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);
}
}