From 22607ed3d87c7f011c235a8513af067b78763f7a Mon Sep 17 00:00:00 2001 From: iPel Date: Tue, 22 Aug 2023 17:19:08 +0800 Subject: [PATCH] feat(android): nested rich text support vertical alignment setting (#3402) * feat(android): nested rich text support vertical alignment setting * feat(android): revert legacy image verticalAlignment * fix(android): postInvalidateDelayed not working --------- Co-authored-by: siguangli --- .../tencent/mtt/hippy/dom/node/NodeProps.java | 2 + .../hippy/uimanager/ControllerManager.java | 7 - .../uimanager/ControllerUpdateManger.java | 4 +- .../mtt/hippy/uimanager/RenderManager.java | 5 +- .../component/text/LegacyIAlignConfig.java | 262 ++++++++++ .../component/text/TextImageSpan.java | 476 ++++++++---------- .../component/text/TextVerticalAlignSpan.java | 63 +++ .../renderer/node/ImageVirtualNode.java | 176 ++++++- .../renderer/node/TextVirtualNode.java | 85 +++- .../tencent/renderer/node/VirtualNode.java | 9 + 10 files changed, 815 insertions(+), 274 deletions(-) create mode 100644 renderer/native/android/src/main/java/com/tencent/renderer/component/text/LegacyIAlignConfig.java create mode 100644 renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextVerticalAlignSpan.java diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java index 47d4dde3039..a09c38617ff 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/dom/node/NodeProps.java @@ -139,7 +139,9 @@ public class NodeProps { public static final String IMAGE_CLASS_NAME = "Image"; public static final String TEXT_INPUT_CLASS_NAME = "TextInput"; public static final String IMAGE_SPAN_TEXT = "[img]"; + @Deprecated public static final String PROP_VERTICAL_ALIGNMENT = "verticalAlignment"; + public static final String VERTICAL_ALIGN = "verticalAlign"; public static final String PROPS = "props"; public static final String ROOT_NODE = "RootNode"; diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java index 0aae540736f..d9dc588bea6 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java @@ -422,13 +422,6 @@ public void replaceId(int rootId, @NonNull View view, int newId, boolean shouldR mControllerRegistry.addView(view, rootId, newId); } - public void postInvalidateDelayed(int rootId, int id, long delayMilliseconds) { - View view = mControllerRegistry.getView(rootId, id); - if (view != null) { - view.postInvalidateDelayed(delayMilliseconds); - } - } - @Nullable public RenderNode createRenderNode(int rootId, int id, @Nullable Map props, String className, boolean isLazy) { diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerUpdateManger.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerUpdateManger.java index 6f503828784..3eb4e306e4d 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerUpdateManger.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerUpdateManger.java @@ -126,7 +126,9 @@ private static void initComponentPropsMap() { HippyControllerProps controllerProps = method .getAnnotation(HippyControllerProps.class); if (controllerProps != null) { - sTextPropsSet.add(controllerProps.name()); + if (!sComponentPropsMethodMap.containsKey(controllerProps.name())) { + sTextPropsSet.add(controllerProps.name()); + } sRenderPropsList.add(controllerProps.name()); } } diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java index 0bf8eb11ed4..4dd6a72116f 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java @@ -424,6 +424,9 @@ public boolean checkRegisteredEvent(int rootId, int nodeId, @NonNull String even } public void postInvalidateDelayed(int rootId, int id, long delayMilliseconds) { - mControllerManager.postInvalidateDelayed(rootId, id, delayMilliseconds); + RenderNode node = getRenderNode(rootId, id); + if (node != null) { + node.postInvalidateDelayed(delayMilliseconds); + } } } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/LegacyIAlignConfig.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/LegacyIAlignConfig.java new file mode 100644 index 00000000000..70d8da5e74b --- /dev/null +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/LegacyIAlignConfig.java @@ -0,0 +1,262 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.renderer.component.text; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.FontMetricsInt; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +@Deprecated +interface LegacyIAlignConfig { + + int ALIGN_BOTTOM = 0; + int ALIGN_BASELINE = 1; + int ALIGN_CENTER = 2; + int ALIGN_TOP = 3; + + static LegacyIAlignConfig fromVerticalAlignment(int verticalAlignment) { + switch (verticalAlignment) { + case ALIGN_BASELINE: + return new AlignBaselineConfig(); + case ALIGN_CENTER: + return new AlignCenterConfig(); + case ALIGN_TOP: + return new AlignTopConfig(); + case ALIGN_BOTTOM: + default: + return new AlignBottomConfig(); + } + } + + void setDesiredSize(int desiredDrawableWidth, int desiredDrawableHeight); + + void setActiveSizeWithRate(float heightRate); + + void setMargin(int marginLeft, int marginRight); + + int getSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, Drawable drawable); + + void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float baseLineX, + int lineTop, int baselineY, int lintBottom, @NonNull Paint paint, + Drawable drawable, @Nullable Paint backgroundPaint); + + abstract class BaseAlignConfig implements LegacyIAlignConfig { + + private int mDesiredDrawableWidth; + private int mDesiredDrawableHeight; + private float mHeightRate; + private final int[] mSize = new int[2]; + private int mMarginLeft; + private int mMarginRight; + + @Override + public void setDesiredSize(int desiredDrawableWidth, int desiredDrawableHeight) { + mDesiredDrawableWidth = desiredDrawableWidth; + mDesiredDrawableHeight = desiredDrawableHeight; + mHeightRate = 0; + } + + @Override + public void setActiveSizeWithRate(float heightRate) { + mHeightRate = heightRate; + mDesiredDrawableWidth = 0; + mDesiredDrawableHeight = 0; + } + + @Override + public void setMargin(int marginLeft, int marginRight) { + mMarginLeft = marginLeft; + mMarginRight = marginRight; + } + + private void calDrawableSize(Rect drawableBounds, Paint paint) { + int dWidth; + int dHeight; + if (mHeightRate > 0) { + int textSize = (int) paint.getTextSize(); + dHeight = (int) (textSize * mHeightRate); + dWidth = drawableBounds.right * dHeight / drawableBounds.bottom; + } else { + dHeight = mDesiredDrawableHeight; + dWidth = mDesiredDrawableWidth; + } + if (dWidth <= 0 || dHeight <= 0) { + dWidth = drawableBounds.right; + dHeight = drawableBounds.bottom; + } + mSize[0] = dWidth; + mSize[1] = dHeight; + } + + @Override + public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, Drawable drawable) { + calDrawableSize(drawable.getBounds(), paint); + int dWidth = mSize[0]; + int dHeight = mSize[1]; + int deltaTop = 0; + int deltaBottom = 0; + if (fm != null) { + deltaTop = fm.top - fm.ascent; + deltaBottom = fm.bottom - fm.descent; + } + int size = getCustomSize(paint, + text, start, end, + fm, dWidth, dHeight); + if (fm != null) { + fm.top = fm.ascent + deltaTop; + fm.bottom = fm.descent + deltaBottom; + } + return mMarginLeft + size + mMarginRight; + } + + @Override + public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, + Drawable drawable, @Nullable Paint backgroundPaint) { + Rect drawableBounds = drawable.getBounds(); + int dWidth = mSize[0]; + int dHeight = mSize[1]; + FontMetricsInt fontMetricsInt = paint.getFontMetricsInt(); + int transY = getTransY(canvas, text, start, end, baseLineX, lineTop, baselineY, + lineBottom, paint, fontMetricsInt, dWidth, dHeight); + transY = adjustTransY(transY, lineTop, lineBottom, dHeight); + float scaleX = (float) dWidth / drawableBounds.right; + float scaleY = (float) dHeight / drawableBounds.bottom; + canvas.save(); + canvas.translate(baseLineX + mMarginLeft, transY); + canvas.scale(scaleX, scaleY); + if (backgroundPaint != null) { + canvas.drawRect(0, 0, dWidth, dHeight, backgroundPaint); + } + drawable.draw(canvas); + canvas.restore(); + } + + private static int adjustTransY(int transY, int lineTop, int lineBottom, + int drawableHeight) { + if (drawableHeight + transY > lineBottom) { + transY = lineBottom - drawableHeight; + } + if (transY < lineTop) { + transY = lineTop; + } + return transY; + } + + abstract int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight); + + abstract int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, + FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight); + } + + class AlignBaselineConfig extends BaseAlignConfig { + + @Override + public int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight) { + if (fm != null) { + fm.ascent = -drawableHeight; + } + return drawableWidth; + } + + @Override + public int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, + FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight) { + return baselineY - drawableHeight; + } + } + + class AlignBottomConfig extends AlignBaselineConfig { + + @Override + public int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight) { + if (fm != null) { + fm.ascent = fm.descent - drawableHeight; + } + return drawableWidth; + } + + @Override + public int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, + FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight) { + return super + .getTransY(canvas, text, start, end, baseLineX, lineTop, baselineY, lineBottom, + paint, fontMetricsInt, drawableWidth, drawableHeight) + + fontMetricsInt.descent; + } + } + + class AlignCenterConfig extends AlignBottomConfig { + + @Override + public int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight) { + if (fm != null) { + int textAreaHeight = fm.descent - fm.ascent; + if (textAreaHeight < drawableHeight) { + int oldSumOfAscentAndDescent = fm.ascent + fm.descent; + fm.ascent = oldSumOfAscentAndDescent - drawableHeight >> 1; + fm.descent = oldSumOfAscentAndDescent + drawableHeight >> 1; + } + + } + return drawableWidth; + } + + @Override + public int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, + FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight) { + int transY = super + .getTransY(canvas, text, start, end, baseLineX, lineTop, baselineY, lineBottom, + paint, fontMetricsInt, drawableWidth, drawableHeight); + int fontHeight = fontMetricsInt.descent - fontMetricsInt.ascent; + transY = transY - (fontHeight >> 1) + (drawableHeight >> 1); + return transY; + } + } + + class AlignTopConfig extends BaseAlignConfig { + + @Override + public int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight) { + if (fm != null) { + fm.descent = drawableHeight + fm.ascent; + } + return drawableWidth; + } + + @Override + public int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, + float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, + FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight) { + return baselineY + fontMetricsInt.ascent; + } + } +} diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextImageSpan.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextImageSpan.java index e19901faa9c..eece831774e 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextImageSpan.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextImageSpan.java @@ -23,60 +23,78 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; import android.graphics.Movie; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.SystemClock; import android.text.TextUtils; import android.text.style.DynamicDrawableSpan; import android.text.style.ImageSpan; - import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.tencent.mtt.hippy.utils.ContextHolder; - +import com.tencent.mtt.hippy.utils.UIThreadUtils; import com.tencent.renderer.NativeRender; import com.tencent.renderer.component.image.ImageDataHolder; import com.tencent.renderer.component.image.ImageDataSupplier; import com.tencent.renderer.component.image.ImageLoaderAdapter; import com.tencent.renderer.component.image.ImageRequestListener; import com.tencent.renderer.node.ImageVirtualNode; - +import com.tencent.renderer.node.TextVirtualNode; import com.tencent.renderer.utils.EventUtils.EventType; - import com.tencent.vfs.UrlUtils; import java.lang.ref.WeakReference; import java.lang.reflect.Field; public class TextImageSpan extends ImageSpan { - private static final int ALIGN_BOTTOM = 0; - private static final int ALIGN_BASELINE = 1; - private static final int ALIGN_CENTER = 2; - private static final int ALIGN_TOP = 3; private static final int STATE_UNLOAD = 0; private static final int STATE_LOADING = 1; private static final int STATE_LOADED = 2; + private final boolean mUseLegacy; + @Nullable + private final String mVerticalAlign; private final int mRootId; private final int mId; private final int mAncestorId; private final int mWidth; private final int mHeight; + @Deprecated private final int mLeft; + @Deprecated private final int mTop; + private final int mMarginLeft; + private final int mMarginTop; + private final int mMarginRight; + private final int mMarginBottom; + private int mMeasuredWidth; + private int mMeasuredHeight; private int mImageLoadState = STATE_UNLOAD; private long mGifProgress; private long mGifLastPlayTime = -1; @NonNull private final WeakReference mNativeRendererRef; @Nullable + private Drawable mSrcDrawable; + @Nullable private Movie mGifMovie; - @NonNull - private IAlignConfig mAlignConfig; + @Deprecated + @Nullable + private LegacyIAlignConfig mAlignConfig; + @Nullable + private Paint mGifPaint; + private float mHeightRate = 0; + @Nullable + private Paint mBackgroundPaint = null; + private int mTintColor; public TextImageSpan(Drawable drawable, String source, @NonNull ImageVirtualNode node, @NonNull NativeRender nativeRenderer) { @@ -89,12 +107,20 @@ public TextImageSpan(Drawable drawable, String source, @NonNull ImageVirtualNode mHeight = node.getHeight(); mLeft = node.getLeft(); mTop = node.getTop(); + mMarginLeft = node.getMarginLeft(); + mMarginRight = node.getMarginRight(); + mMarginTop = node.getMarginTop(); + mMarginBottom = node.getMarginBottom(); + mVerticalAlign = node.getVerticalAlign(); + mUseLegacy = mVerticalAlign == null; + mAlignConfig = mUseLegacy ? LegacyIAlignConfig.fromVerticalAlignment(node.getVerticalAlignment()) : null; + mTintColor = node.getTintColor(); + int backgroundColor = node.getBackgroundColor(); + if (backgroundColor != Color.TRANSPARENT) { + mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBackgroundPaint.setColor(node.getBackgroundColor()); + } setUrl(source); - mAlignConfig = createAlignConfig(node.getVerticalAlignment()); - } - - public void setVerticalAlignment(int verticalAlignment) { - mAlignConfig = createAlignConfig(verticalAlignment); } public void setUrl(@Nullable final String url) { @@ -106,6 +132,31 @@ public void setUrl(@Nullable final String url) { @Override public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable FontMetricsInt fm) { + if (mUseLegacy) { + return legacyGetSize(paint, text, start, end, fm); + } + if (mHeightRate > 0) { + if (mHeight == 0) { + mMeasuredWidth = mMeasuredHeight = 0; + } else { + int textSize = (int) paint.getTextSize(); + mMeasuredHeight = (int) (textSize * mHeightRate); + mMeasuredWidth = mWidth * mMeasuredHeight / mHeight; + } + } else { + mMeasuredWidth = mWidth; + mMeasuredHeight = mHeight; + } + if (fm != null) { + fm.top = fm.ascent = -(mMeasuredHeight + mMarginTop + mMarginBottom); + fm.leading = fm.bottom = fm.descent = 0; + } + + return mMeasuredWidth + mMarginLeft + mMarginRight; + } + + private int legacyGetSize(@NonNull Paint paint, CharSequence text, int start, int end, + @Nullable FontMetricsInt fm) { if (mGifMovie != null) { return super.getSize(paint, text, start, end, fm); } @@ -113,57 +164,93 @@ public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, return mAlignConfig.getSize(paint, text, start, end, fm, drawable); } - @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { + if (mUseLegacy) { + legacyDraw(canvas, text, start, end, x, top, y, bottom, paint); + return; + } + if (mMeasuredWidth == 0 || mMeasuredHeight == 0) { + return; + } + canvas.save(); + int transY; + assert mVerticalAlign != null; + switch (mVerticalAlign) { + case TextVirtualNode.V_ALIGN_TOP: + transY = top + mMarginTop; + break; + case TextVirtualNode.V_ALIGN_MIDDLE: + transY = top + (bottom - top) / 2 - mMeasuredHeight / 2; + break; + case TextVirtualNode.V_ALIGN_BOTTOM: + transY = bottom - mMeasuredHeight - mMarginBottom; + break; + case TextVirtualNode.V_ALIGN_BASELINE: + default: + transY = y - mMeasuredHeight - mMarginBottom; + break; + } + + canvas.translate(x + mMarginLeft, transY); + if (mBackgroundPaint != null) { + canvas.drawRect(0, 0, mMeasuredWidth, mMeasuredHeight, mBackgroundPaint); + } + if (mGifMovie != null) { + updateGifTime(); + float scaleX = mMeasuredWidth / (float) mGifMovie.width(); + float scaleY = mMeasuredHeight / (float) mGifMovie.height(); + canvas.scale(scaleX, scaleY, 0, 0); + mGifMovie.draw(canvas, 0, 0, mGifPaint); + postInvalidateDelayed(40); + } else { + Drawable drawable = mSrcDrawable == null ? super.getDrawable() : mSrcDrawable; + Rect rect = drawable.getBounds(); + float scaleX = mMeasuredWidth / (float) rect.right; + float scaleY = mMeasuredHeight / (float) rect.bottom; + canvas.scale(scaleX, scaleY, 0, 0); + drawable.draw(canvas); + } + canvas.restore(); + } + + private void legacyDraw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, + int bottom, Paint paint) { int transY; Paint.FontMetricsInt fm = paint.getFontMetricsInt(); if (mGifMovie != null) { int width = (mWidth == 0) ? mGifMovie.width() : mWidth; int height = (mHeight == 0) ? mGifMovie.height() : mHeight; transY = (y + fm.descent + y + fm.ascent) / 2 - height / 2; - drawGIF(canvas, x + mLeft, transY + mTop, width, height); + legacyDrawGIF(canvas, x + mLeft, transY + mTop, width, height); } else { Drawable drawable = getDrawable(); - mAlignConfig.draw(canvas, text, start, end, x, top, y, bottom, paint, drawable); - } - } - - @NonNull - private IAlignConfig createAlignConfig(int verticalAlignment) { - IAlignConfig alignConfig; - switch (verticalAlignment) { - case ALIGN_BASELINE: - alignConfig = new AlignBaselineConfig(); - break; - case ALIGN_CENTER: - alignConfig = new AlignCenterConfig(); - break; - case ALIGN_TOP: - alignConfig = new AlignTopConfig(); - break; - case ALIGN_BOTTOM: - // fall through - default: - alignConfig = new AlignBottomConfig(); - break; + mAlignConfig.draw(canvas, text, start, end, x, top, y, bottom, paint, drawable, mBackgroundPaint); } - return alignConfig; } + @Deprecated @SuppressWarnings("unused") public void setDesiredSize(int width, int height) { - mAlignConfig.setDesiredSize(width, height); + if (mUseLegacy) { + mAlignConfig.setDesiredSize(width, height); + } } @SuppressWarnings("unused") public void setActiveSizeWithRate(float heightRate) { - mAlignConfig.setActiveSizeWithRate(heightRate); + mHeightRate = heightRate; + if (mUseLegacy) { + mAlignConfig.setActiveSizeWithRate(heightRate); + } } + @Deprecated @SuppressWarnings("unused") public void setMargin(int marginLeft, int marginRight) { - mAlignConfig.setMargin(marginLeft, marginRight); + if (mUseLegacy) { + mAlignConfig.setMargin(marginLeft, marginRight); + } } protected boolean shouldUseFetchImageMode(String url) { @@ -199,7 +286,7 @@ public void onRequestFail(Throwable throwable) { }, null, mWidth, mHeight); } - private void drawGIF(Canvas canvas, float left, float top, int width, int height) { + private void updateGifTime() { if (mGifMovie == null) { return; } @@ -207,7 +294,9 @@ private void drawGIF(Canvas canvas, float left, float top, int width, int height if (duration == 0) { duration = 1000; } - long now = System.currentTimeMillis(); + + long now = SystemClock.elapsedRealtime(); + if (mGifLastPlayTime != -1) { mGifProgress += now - mGifLastPlayTime; if (mGifProgress > duration) { @@ -215,6 +304,19 @@ private void drawGIF(Canvas canvas, float left, float top, int width, int height } } mGifLastPlayTime = now; + + int progress = mGifProgress > Integer.MAX_VALUE ? 0 : (int) mGifProgress; + mGifMovie.setTime(progress); + } + + private void legacyDrawGIF(Canvas canvas, float left, float top, int width, int height) { + if (mGifMovie == null) { + return; + } + updateGifTime(); + if (mBackgroundPaint != null) { + canvas.drawRect(left, top, left + width, top + height, mBackgroundPaint); + } final float mGifScaleX = width / (float) mGifMovie.width(); final float mGifScaleY = height / (float) mGifMovie.height(); final float x = (mGifScaleX != 0) ? left / mGifScaleX : left; @@ -223,7 +325,7 @@ private void drawGIF(Canvas canvas, float left, float top, int width, int height mGifMovie.setTime(progress); canvas.save(); canvas.scale(mGifScaleX, mGifScaleY); - mGifMovie.draw(canvas, x, y); + mGifMovie.draw(canvas, x, y, mGifPaint); canvas.restore(); postInvalidateDelayed(40); } @@ -236,14 +338,47 @@ private void postInvalidateDelayed(long delayMilliseconds) { } } - @SuppressLint("DiscouragedPrivateApi") private void shouldReplaceDrawable(@NonNull ImageDataHolder imageHolder) { + if (mUseLegacy) { + legacyShouldReplaceDrawable(imageHolder); + return; + } + mSrcDrawable = null; + mGifMovie = null; + mGifPaint = null; + Drawable imageDrawable = imageHolder.getDrawable(); + Bitmap bitmap = imageHolder.getBitmap(); + if (imageDrawable != null || bitmap != null) { + Resources resources = ContextHolder.getAppContext().getResources(); + Drawable drawable = imageDrawable != null ? imageDrawable : + new BitmapDrawable(resources, bitmap); + if (mTintColor != Color.TRANSPARENT) { + drawable.setColorFilter(new PorterDuffColorFilter(mTintColor, PorterDuff.Mode.SRC_ATOP)); + } + drawable.setBounds(0, 0, mWidth, mHeight); + mSrcDrawable = drawable; + } else if (imageHolder.isAnimated()) { + mGifMovie = imageHolder.getGifMovie(); + if (mTintColor != Color.TRANSPARENT) { + mGifPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mGifPaint.setColorFilter(new PorterDuffColorFilter(mTintColor, PorterDuff.Mode.SRC_ATOP)); + } + } + imageHolder.attached(); + postInvalidateDelayed(0); + } + + @SuppressLint("DiscouragedPrivateApi") + private void legacyShouldReplaceDrawable(@NonNull ImageDataHolder imageHolder) { Drawable imageDrawable = imageHolder.getDrawable(); Bitmap bitmap = imageHolder.getBitmap(); if (imageDrawable != null || bitmap != null) { Resources resources = ContextHolder.getAppContext().getResources(); Drawable drawable = imageDrawable != null ? imageDrawable : new BitmapDrawable(resources, bitmap); + if (mTintColor != Color.TRANSPARENT) { + drawable.setColorFilter(new PorterDuffColorFilter(mTintColor, PorterDuff.Mode.SRC_ATOP)); + } int w = (mWidth == 0) ? drawable.getIntrinsicWidth() : mWidth; int h = (mHeight == 0) ? drawable.getIntrinsicHeight() : mHeight; drawable.setBounds(0, 0, w, h); @@ -262,6 +397,10 @@ private void shouldReplaceDrawable(@NonNull ImageDataHolder imageHolder) { } } else if (imageHolder.isAnimated()) { mGifMovie = imageHolder.getGifMovie(); + if (mTintColor != Color.TRANSPARENT) { + mGifPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mGifPaint.setColorFilter(new PorterDuffColorFilter(mTintColor, PorterDuff.Mode.SRC_ATOP)); + } } postInvalidateDelayed(0); } @@ -285,218 +424,49 @@ private void handleFetchImageResult(@Nullable final ImageDataSupplier imageHolde } } - private interface IAlignConfig { - - void setDesiredSize(int desiredDrawableWidth, int desiredDrawableHeight); - - void setActiveSizeWithRate(float heightRate); - - void setMargin(int marginLeft, int marginRight); - - int getSize(@NonNull Paint paint, CharSequence text, int start, int end, - @Nullable FontMetricsInt fm, Drawable drawable); - - void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float baseLineX, - int lineTop, int baselineY, int lintBottom, @NonNull Paint paint, - Drawable drawable); - } - - private abstract static class BaseAlignConfig implements IAlignConfig { - - private int mDesiredDrawableWidth; - private int mDesiredDrawableHeight; - private float mHeightRate; - private final int[] mSize = new int[2]; - private int mMarginLeft; - private int mMarginRight; - - @Override - public void setDesiredSize(int desiredDrawableWidth, int desiredDrawableHeight) { - mDesiredDrawableWidth = desiredDrawableWidth; - mDesiredDrawableHeight = desiredDrawableHeight; - mHeightRate = 0; - } - - @Override - public void setActiveSizeWithRate(float heightRate) { - mHeightRate = heightRate; - mDesiredDrawableWidth = 0; - mDesiredDrawableHeight = 0; - } - - @Override - public void setMargin(int marginLeft, int marginRight) { - mMarginLeft = marginLeft; - mMarginRight = marginRight; - } - - private void calDrawableSize(Rect drawableBounds, Paint paint) { - int dWidth; - int dHeight; - if (mHeightRate > 0) { - int textSize = (int) paint.getTextSize(); - dHeight = (int) (textSize * mHeightRate); - dWidth = drawableBounds.right * dHeight / drawableBounds.bottom; - } else { - dHeight = mDesiredDrawableHeight; - dWidth = mDesiredDrawableWidth; - } - if (dWidth <= 0 || dHeight <= 0) { - dWidth = drawableBounds.right; - dHeight = drawableBounds.bottom; - } - mSize[0] = dWidth; - mSize[1] = dHeight; - } - - @Override - public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, - @Nullable FontMetricsInt fm, Drawable drawable) { - calDrawableSize(drawable.getBounds(), paint); - int dWidth = mSize[0]; - int dHeight = mSize[1]; - int deltaTop = 0; - int deltaBottom = 0; - if (fm != null) { - deltaTop = fm.top - fm.ascent; - deltaBottom = fm.bottom - fm.descent; - } - int size = getCustomSize(paint, - text, start, end, - fm, dWidth, dHeight); - if (fm != null) { - fm.top = fm.ascent + deltaTop; - fm.bottom = fm.descent + deltaBottom; - } - return mMarginLeft + size + mMarginRight; - } - - @Override - public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, - float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, - Drawable drawable) { - Rect drawableBounds = drawable.getBounds(); - int dWidth = mSize[0]; - int dHeight = mSize[1]; - FontMetricsInt fontMetricsInt = paint.getFontMetricsInt(); - int transY = getTransY(canvas, text, start, end, baseLineX, lineTop, baselineY, - lineBottom, paint, fontMetricsInt, dWidth, dHeight); - transY = adjustTransY(transY, lineTop, lineBottom, dHeight); - float scaleX = (float) dWidth / drawableBounds.right; - float scaleY = (float) dHeight / drawableBounds.bottom; - canvas.save(); - canvas.translate(baseLineX + mMarginLeft, transY); - canvas.scale(scaleX, scaleY); - drawable.draw(canvas); - canvas.restore(); - } - - private static int adjustTransY(int transY, int lineTop, int lineBottom, - int drawableHeight) { - if (drawableHeight + transY > lineBottom) { - transY = lineBottom - drawableHeight; - } - if (transY < lineTop) { - transY = lineTop; - } - return transY; - } - - abstract int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, - @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight); - - abstract int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, - float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, - FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight); - } - - private static class AlignBaselineConfig extends BaseAlignConfig { - - @Override - public int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, - @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight) { - if (fm != null) { - fm.ascent = -drawableHeight; - } - return drawableWidth; - } - - @Override - public int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, - float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, - FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight) { - return baselineY - drawableHeight; - } - } - - private static class AlignBottomConfig extends AlignBaselineConfig { - - @Override - public int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, - @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight) { - if (fm != null) { - fm.ascent = fm.descent - drawableHeight; + public void setTintColor(final int tintColor) { + Runnable action = new Runnable() { + @Override + public void run() { + if (tintColor != mTintColor) { + mTintColor = tintColor; + ColorFilter colorFilter = tintColor == Color.TRANSPARENT ? null + : new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP); + if (mSrcDrawable != null) { + mSrcDrawable.setColorFilter(colorFilter); + } else if (mGifMovie != null) { + mGifPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mGifPaint.setColorFilter(colorFilter); + } + } } - return drawableWidth; - } - - @Override - public int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, - float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, - FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight) { - return super - .getTransY(canvas, text, start, end, baseLineX, lineTop, baselineY, lineBottom, - paint, fontMetricsInt, drawableWidth, drawableHeight) - + fontMetricsInt.descent; + }; + if (UIThreadUtils.isOnUiThread()) { + action.run(); + } else { + UIThreadUtils.runOnUiThread(action); } } - private static class AlignCenterConfig extends AlignBottomConfig { - - @Override - public int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, - @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight) { - if (fm != null) { - int textAreaHeight = fm.descent - fm.ascent; - if (textAreaHeight < drawableHeight) { - int oldSumOfAscentAndDescent = fm.ascent + fm.descent; - fm.ascent = oldSumOfAscentAndDescent - drawableHeight >> 1; - fm.descent = oldSumOfAscentAndDescent + drawableHeight >> 1; + public void setBackgroundColor(final int color) { + Runnable action = new Runnable() { + @Override + public void run() { + if (color == Color.TRANSPARENT) { + mBackgroundPaint = null; + } else { + if (mBackgroundPaint == null) { + mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + mBackgroundPaint.setColor(color); } - } - return drawableWidth; - } - - @Override - public int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, - float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, - FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight) { - int transY = super - .getTransY(canvas, text, start, end, baseLineX, lineTop, baselineY, lineBottom, - paint, fontMetricsInt, drawableWidth, drawableHeight); - int fontHeight = fontMetricsInt.descent - fontMetricsInt.ascent; - transY = transY - (fontHeight >> 1) + (drawableHeight >> 1); - return transY; + }; + if (UIThreadUtils.isOnUiThread()) { + action.run(); + } else { + UIThreadUtils.runOnUiThread(action); } } - private static class AlignTopConfig extends BaseAlignConfig { - - @Override - public int getCustomSize(@NonNull Paint paint, CharSequence text, int start, int end, - @Nullable FontMetricsInt fm, int drawableWidth, int drawableHeight) { - if (fm != null) { - fm.descent = drawableHeight + fm.ascent; - } - return drawableWidth; - } - - @Override - public int getTransY(@NonNull Canvas canvas, CharSequence text, int start, int end, - float baseLineX, int lineTop, int baselineY, int lineBottom, @NonNull Paint paint, - FontMetricsInt fontMetricsInt, int drawableWidth, int drawableHeight) { - return baselineY + fontMetricsInt.ascent; - } - } } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextVerticalAlignSpan.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextVerticalAlignSpan.java new file mode 100644 index 00000000000..0f34f5a9fae --- /dev/null +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextVerticalAlignSpan.java @@ -0,0 +1,63 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.renderer.component.text; + +import android.graphics.Paint.FontMetricsInt; +import android.text.TextPaint; +import android.text.style.CharacterStyle; +import com.tencent.renderer.node.TextVirtualNode; + +public class TextVerticalAlignSpan extends CharacterStyle { + + private final FontMetricsInt mReusableFontMetricsInt = new FontMetricsInt(); + private final String mVerticalAlign; + private int mLineTop; + private int mLineBottom; + + public TextVerticalAlignSpan(String verticalAlign) { + this.mVerticalAlign = verticalAlign; + } + + public void setLineMetrics(int top, int bottom) { + mLineTop = top; + mLineBottom = bottom; + } + + @Override + public void updateDrawState(TextPaint tp) { + if (mLineTop != 0 || mLineBottom != 0) { + final FontMetricsInt fmi = mReusableFontMetricsInt; + switch (mVerticalAlign) { + case TextVirtualNode.V_ALIGN_TOP: + tp.getFontMetricsInt(fmi); + tp.baselineShift = mLineTop - fmi.top; + break; + case TextVirtualNode.V_ALIGN_MIDDLE: + tp.getFontMetricsInt(fmi); + tp.baselineShift = (mLineTop + mLineBottom - fmi.top - fmi.bottom) / 2; + break; + case TextVirtualNode.V_ALIGN_BOTTOM: + tp.getFontMetricsInt(fmi); + tp.baselineShift = mLineBottom - fmi.bottom; + break; + case TextVirtualNode.V_ALIGN_BASELINE: + default: + break; + } + } + } +} diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/ImageVirtualNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/ImageVirtualNode.java index 367a6208f80..5933f16500e 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/ImageVirtualNode.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/ImageVirtualNode.java @@ -25,8 +25,8 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.text.SpannableStringBuilder; -import android.text.style.ImageSpan; +import android.text.style.ImageSpan; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -41,14 +41,29 @@ import com.tencent.renderer.component.text.TextGestureSpan; import com.tencent.renderer.component.text.TextImageSpan; import java.util.List; +import java.util.Objects; public class ImageVirtualNode extends VirtualNode { protected int mWidth; protected int mHeight; + @Deprecated protected int mLeft; + @Deprecated protected int mTop; + @Deprecated protected int mVerticalAlignment = ImageSpan.ALIGN_BASELINE; + protected float mMargin = Float.NaN; + protected float mMarginVertical = Float.NaN; + protected float mMarginHorizontal = Float.NaN; + protected float mMarginLeft = Float.NaN; + protected float mMarginTop = Float.NaN; + protected float mMarginRight = Float.NaN; + protected float mMarginBottom = Float.NaN; + @Nullable + protected String mVerticalAlign; + protected int mTintColor = Color.TRANSPARENT; + protected int mBackgroundColor = Color.TRANSPARENT; @Nullable protected TextImageSpan mImageSpan; @Nullable @@ -72,18 +87,64 @@ public int getHeight() { return mHeight; } + @Deprecated public int getLeft() { return mLeft; } + @Deprecated public int getTop() { return mTop; } + /** + * @deprecated use {@link #getVerticalAlign} instead + */ + @Deprecated public int getVerticalAlignment() { return mVerticalAlignment; } + public int getMarginLeft() { + return getValue(mMarginLeft, mMarginHorizontal, mMargin); + } + + public int getMarginTop() { + return getValue(mMarginTop, mMarginVertical, mMargin); + } + + public int getMarginRight() { + return getValue(mMarginRight, mMarginHorizontal, mMargin); + } + + public int getMarginBottom() { + return getValue(mMarginBottom, mMarginVertical, mMargin); + } + + private int getValue(float primary, float secondary, float tertiary) { + if (!Float.isNaN(primary)) { + return Math.round(primary); + } + if (!Float.isNaN(secondary)) { + return Math.round(secondary); + } + if (!Float.isNaN(tertiary)) { + return Math.round(tertiary); + } + return 0; + } + + @Nullable + public String getVerticalAlign() { + if (mVerticalAlign != null) { + return mVerticalAlign; + } + if (mParent instanceof TextVirtualNode) { + return ((TextVirtualNode) mParent).getVerticalAlign(); + } + return null; + } + @NonNull protected TextImageSpan createImageSpan() { Drawable drawable = null; @@ -135,6 +196,7 @@ public void setHeight(float height) { markDirty(); } + @Deprecated @SuppressWarnings("unused") @HippyControllerProps(name = NodeProps.LEFT, defaultType = HippyControllerProps.NUMBER) public void setLeft(float left) { @@ -143,6 +205,7 @@ public void setLeft(float left) { markDirty(); } + @Deprecated @SuppressWarnings("unused") @HippyControllerProps(name = NodeProps.TOP, defaultType = HippyControllerProps.NUMBER) public void setTop(float top) { @@ -151,18 +214,99 @@ public void setTop(float top) { markDirty(); } + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.MARGIN, defaultType = HippyControllerProps.NUMBER, defaultNumber = Float.NaN) + public void setMargin(float value) { + mMargin = Float.isNaN(value) ? Float.NaN : PixelUtil.dp2px(value); + markDirty(); + } + + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.MARGIN_VERTICAL, defaultType = HippyControllerProps.NUMBER, defaultNumber + = Float.NaN) + public void setMarginVertical(float value) { + mMarginVertical = Float.isNaN(value) ? Float.NaN : PixelUtil.dp2px(value); + markDirty(); + } + + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.MARGIN_HORIZONTAL, defaultType = HippyControllerProps.NUMBER, + defaultNumber = Float.NaN) + public void setMarginHorizontal(float value) { + mMarginHorizontal = Float.isNaN(value) ? Float.NaN : PixelUtil.dp2px(value); + markDirty(); + } + + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.MARGIN_LEFT, defaultType = HippyControllerProps.NUMBER, defaultNumber = + Float.NaN) + public void setMarginLeft(float value) { + mMarginLeft = Float.isNaN(value) ? Float.NaN : PixelUtil.dp2px(value); + markDirty(); + } + + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.MARGIN_TOP, defaultType = HippyControllerProps.NUMBER, defaultNumber = + Float.NaN) + public void setMarginTop(float value) { + mMarginTop = Float.isNaN(value) ? Float.NaN : PixelUtil.dp2px(value); + markDirty(); + } + + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.MARGIN_RIGHT, defaultType = HippyControllerProps.NUMBER, defaultNumber = + Float.NaN) + public void setMarginRight(float value) { + mMarginRight = Float.isNaN(value) ? Float.NaN : PixelUtil.dp2px(value); + markDirty(); + } + + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.MARGIN_BOTTOM, defaultType = HippyControllerProps.NUMBER, defaultNumber = + Float.NaN) + public void setMarginBottom(float value) { + mMarginBottom = Float.isNaN(value) ? Float.NaN : PixelUtil.dp2px(value); + markDirty(); + } + + /** + * @deprecated use {@link #setVerticalAlign} instead + */ + @Deprecated @SuppressWarnings("unused") @HippyControllerProps(name = NodeProps.PROP_VERTICAL_ALIGNMENT, defaultType = HippyControllerProps.NUMBER, defaultNumber = ImageSpan.ALIGN_BASELINE) public void setVerticalAlignment(int alignment) { if (alignment != mVerticalAlignment) { mVerticalAlignment = alignment; - if (mImageSpan != null) { - mImageSpan.setVerticalAlignment(alignment); - } + markDirty(); } } + @SuppressWarnings("unused") + @HippyControllerProps(name = NodeProps.VERTICAL_ALIGN, defaultType = HippyControllerProps.STRING) + public void setVerticalAlign(String align) { + if (Objects.equals(mVerticalAlign, align)) { + return; + } + switch (align) { + case HippyControllerProps.DEFAULT: + // reset to default + mVerticalAlign = null; + break; + case TextVirtualNode.V_ALIGN_TOP: + case TextVirtualNode.V_ALIGN_MIDDLE: + case TextVirtualNode.V_ALIGN_BASELINE: + case TextVirtualNode.V_ALIGN_BOTTOM: + mVerticalAlign = align; + break; + default: + mVerticalAlign = TextVirtualNode.V_ALIGN_BASELINE; + break; + } + markDirty(); + } + @SuppressWarnings("unused") @HippyControllerProps(name = "defaultSource", defaultType = HippyControllerProps.STRING) public void setDefaultSource(String defaultSource) { @@ -179,4 +323,28 @@ public void setUrl(String src) { } } } + + @HippyControllerProps(name = "tintColor", defaultType = HippyControllerProps.NUMBER) + public void setTintColor(int tintColor) { + mTintColor = tintColor; + if (mImageSpan != null) { + mImageSpan.setTintColor(tintColor); + } + } + + public int getTintColor() { + return mTintColor; + } + + @HippyControllerProps(name = NodeProps.BACKGROUND_COLOR, defaultType = HippyControllerProps.NUMBER) + public void setBackgroundColor(int color) { + mBackgroundColor = color; + if (mImageSpan != null) { + mImageSpan.setBackgroundColor(color); + } + } + + public int getBackgroundColor() { + return mBackgroundColor; + } } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java index 4d434f852c4..92952b7b0fb 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java @@ -33,6 +33,7 @@ import android.text.TextPaint; import android.text.TextUtils; import android.text.style.AbsoluteSizeSpan; +import android.text.style.BackgroundColorSpan; import android.text.style.ImageSpan; import android.text.style.StrikethroughSpan; import android.text.style.UnderlineSpan; @@ -51,6 +52,7 @@ import com.tencent.renderer.component.text.TextLineHeightSpan; import com.tencent.renderer.component.text.TextShadowSpan; import com.tencent.renderer.component.text.TextStyleSpan; +import com.tencent.renderer.component.text.TextVerticalAlignSpan; import com.tencent.renderer.utils.FlexUtils.FlexMeasureMode; import java.util.ArrayList; import java.util.HashMap; @@ -61,6 +63,10 @@ public class TextVirtualNode extends VirtualNode { public static final String STRATEGY_SIMPLE = "simple"; public static final String STRATEGY_HIGH_QUALITY = "high_quality"; public static final String STRATEGY_BALANCED = "balanced"; + public final static String V_ALIGN_TOP = "top"; + public final static String V_ALIGN_MIDDLE = "middle"; + public final static String V_ALIGN_BASELINE = "baseline"; + public final static String V_ALIGN_BOTTOM = "bottom"; private static final int TEXT_BOLD_MIN_VALUE = 500; private static final int TEXT_SHADOW_COLOR_DEFAULT = 0x55000000; @@ -115,6 +121,9 @@ public class TextVirtualNode extends VirtualNode { protected final FontAdapter mFontAdapter; @Nullable protected Layout mLayout; + @Nullable + protected String mVerticalAlign; + protected int mBackgroundColor = Color.TRANSPARENT; public TextVirtualNode(int rootId, int id, int pid, int index, @NonNull NativeRender nativeRender) { @@ -398,7 +407,15 @@ protected void createSpanOperationImpl(@NonNull List ops, if (start > end) { return; } + String verticalAlign = getVerticalAlign(); + if (verticalAlign != null && !V_ALIGN_BASELINE.equals(verticalAlign)) { + TextVerticalAlignSpan span = new TextVerticalAlignSpan(verticalAlign); + ops.add(new SpanOperation(start, end, span, SpanOperation.PRIORITY_LOWEST)); + } ops.add(new SpanOperation(start, end, createForegroundColorSpan())); + if (mBackgroundColor != Color.TRANSPARENT && mParent != null) { + ops.add(new SpanOperation(start, end, new BackgroundColorSpan(mBackgroundColor))); + } if (mLetterSpacing != 0) { ops.add(new SpanOperation(start, end, new TextLetterSpacingSpan(mLetterSpacing))); @@ -421,13 +438,6 @@ protected void createSpanOperationImpl(@NonNull List ops, new TextShadowSpan(mShadowOffsetDx, mShadowOffsetDy, mShadowRadius, mShadowColor))); } - if (mLineHeight != 0 && mLineSpacingMultiplier == 1.0f && mLineSpacingExtra == 0) { - float lh = mLineHeight; - if (mFontAdapter != null && mEnableScale) { - lh = (lh * mFontAdapter.getFontScale()); - } - ops.add(new SpanOperation(start, end, new TextLineHeightSpan(lh))); - } if (mGestureTypes != null && mGestureTypes.size() > 0) { TextGestureSpan span = new TextGestureSpan(mId); span.addGestureTypes(mGestureTypes); @@ -436,6 +446,18 @@ protected void createSpanOperationImpl(@NonNull List ops, if (useChild) { createChildrenSpanOperation(ops, builder); } + // apply paragraph spans + if (mParent == null) { + int paragraphStart = 0; + int paragraphEnd = builder.length(); + if (mLineHeight != 0 && mLineSpacingMultiplier == 1.0f && mLineSpacingExtra == 0) { + float lh = mLineHeight; + if (mFontAdapter != null && mEnableScale) { + lh = (lh * mFontAdapter.getFontScale()); + } + ops.add(new SpanOperation(paragraphStart, paragraphEnd, new TextLineHeightSpan(lh))); + } + } } protected float getLineSpacingMultiplier() { @@ -486,7 +508,17 @@ protected Layout createLayout(final float width, final FlexMeasureMode widthMode } } } - + CharSequence layoutText = layout.getText(); + if (layoutText instanceof Spanned) { + Spanned spanned = (Spanned) layoutText; + TextVerticalAlignSpan[] spans = spanned.getSpans(0, spanned.length(), TextVerticalAlignSpan.class); + for (TextVerticalAlignSpan span : spans) { + int offset = spanned.getSpanStart(span); + int line = layout.getLineForOffset(offset); + int baseline = layout.getLineBaseline(line); + span.setLineMetrics(layout.getLineTop(line) - baseline, layout.getLineBottom(line) - baseline); + } + } mLayout = layout; mLastLayoutWidth = layout.getWidth(); return layout; @@ -709,4 +741,41 @@ private CharSequence ellipsizeTail(CharSequence origin, TextPaint paint, int wid } return TextUtils.ellipsize(tmp, paint, width, TextUtils.TruncateAt.END); } + + @HippyControllerProps(name = NodeProps.BACKGROUND_COLOR, defaultType = HippyControllerProps.NUMBER) + public void setBackgroundColor(int backgroundColor) { + mBackgroundColor = backgroundColor; + markDirty(); + } + + @HippyControllerProps(name = NodeProps.VERTICAL_ALIGN, defaultType = HippyControllerProps.STRING) + public void setVerticalAlign(String align) { + switch (align) { + case HippyControllerProps.DEFAULT: + // reset to default + mVerticalAlign = null; + break; + case V_ALIGN_TOP: + case V_ALIGN_MIDDLE: + case V_ALIGN_BASELINE: + case V_ALIGN_BOTTOM: + mVerticalAlign = align; + break; + default: + mVerticalAlign = V_ALIGN_BASELINE; + break; + } + markDirty(); + } + + @Nullable + public String getVerticalAlign() { + if (mVerticalAlign != null) { + return mVerticalAlign; + } + if (mParent instanceof TextVirtualNode) { + return ((TextVirtualNode) mParent).getVerticalAlign(); + } + return null; + } } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNode.java index 1b774bcbce3..b8234212653 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNode.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNode.java @@ -135,15 +135,23 @@ public int getChildCount() { protected static class SpanOperation { + public static final int PRIORITY_DEFAULT = 1; + public static final int PRIORITY_LOWEST = 0; private final int mStart; private final int mEnd; private final Object mWhat; + private final int mPriority; @SuppressWarnings("unused") SpanOperation(int start, int end, Object what) { + this(start, end, what, PRIORITY_DEFAULT); + } + + SpanOperation(int start, int end, Object what, int priority) { mStart = start; mEnd = end; mWhat = what; + mPriority = priority; } public void execute(SpannableStringBuilder builder) { @@ -155,6 +163,7 @@ public void execute(SpannableStringBuilder builder) { } else { spanFlags = Spannable.SPAN_EXCLUSIVE_INCLUSIVE; } + spanFlags |= (mPriority << Spannable.SPAN_PRIORITY_SHIFT) & Spannable.SPAN_PRIORITY; builder.setSpan(mWhat, mStart, mEnd, spanFlags); } }