mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +08:00
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:

committed by
Nathan Walker

parent
4f5f0aae77
commit
1ffc1628d0
@ -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) {
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user