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 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 RCTRawTextManager());
|
||||
viewManagers.add(new RCTVirtualTextManager());
|
||||
viewManagers.add(new RCTTextInlineImageManager());
|
||||
viewManagers.add(new RCTImageViewManager());
|
||||
|
||||
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).
|
||||
*/
|
||||
/* package */ class RCTRawText extends FlatTextShadowNode {
|
||||
/* package */ final class RCTRawText extends FlatTextShadowNode {
|
||||
|
||||
private @Nullable String mText;
|
||||
|
||||
|
@ -39,6 +39,11 @@ import com.facebook.react.uimanager.ReactProp;
|
|||
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@ReactProp(name = "text")
|
||||
public void setText(@Nullable String text) {
|
||||
mText = text;
|
||||
|
|
|
@ -201,6 +201,8 @@ import com.facebook.react.uimanager.ViewProps;
|
|||
clipRight,
|
||||
clipBottom);
|
||||
stateBuilder.addDrawCommand(mDrawCommand);
|
||||
|
||||
performCollectAttachDetachListeners(stateBuilder);
|
||||
}
|
||||
|
||||
@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)
|
||||
public void setFontSize(float fontSizeSp) {
|
||||
final int fontSize;
|
||||
|
|
Loading…
Reference in New Issue