fix text node shadow & gradient conflict

This commit is contained in:
王劲鹏 2022-11-03 15:00:29 +08:00 committed by osborn
parent 0a305b3104
commit 5782a0d161
3 changed files with 233 additions and 86 deletions

View File

@ -0,0 +1,174 @@
package pub.doric.shader;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.text.Layout;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
@SuppressLint("AppCompatCustomView")
public class DoricTextView extends TextView {
private boolean strikethrough = false;
private boolean underline = false;
private float shadowAlpha = 0;
private float shadowRadius = 0;
private float shadowDx = 0;
private float shadowDy = 0;
private int shadowColor = Color.TRANSPARENT;
private float gradientAngle = 0;
private int[] gradientColors = null;
private float[] gradientPositions = null;
public DoricTextView(Context context) {
super(context);
}
public DoricTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public DoricTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setShadow(float alpha, float radius, float dx, float dy, int color) {
this.shadowAlpha = alpha;
this.shadowRadius = radius;
this.shadowDx = dx;
this.shadowDy = dy;
this.shadowColor = color;
getPaint().setAlpha((int) (255 * alpha));
getPaint().setShadowLayer(radius, dx, dy, color);
}
public boolean hasShadow() {
return this.shadowAlpha > 0;
}
public void setGradient(float angle, int[] colors, float[] positions) {
this.gradientAngle = angle;
this.gradientColors = colors;
this.gradientPositions = positions;
invalidate();
}
public boolean hasGradient() {
return this.gradientColors != null;
}
public void setUnderline(boolean underline) {
this.underline = underline;
getPaint().setUnderlineText(underline);
}
public void setStrikethrough(boolean strikethrough) {
this.strikethrough = strikethrough;
getPaint().setStrikeThruText(strikethrough);
}
@Override
protected void onDraw(Canvas canvas) {
if (hasGradient()) {
if (hasShadow() || strikethrough || underline) {
getPaint().setShader(null);
// draw the shadow
if (hasShadow()) {
// shadowColor must be opaque.
setTextColor(0x00ffffff);
getPaint().setAlpha((int) (255 * shadowAlpha));
getPaint().setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor);
}
getPaint().setStrikeThruText(strikethrough);
getPaint().setUnderlineText(underline);
super.onDraw(canvas);
}
// draw the gradient filled text
if (hasShadow()) {
getPaint().clearShadowLayer();
}
// gradient colors must be opaque, too.
setGradientTextColor(this, this.gradientAngle, this.gradientColors, this.gradientPositions);
super.onDraw(canvas);
} else {
super.onDraw(canvas);
}
}
public static void setGradientTextColor(final TextView textView, final float angle, final int[] colors, final float[] positions) {
final Rect textBound = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
final Layout layout = textView.getLayout();
if (layout == null) {
return;
}
for (int i = 0; i < textView.getLineCount(); i++) {
float left = layout.getLineLeft(i);
float right = layout.getLineRight(i);
if (left < textBound.left) textBound.left = (int) left;
if (right > textBound.right) textBound.right = (int) right;
}
textBound.top = layout.getLineTop(0);
textBound.bottom = layout.getLineBottom(textView.getLineCount() - 1);
if (textView.getIncludeFontPadding()) {
Paint.FontMetrics fontMetrics = textView.getPaint().getFontMetrics();
textBound.top += (fontMetrics.ascent - fontMetrics.top);
textBound.bottom -= (fontMetrics.bottom - fontMetrics.descent);
}
double angleInRadians = Math.toRadians(angle);
double r = Math.sqrt(Math.pow(textBound.bottom - textBound.top, 2) +
Math.pow(textBound.right - textBound.left, 2)) / 2;
float centerX = textBound.left + (textBound.right - textBound.left) / 2.f;
float centerY = textBound.top + (textBound.bottom - textBound.top) / 2.f;
float startX = (float) (centerX - r * Math.cos(angleInRadians));
float startY = (float) (centerY + r * Math.sin(angleInRadians));
float endX = (float) (centerX + r * Math.cos(angleInRadians));
float endY = (float) (centerY - r * Math.sin(angleInRadians));
Shader textShader = new LinearGradient(startX, startY, endX, endY, colors, positions,
Shader.TileMode.CLAMP);
textView.setTextColor(0xffffffff);
textView.getPaint().setShader(textShader);
}
public void reset() {
strikethrough = false;
underline = false;
shadowAlpha = 0;
shadowRadius = 0;
shadowDx = 0;
shadowDy = 0;
shadowColor = Color.TRANSPARENT;
gradientAngle = 0;
gradientColors = null;
gradientPositions = null;
invalidate();
}
}

View File

@ -16,20 +16,14 @@
package pub.doric.shader; package pub.doric.shader;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.Html; import android.text.Html;
import android.text.Layout;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.TextView;
import androidx.core.content.res.ResourcesCompat; import androidx.core.content.res.ResourcesCompat;
@ -60,14 +54,14 @@ import pub.doric.utils.ThreadMode;
* @CreateDate: 2019-07-20 * @CreateDate: 2019-07-20
*/ */
@DoricPlugin(name = "Text") @DoricPlugin(name = "Text")
public class TextNode extends ViewNode<TextView> { public class TextNode extends ViewNode<DoricTextView> {
public TextNode(DoricContext doricContext) { public TextNode(DoricContext doricContext) {
super(doricContext); super(doricContext);
} }
@Override @Override
protected TextView build() { protected DoricTextView build() {
TextView tv = new TextView(getContext()); DoricTextView tv = new DoricTextView(getContext());
tv.setGravity(Gravity.CENTER); tv.setGravity(Gravity.CENTER);
tv.setMaxLines(1); tv.setMaxLines(1);
tv.setSingleLine(true); tv.setSingleLine(true);
@ -90,7 +84,7 @@ public class TextNode extends ViewNode<TextView> {
} }
@Override @Override
protected void blend(final TextView view, final String name, final JSValue prop) { protected void blend(final DoricTextView view, final String name, final JSValue prop) {
switch (name) { switch (name) {
case "text": case "text":
if (!prop.isString()) { if (!prop.isString()) {
@ -178,7 +172,7 @@ public class TextNode extends ViewNode<TextView> {
} }
} }
setGradientTextColor(view, angle, colors, locations); mView.setGradient(angle, colors, locations);
return true; return true;
} }
@ -312,40 +306,40 @@ public class TextNode extends ViewNode<TextView> {
break; break;
case "strikethrough": case "strikethrough":
if (prop.isBoolean()) { if (prop.isBoolean()) {
view.getPaint().setStrikeThruText(prop.asBoolean().value()); view.setStrikethrough(prop.asBoolean().value());
} }
break; break;
case "underline": case "underline":
if (prop.isBoolean()) { if (prop.isBoolean()) {
view.getPaint().setUnderlineText(prop.asBoolean().value()); view.setUnderline(prop.asBoolean().value());
} }
break; break;
case "htmlText": case "htmlText":
if (prop.isString()) { if (prop.isString()) {
getDoricContext().getDriver().asyncCall(new Callable<Spanned>() { getDoricContext().getDriver().asyncCall(new Callable<Spanned>() {
@Override @Override
public Spanned call() { public Spanned call() {
return HtmlParser.buildSpannedText(prop.asString().value(), return HtmlParser.buildSpannedText(prop.asString().value(),
new Html.ImageGetter() { new Html.ImageGetter() {
@Override @Override
public Drawable getDrawable(String source) { public Drawable getDrawable(String source) {
try { try {
Drawable drawable = Glide.with(view) Drawable drawable = Glide.with(view)
.asDrawable() .asDrawable()
.load(source) .load(source)
.submit() .submit()
.get(); .get();
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
return drawable; return drawable;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
}, },
new CustomTagHandler()); new CustomTagHandler());
} }
}, ThreadMode.INDEPENDENT) }, ThreadMode.INDEPENDENT)
.setCallback(new AsyncResult.Callback<Spanned>() { .setCallback(new AsyncResult.Callback<Spanned>() {
@Override @Override
public void onResult(final Spanned result) { public void onResult(final Spanned result) {
@ -389,8 +383,8 @@ public class TextNode extends ViewNode<TextView> {
break; break;
case "shadow": case "shadow":
if (prop.isObject()) { if (prop.isObject()) {
mView.setAlpha((prop.asObject().getProperty("opacity").asNumber().toFloat())); mView.setShadow(
mView.setShadowLayer( prop.asObject().getProperty("opacity").asNumber().toFloat(),
prop.asObject().getProperty("radius").asNumber().toFloat(), prop.asObject().getProperty("radius").asNumber().toFloat(),
DoricUtils.dp2px(prop.asObject().getProperty("offsetX").asNumber().toFloat()), DoricUtils.dp2px(prop.asObject().getProperty("offsetX").asNumber().toFloat()),
DoricUtils.dp2px(prop.asObject().getProperty("offsetY").asNumber().toFloat()), DoricUtils.dp2px(prop.asObject().getProperty("offsetY").asNumber().toFloat()),
@ -404,57 +398,13 @@ public class TextNode extends ViewNode<TextView> {
} }
} }
public static void setGradientTextColor(final TextView textView, final float angle, final int[] colors, final float[] positions) {
final Rect textBound = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
final Layout layout = textView.getLayout(); private static File createFile(byte[] bfile, String filePath, String fileName) throws IOException {
if (layout == null) {
return;
}
for (int i = 0; i < textView.getLineCount(); i++) {
float left = layout.getLineLeft(i);
float right = layout.getLineRight(i);
if (left < textBound.left) textBound.left = (int) left;
if (right > textBound.right) textBound.right = (int) right;
}
textBound.top = layout.getLineTop(0);
textBound.bottom = layout.getLineBottom(textView.getLineCount() - 1);
if (textView.getIncludeFontPadding()) {
Paint.FontMetrics fontMetrics = textView.getPaint().getFontMetrics();
textBound.top += (fontMetrics.ascent - fontMetrics.top);
textBound.bottom -= (fontMetrics.bottom - fontMetrics.descent);
}
double angleInRadians = Math.toRadians(angle);
double r = Math.sqrt(Math.pow(textBound.bottom - textBound.top, 2) +
Math.pow(textBound.right - textBound.left, 2)) / 2;
float centerX = textBound.left + (textBound.right - textBound.left) / 2.f;
float centerY = textBound.top + (textBound.bottom - textBound.top) / 2.f;
float startX = (float) (centerX - r * Math.cos(angleInRadians));
float startY = (float) (centerY + r * Math.sin(angleInRadians));
float endX = (float) (centerX + r * Math.cos(angleInRadians));
float endY = (float) (centerY - r * Math.sin(angleInRadians));
Shader textShader = new LinearGradient(startX, startY, endX, endY, colors, positions,
Shader.TileMode.CLAMP);
textView.setTextColor(Color.WHITE);
textView.getPaint().setShader(textShader);
textView.invalidate();
}
private static File createFile(byte[] bfile, String filePath,String fileName) throws IOException {
BufferedOutputStream bos = null; BufferedOutputStream bos = null;
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
File dir = new File(filePath); File dir = new File(filePath);
if(!dir.exists()){ if (!dir.exists()) {
dir.mkdirs(); dir.mkdirs();
} }
String pathName = filePath + File.separator + fileName; String pathName = filePath + File.separator + fileName;
@ -496,7 +446,5 @@ public class TextNode extends ViewNode<TextView> {
mView.setMaxLines(1); mView.setMaxLines(1);
mView.setSingleLine(true); mView.setSingleLine(true);
mView.setEllipsize(TextUtils.TruncateAt.END); mView.setEllipsize(TextUtils.TruncateAt.END);
mView.getPaint().setStrikeThruText(false);
mView.getPaint().setUnderlineText(false);
} }
} }

View File

@ -7,6 +7,31 @@ class TextDemo extends Panel {
vlayout( vlayout(
[ [
title("Text Demo"), title("Text Demo"),
text({
text: '十万钻石场',
textSize: 60,
font: 'PingFangSC-Medium',
textAlignment: Gravity.Center,
shadow: {
color: Color.parse('#640000'),
opacity: 0.49,
radius: 0.5,
offsetX: 0,
offsetY: 1,
},
textColor: {
colors: [Color.parse('#FFFFFF'), Color.parse('#FFE6A5'), Color.parse('#FFE39B'), Color.parse('#FFF0CE')],
locations: [0.0, 0.7, 0.9, 1.0],
orientation: GradientOrientation.TOP_BOTTOM,
}
}).apply({
layoutConfig: {
widthSpec: LayoutSpec.FIT,
heightSpec: LayoutSpec.FIT,
margin: { top: 131 },
alignment: Gravity.CenterX,
}
}),
input({ input({
width: 100, width: 100,
height: 100, height: 100,