Implement RCTTextInlineImage with Nodes
Summary: React allows nesting <Image> inside <Text>, in which case it turns into an RCTTextInlineImage element. RNFeed is using it in a few places and thus we need to support it, too. This diff implements it with InlineImageSpan (WithPipeline, and WithDrawee separately). Reviewed By: ahmedre Differential Revision: D2792569
This commit is contained in:
parent
28efab0670
commit
31d2443dd4
|
@ -59,4 +59,5 @@ import com.facebook.react.uimanager.ReactShadowNode;
|
||||||
|
|
||||||
protected abstract void performCollectText(SpannableStringBuilder builder);
|
protected abstract void performCollectText(SpannableStringBuilder builder);
|
||||||
protected abstract void performApplySpans(SpannableStringBuilder builder, int begin, int end);
|
protected abstract void performApplySpans(SpannableStringBuilder builder, int begin, int end);
|
||||||
|
protected abstract void performCollectAttachDetachListeners(StateBuilder stateBuilder);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ public class FlatUIImplementation extends UIImplementation {
|
||||||
viewManagers.add(new RCTTextManager());
|
viewManagers.add(new RCTTextManager());
|
||||||
viewManagers.add(new RCTRawTextManager());
|
viewManagers.add(new RCTRawTextManager());
|
||||||
viewManagers.add(new RCTVirtualTextManager());
|
viewManagers.add(new RCTVirtualTextManager());
|
||||||
|
viewManagers.add(new RCTTextInlineImageManager());
|
||||||
viewManagers.add(new RCTImageViewManager());
|
viewManagers.add(new RCTImageViewManager());
|
||||||
|
|
||||||
ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagers);
|
ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagers);
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.flat;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.text.style.ReplacementSpan;
|
||||||
|
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
|
import com.facebook.infer.annotation.Assertions;
|
||||||
|
|
||||||
|
/* package */ final class InlineImageSpanWithPipeline extends ReplacementSpan
|
||||||
|
implements AttachDetachListener, BitmapUpdateListener {
|
||||||
|
|
||||||
|
private static final RectF TMP_RECT = new RectF();
|
||||||
|
|
||||||
|
private @Nullable PipelineRequestHelper mRequestHelper;
|
||||||
|
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
|
||||||
|
private float mWidth;
|
||||||
|
private float mHeight;
|
||||||
|
private boolean mFrozen;
|
||||||
|
|
||||||
|
/* package */ InlineImageSpanWithPipeline() {
|
||||||
|
this(null, Float.NaN, Float.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InlineImageSpanWithPipeline(
|
||||||
|
@Nullable PipelineRequestHelper requestHelper,
|
||||||
|
float width,
|
||||||
|
float height) {
|
||||||
|
mRequestHelper = requestHelper;
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ InlineImageSpanWithPipeline mutableCopy() {
|
||||||
|
return new InlineImageSpanWithPipeline(mRequestHelper, mWidth, mHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ boolean hasImageRequest() {
|
||||||
|
return mRequestHelper != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns a new image request to the DrawImage, or null to clear the image request.
|
||||||
|
*/
|
||||||
|
/* package */ void setImageRequest(@Nullable ImageRequest imageRequest) {
|
||||||
|
if (imageRequest == null) {
|
||||||
|
mRequestHelper = null;
|
||||||
|
} else {
|
||||||
|
mRequestHelper = new PipelineRequestHelper(imageRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ float getWidth() {
|
||||||
|
return mWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void setWidth(float width) {
|
||||||
|
mWidth = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ float getHeight() {
|
||||||
|
return mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void setHeight(float height) {
|
||||||
|
mHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void freeze() {
|
||||||
|
mFrozen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ boolean isFrozen() {
|
||||||
|
return mFrozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSecondaryAttach(Bitmap bitmap) {
|
||||||
|
// We don't know if width or height changed, so invalidate just in case.
|
||||||
|
Assertions.assumeNotNull(mCallback).invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBitmapReady(Bitmap bitmap) {
|
||||||
|
// Bitmap is now ready, draw it.
|
||||||
|
Assertions.assumeNotNull(mCallback).invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
|
||||||
|
mCallback = callback;
|
||||||
|
|
||||||
|
if (mRequestHelper != null) {
|
||||||
|
mRequestHelper.attach(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetached() {
|
||||||
|
if (mRequestHelper != null) {
|
||||||
|
mRequestHelper.detach();
|
||||||
|
|
||||||
|
if (mRequestHelper.isDetached()) {
|
||||||
|
// optional
|
||||||
|
mCallback = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
|
||||||
|
if (fm != null) {
|
||||||
|
fm.ascent = -Math.round(mHeight);
|
||||||
|
fm.descent = 0;
|
||||||
|
|
||||||
|
fm.top = fm.ascent;
|
||||||
|
fm.bottom = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(mWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(
|
||||||
|
Canvas canvas,
|
||||||
|
CharSequence text,
|
||||||
|
int start,
|
||||||
|
int end,
|
||||||
|
float x,
|
||||||
|
int top,
|
||||||
|
int y,
|
||||||
|
int bottom,
|
||||||
|
Paint paint) {
|
||||||
|
if (mRequestHelper == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bitmap = mRequestHelper.getBitmap();
|
||||||
|
if (bitmap == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float bottomFloat = (float) bottom;
|
||||||
|
TMP_RECT.set(x, bottomFloat - mHeight, x + mWidth, bottomFloat);
|
||||||
|
|
||||||
|
canvas.drawBitmap(bitmap, null, TMP_RECT, paint);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ import com.facebook.react.uimanager.ReactProp;
|
||||||
/**
|
/**
|
||||||
* RCTRawText is a FlatTextShadowNode that can only contain raw text (but not styling).
|
* RCTRawText is a FlatTextShadowNode that can only contain raw text (but not styling).
|
||||||
*/
|
*/
|
||||||
/* package */ class RCTRawText extends FlatTextShadowNode {
|
/* package */ final class RCTRawText extends FlatTextShadowNode {
|
||||||
|
|
||||||
private @Nullable String mText;
|
private @Nullable String mText;
|
||||||
|
|
||||||
|
@ -39,6 +39,11 @@ import com.facebook.react.uimanager.ReactProp;
|
||||||
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
@ReactProp(name = "text")
|
@ReactProp(name = "text")
|
||||||
public void setText(@Nullable String text) {
|
public void setText(@Nullable String text) {
|
||||||
mText = text;
|
mText = text;
|
||||||
|
|
|
@ -201,6 +201,8 @@ import com.facebook.react.uimanager.ViewProps;
|
||||||
clipRight,
|
clipRight,
|
||||||
clipBottom);
|
clipBottom);
|
||||||
stateBuilder.addDrawCommand(mDrawCommand);
|
stateBuilder.addDrawCommand(mDrawCommand);
|
||||||
|
|
||||||
|
performCollectAttachDetachListeners(stateBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = ViewProps.LINE_HEIGHT, defaultDouble = Double.NaN)
|
@ReactProp(name = ViewProps.LINE_HEIGHT, defaultDouble = Double.NaN)
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.flat;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
|
||||||
|
import com.facebook.react.uimanager.ReactProp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RCTTextInlineImage
|
||||||
|
*/
|
||||||
|
/* package */ class RCTTextInlineImage extends FlatTextShadowNode {
|
||||||
|
|
||||||
|
private InlineImageSpanWithPipeline mInlineImageSpan = new InlineImageSpanWithPipeline();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStyleWidth(float width) {
|
||||||
|
super.setStyleWidth(width);
|
||||||
|
|
||||||
|
if (mInlineImageSpan.getWidth() != width) {
|
||||||
|
getMutableSpan().setWidth(width);
|
||||||
|
notifyChanged(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStyleHeight(float height) {
|
||||||
|
super.setStyleHeight(height);
|
||||||
|
|
||||||
|
if (mInlineImageSpan.getHeight() != height) {
|
||||||
|
getMutableSpan().setHeight(height);
|
||||||
|
notifyChanged(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void performCollectText(SpannableStringBuilder builder) {
|
||||||
|
builder.append("I");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void performApplySpans(SpannableStringBuilder builder, int begin, int end) {
|
||||||
|
mInlineImageSpan.freeze();
|
||||||
|
builder.setSpan(
|
||||||
|
mInlineImageSpan,
|
||||||
|
begin,
|
||||||
|
end,
|
||||||
|
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
|
||||||
|
// mInlineImageSpan should already be frozen so no need to freeze it again
|
||||||
|
stateBuilder.addAttachDetachListener(mInlineImageSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "src")
|
||||||
|
public void setSource(@Nullable String source) {
|
||||||
|
getMutableSpan().setImageRequest(
|
||||||
|
ImageRequestHelper.createImageRequest(getThemedContext(), source));
|
||||||
|
}
|
||||||
|
|
||||||
|
private InlineImageSpanWithPipeline getMutableSpan() {
|
||||||
|
if (mInlineImageSpan.isFrozen()) {
|
||||||
|
mInlineImageSpan = mInlineImageSpan.mutableCopy();
|
||||||
|
}
|
||||||
|
return mInlineImageSpan;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.flat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewManager that creates instances of RCTTextInlineImage.
|
||||||
|
*/
|
||||||
|
/* package */ final class RCTTextInlineImageManager extends VirtualViewManager<RCTTextInlineImage> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "RCTTextInlineImage";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RCTTextInlineImage createShadowNodeInstance() {
|
||||||
|
return new RCTTextInlineImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<RCTTextInlineImage> getShadowNodeClass() {
|
||||||
|
return RCTTextInlineImage.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,6 +59,14 @@ import com.facebook.react.uimanager.ViewProps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
|
||||||
|
for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
|
||||||
|
FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i);
|
||||||
|
child.performCollectAttachDetachListeners(stateBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ReactProp(name = ViewProps.FONT_SIZE, defaultFloat = Float.NaN)
|
@ReactProp(name = ViewProps.FONT_SIZE, defaultFloat = Float.NaN)
|
||||||
public void setFontSize(float fontSizeSp) {
|
public void setFontSize(float fontSizeSp) {
|
||||||
final int fontSize;
|
final int fontSize;
|
||||||
|
|
Loading…
Reference in New Issue