mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-19 14:20:22 +08:00
fix: android support for clipToBounds
It comes with a major refactoring of the BorderDrawable. Now all Paths are cached not to regenerate them on every draw pass. Also it is important to understand that the clipping feature on android comes at a cost. It will be done only when truelly necessary. but with complex paths ((border radius > 0 && border non uniform) || border-width >0) then we must apply a costly drawing pass (though hardware accelerated) to accomplish it.
This commit is contained in:
@ -9,18 +9,8 @@ export class LayoutBase extends LayoutBaseCommon {
|
||||
return true;
|
||||
}
|
||||
[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) {
|
||||
|
@ -51,7 +51,12 @@ def computeTargetSdkVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
compileSdkVersion computeCompileSdkVersion()
|
||||
buildToolsVersion computeBuildToolsVersion()
|
||||
|
||||
|
@ -18,13 +18,20 @@ 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.
|
||||
*/
|
||||
@ -49,6 +56,20 @@ 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;
|
||||
@ -141,6 +162,65 @@ 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<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() {
|
||||
return backgroundColor;
|
||||
}
|
||||
@ -153,7 +233,9 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
|
||||
return backgroundBitmap;
|
||||
}
|
||||
|
||||
public LinearGradientDefinition getBackgroundGradient() { return backgroundGradient; }
|
||||
public LinearGradientDefinition getBackgroundGradient() {
|
||||
return backgroundGradient;
|
||||
}
|
||||
|
||||
public String getBackgroundRepeat() {
|
||||
return backgroundRepeat;
|
||||
@ -168,34 +250,33 @@ 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;
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean hasUniformBorder() {
|
||||
return this.hasUniformBorderColor() &&
|
||||
this.hasUniformBorderWidth() &&
|
||||
this.hasUniformBorderRadius();
|
||||
return this.hasUniformBorderColor() && this.hasUniformBorderWidth() && this.hasUniformBorderRadius();
|
||||
}
|
||||
|
||||
public BorderDrawable(float density) {
|
||||
@ -209,33 +290,19 @@ 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;
|
||||
@ -254,6 +321,9 @@ 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;
|
||||
@ -273,39 +343,67 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
Rect bounds = this.getBounds();
|
||||
float width = (float)bounds.width();
|
||||
float height = (float)bounds.height();
|
||||
private void generateBackgroundPath(Rect bounds) {
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
// When the view is off-screen the bounds might be empty and we don't have anything to draw.
|
||||
if (backgroundPath != null && hasCache("backgroundPath")) {
|
||||
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 rightBackoffAntialias = calculateBackoffAntialias(this.borderRightColor, this.borderRightWidth);
|
||||
float bottomBackoffAntialias = calculateBackoffAntialias(this.borderBottomColor, this.borderBottomWidth);
|
||||
float leftBackoffAntialias = calculateBackoffAntialias(this.borderLeftColor, this.borderLeftWidth);
|
||||
|
||||
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)
|
||||
};
|
||||
float width = (float) bounds.width();
|
||||
float height = (float) bounds.height();
|
||||
|
||||
Path backgroundPath = new Path();
|
||||
RectF backgroundRect = new RectF(
|
||||
leftBackoffAntialias,
|
||||
topBackoffAntialias,
|
||||
width - rightBackoffAntialias,
|
||||
height - bottomBackoffAntialias
|
||||
);
|
||||
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) {
|
||||
@ -317,6 +415,7 @@ 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);
|
||||
}
|
||||
}
|
||||
@ -335,11 +434,9 @@ 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);
|
||||
@ -354,13 +451,16 @@ 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);
|
||||
@ -372,10 +472,9 @@ 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);
|
||||
@ -391,7 +490,10 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
|
||||
if (this.clipPath != null && !this.clipPath.isEmpty()) {
|
||||
float borderWidth = this.getUniformBorderWidth();
|
||||
if (borderWidth > 0) {
|
||||
Paint borderPaint = new Paint();
|
||||
if (borderPaint == null) {
|
||||
borderPaint = new Paint();
|
||||
borderPaint.setAntiAlias(true);
|
||||
}
|
||||
borderPaint.setColor(this.getUniformBorderColor());
|
||||
borderPaint.setStyle(Paint.Style.STROKE);
|
||||
borderPaint.setStrokeWidth(borderWidth);
|
||||
@ -402,36 +504,15 @@ 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) {
|
||||
Paint borderPaint = new Paint();
|
||||
borderPaint.setColor(this.getUniformBorderColor());
|
||||
if (borderPaint == null) {
|
||||
borderPaint = new Paint();
|
||||
borderPaint.setAntiAlias(true);
|
||||
}
|
||||
borderPaint.setStyle(Paint.Style.FILL);
|
||||
borderPaint.setAntiAlias(true);
|
||||
Path borderPath = new Path();
|
||||
borderPaint.setColor(this.getUniformBorderColor());
|
||||
|
||||
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);
|
||||
generateUniformedColorBorderPath(bounds);
|
||||
canvas.drawPath(unifiedColorBorderPath, borderPaint);
|
||||
}
|
||||
} else {
|
||||
float top = this.borderTopWidth;
|
||||
@ -439,16 +520,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
|
||||
@ -463,66 +544,109 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
|
||||
PointF lbi = new PointF(left, bounds.bottom - bottom); // left-bottom-inside
|
||||
|
||||
if (this.borderTopWidth > 0) {
|
||||
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 (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);
|
||||
}
|
||||
|
||||
if (this.borderRightWidth > 0) {
|
||||
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 (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);
|
||||
}
|
||||
|
||||
if (this.borderBottomWidth > 0) {
|
||||
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 (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);
|
||||
}
|
||||
|
||||
if (this.borderLeftWidth > 0) {
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -536,11 +660,22 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
|
||||
private static Pattern spaceAndComma = Pattern.compile("[\\s,]+");
|
||||
private static Pattern space = Pattern.compile("\\s+");
|
||||
|
||||
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%);
|
||||
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%);
|
||||
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;
|
||||
@ -554,8 +689,7 @@ 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);
|
||||
|
||||
canvas.drawRect(left, top, right, bottom, paint);
|
||||
clipPathPath.addRect(new RectF(left, top, right, bottom), Path.Direction.CW);
|
||||
break;
|
||||
case "inset":
|
||||
arr = spaceAndComma.split(value);
|
||||
@ -580,18 +714,21 @@ 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);
|
||||
|
||||
canvas.drawRect(left, top, right, bottom, paint);
|
||||
clipPathPath.addRect(new RectF(left, top, right, bottom), Path.Direction.CW);
|
||||
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);
|
||||
canvas.drawCircle(x, y, radius, paint);
|
||||
clipPathPath.addCircle(x, y, radius, Path.Direction.CW);
|
||||
break;
|
||||
case "ellipse":
|
||||
arr = space.split(value);
|
||||
@ -603,31 +740,35 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
|
||||
top = cY - rY;
|
||||
right = (rX * 2) + left;
|
||||
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;
|
||||
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;
|
||||
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) {
|
||||
path.lineTo(firstPoint.x, firstPoint.y);
|
||||
clipPathPath.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();
|
||||
|
||||
@ -663,15 +804,18 @@ 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())) {
|
||||
@ -702,8 +846,10 @@ 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())) {
|
||||
@ -751,14 +897,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 };
|
||||
}
|
||||
}
|
||||
|
||||
@ -781,29 +927,22 @@ 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
|
||||
@ -823,18 +962,72 @@ 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) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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 sizeY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,17 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
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;
|
||||
@ -11,32 +19,58 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,21 +84,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) {
|
||||
@ -73,24 +107,25 @@ 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;
|
||||
@ -99,4 +134,45 @@ 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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user