Skip to content

Commit

Permalink
fix text node shadow & gradient conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
jingpeng authored and penfeizhou committed Nov 3, 2022
1 parent 0a305b3 commit 5782a0d
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 86 deletions.
174 changes: 174 additions & 0 deletions doric-android/doric/src/main/java/pub/doric/shader/DoricTextView.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
120 changes: 34 additions & 86 deletions doric-android/doric/src/main/java/pub/doric/shader/TextNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,14 @@
package pub.doric.shader;

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.drawable.Drawable;
import android.text.Html;
import android.text.Layout;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.ViewTreeObserver;
import android.widget.TextView;

import androidx.core.content.res.ResourcesCompat;

Expand Down Expand Up @@ -60,14 +54,14 @@
* @CreateDate: 2019-07-20
*/
@DoricPlugin(name = "Text")
public class TextNode extends ViewNode<TextView> {
public class TextNode extends ViewNode<DoricTextView> {
public TextNode(DoricContext doricContext) {
super(doricContext);
}

@Override
protected TextView build() {
TextView tv = new TextView(getContext());
protected DoricTextView build() {
DoricTextView tv = new DoricTextView(getContext());
tv.setGravity(Gravity.CENTER);
tv.setMaxLines(1);
tv.setSingleLine(true);
Expand All @@ -90,7 +84,7 @@ protected void blendLayoutConfig(JSObject jsObject) {
}

@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) {
case "text":
if (!prop.isString()) {
Expand Down Expand Up @@ -178,7 +172,7 @@ public boolean onPreDraw() {
}
}

setGradientTextColor(view, angle, colors, locations);
mView.setGradient(angle, colors, locations);

return true;
}
Expand Down Expand Up @@ -312,40 +306,40 @@ public void onFinish() {
break;
case "strikethrough":
if (prop.isBoolean()) {
view.getPaint().setStrikeThruText(prop.asBoolean().value());
view.setStrikethrough(prop.asBoolean().value());
}
break;
case "underline":
if (prop.isBoolean()) {
view.getPaint().setUnderlineText(prop.asBoolean().value());
view.setUnderline(prop.asBoolean().value());
}
break;
case "htmlText":
if (prop.isString()) {
getDoricContext().getDriver().asyncCall(new Callable<Spanned>() {
@Override
public Spanned call() {
return HtmlParser.buildSpannedText(prop.asString().value(),
new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
try {
Drawable drawable = Glide.with(view)
.asDrawable()
.load(source)
.submit()
.get();
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
return drawable;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
},
new CustomTagHandler());
}
}, ThreadMode.INDEPENDENT)
@Override
public Spanned call() {
return HtmlParser.buildSpannedText(prop.asString().value(),
new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
try {
Drawable drawable = Glide.with(view)
.asDrawable()
.load(source)
.submit()
.get();
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
return drawable;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
},
new CustomTagHandler());
}
}, ThreadMode.INDEPENDENT)
.setCallback(new AsyncResult.Callback<Spanned>() {
@Override
public void onResult(final Spanned result) {
Expand Down Expand Up @@ -389,8 +383,8 @@ public void onFinish() {
break;
case "shadow":
if (prop.isObject()) {
mView.setAlpha((prop.asObject().getProperty("opacity").asNumber().toFloat()));
mView.setShadowLayer(
mView.setShadow(
prop.asObject().getProperty("opacity").asNumber().toFloat(),
prop.asObject().getProperty("radius").asNumber().toFloat(),
DoricUtils.dp2px(prop.asObject().getProperty("offsetX").asNumber().toFloat()),
DoricUtils.dp2px(prop.asObject().getProperty("offsetY").asNumber().toFloat()),
Expand All @@ -404,57 +398,13 @@ public void onFinish() {
}
}

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(Color.WHITE);
textView.getPaint().setShader(textShader);
textView.invalidate();
}

private static File createFile(byte[] bfile, String filePath,String fileName) throws IOException {
private static File createFile(byte[] bfile, String filePath, String fileName) throws IOException {
BufferedOutputStream bos = null;
FileOutputStream fos = null;
try {
File dir = new File(filePath);
if(!dir.exists()){
if (!dir.exists()) {
dir.mkdirs();
}
String pathName = filePath + File.separator + fileName;
Expand Down Expand Up @@ -496,7 +446,5 @@ protected void reset() {
mView.setMaxLines(1);
mView.setSingleLine(true);
mView.setEllipsize(TextUtils.TruncateAt.END);
mView.getPaint().setStrikeThruText(false);
mView.getPaint().setUnderlineText(false);
}
}
Loading

0 comments on commit 5782a0d

Please sign in to comment.