decouple textview from fresco
Reviewed By: andreicoman11 Differential Revision: D2960626 fb-gh-sync-id: c03aa7f16cdea795cefe39da2c5d660ae6278a37 shipit-source-id: c03aa7f16cdea795cefe39da2c5d660ae6278a37
This commit is contained in:
parent
c32e5fd84f
commit
9baef48498
|
@ -19,6 +19,7 @@ android_library(
|
|||
react_native_target('java/com/facebook/react/views/swiperefresh:swiperefresh'),
|
||||
react_native_target('java/com/facebook/react/views/switchview:switchview'),
|
||||
react_native_target('java/com/facebook/react/views/text:text'),
|
||||
react_native_target('java/com/facebook/react/views/text/frescosupport:frescosupport'),
|
||||
react_native_target('java/com/facebook/react/views/textinput:textinput'),
|
||||
react_native_target('java/com/facebook/react/views/toolbar:toolbar'),
|
||||
react_native_target('java/com/facebook/react/views/view:view'),
|
||||
|
|
|
@ -22,8 +22,8 @@ import com.facebook.react.modules.camera.CameraRollManager;
|
|||
import com.facebook.react.modules.camera.ImageEditingManager;
|
||||
import com.facebook.react.modules.camera.ImageStoreManager;
|
||||
import com.facebook.react.modules.clipboard.ClipboardModule;
|
||||
import com.facebook.react.modules.dialog.DialogModule;
|
||||
import com.facebook.react.modules.datepicker.DatePickerDialogModule;
|
||||
import com.facebook.react.modules.dialog.DialogModule;
|
||||
import com.facebook.react.modules.fresco.FrescoModule;
|
||||
import com.facebook.react.modules.intent.IntentModule;
|
||||
import com.facebook.react.modules.location.LocationModule;
|
||||
|
@ -45,16 +45,16 @@ import com.facebook.react.views.progressbar.ReactProgressBarViewManager;
|
|||
import com.facebook.react.views.recyclerview.RecyclerViewBackedScrollViewManager;
|
||||
import com.facebook.react.views.scroll.ReactHorizontalScrollViewManager;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewManager;
|
||||
import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager;
|
||||
import com.facebook.react.views.switchview.ReactSwitchManager;
|
||||
import com.facebook.react.views.text.ReactRawTextManager;
|
||||
import com.facebook.react.views.text.ReactTextViewManager;
|
||||
import com.facebook.react.views.text.ReactTextInlineImageViewManager;
|
||||
import com.facebook.react.views.text.ReactVirtualTextViewManager;
|
||||
import com.facebook.react.views.textfrescosupport.FrescoBasedReactTextInlineImageViewManager;
|
||||
import com.facebook.react.views.textinput.ReactTextInputManager;
|
||||
import com.facebook.react.views.toolbar.ReactToolbarManager;
|
||||
import com.facebook.react.views.view.ReactViewManager;
|
||||
import com.facebook.react.views.viewpager.ReactViewPagerManager;
|
||||
import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager;
|
||||
import com.facebook.react.views.webview.ReactWebViewManager;
|
||||
|
||||
/**
|
||||
|
@ -106,7 +106,7 @@ public class MainReactPackage implements ReactPackage {
|
|||
new ReactRawTextManager(),
|
||||
new ReactScrollViewManager(),
|
||||
new ReactSwitchManager(),
|
||||
new ReactTextInlineImageViewManager(),
|
||||
new FrescoBasedReactTextInlineImageViewManager(),
|
||||
new ReactTextInputManager(),
|
||||
new ReactTextViewManager(),
|
||||
new ReactToolbarManager(),
|
||||
|
|
|
@ -9,10 +9,6 @@ android_library(
|
|||
react_native_target('java/com/facebook/csslayout:csslayout'),
|
||||
react_native_target('java/com/facebook/react/uimanager:uimanager'),
|
||||
react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
],
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
package com.facebook.react.views.text;
|
||||
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,90 +9,17 @@
|
|||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.facebook.common.util.UriUtil;
|
||||
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
|
||||
/**
|
||||
* {@link ReactShadowNode} class for Image embedded within a TextView.
|
||||
*
|
||||
* Base class for {@link com.facebook.csslayout.CSSNode}s that represent inline images.
|
||||
*/
|
||||
public class ReactTextInlineImageShadowNode extends LayoutShadowNode {
|
||||
public abstract class ReactTextInlineImageShadowNode extends LayoutShadowNode {
|
||||
|
||||
private @Nullable Uri mUri;
|
||||
private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
|
||||
private final @Nullable Object mCallerContext;
|
||||
|
||||
public ReactTextInlineImageShadowNode(
|
||||
AbstractDraweeControllerBuilder draweeControllerBuilder,
|
||||
@Nullable Object callerContext) {
|
||||
mDraweeControllerBuilder = draweeControllerBuilder;
|
||||
mCallerContext = callerContext;
|
||||
}
|
||||
|
||||
@ReactProp(name = "src")
|
||||
public void setSource(@Nullable String source) {
|
||||
Uri uri = null;
|
||||
if (source != null) {
|
||||
try {
|
||||
uri = Uri.parse(source);
|
||||
// Verify scheme is set, so that relative uri (used by static resources) are not handled.
|
||||
if (uri.getScheme() == null) {
|
||||
uri = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore malformed uri, then attempt to extract resource ID.
|
||||
}
|
||||
if (uri == null) {
|
||||
uri = getResourceDrawableUri(getThemedContext(), source);
|
||||
}
|
||||
}
|
||||
if (uri != mUri) {
|
||||
markUpdated();
|
||||
}
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
public @Nullable Uri getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
// TODO: t9053573 is tracking that this code should be shared
|
||||
private static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
name = name.toLowerCase(Locale.getDefault()).replace("-", "_");
|
||||
int resId = context.getResources().getIdentifier(
|
||||
name,
|
||||
"drawable",
|
||||
context.getPackageName());
|
||||
return new Uri.Builder()
|
||||
.scheme(UriUtil.LOCAL_RESOURCE_SCHEME)
|
||||
.path(String.valueOf(resId))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public AbstractDraweeControllerBuilder getDraweeControllerBuilder() {
|
||||
return mDraweeControllerBuilder;
|
||||
}
|
||||
|
||||
public @Nullable Object getCallerContext() {
|
||||
return mCallerContext;
|
||||
}
|
||||
/**
|
||||
* Build a {@link TextInlineImageSpan} from this node. This will be added to the TextView in
|
||||
* place of this node.
|
||||
*/
|
||||
public abstract TextInlineImageSpan buildInlineImageSpan();
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import javax.annotation.Nullable;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.BoringLayout;
|
||||
import android.text.Layout;
|
||||
|
@ -37,11 +36,11 @@ import com.facebook.react.common.annotations.VisibleForTesting;
|
|||
import com.facebook.react.uimanager.IllegalViewOperationException;
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import com.facebook.react.uimanager.ViewDefaults;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
/**
|
||||
* {@link ReactShadowNode} class for spannable text view.
|
||||
|
@ -94,7 +93,7 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
|||
}
|
||||
}
|
||||
|
||||
private static final void buildSpannedFromTextCSSNode(
|
||||
private static void buildSpannedFromTextCSSNode(
|
||||
ReactTextShadowNode textCSSNode,
|
||||
SpannableStringBuilder sb,
|
||||
List<SetSpanOperation> ops) {
|
||||
|
@ -107,7 +106,14 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
|||
if (child instanceof ReactTextShadowNode) {
|
||||
buildSpannedFromTextCSSNode((ReactTextShadowNode) child, sb, ops);
|
||||
} else if (child instanceof ReactTextInlineImageShadowNode) {
|
||||
buildSpannedFromImageNode((ReactTextInlineImageShadowNode) child, sb, ops);
|
||||
// We make the image take up 1 character in the span and put a corresponding character into
|
||||
// the text so that the image doesn't run over any following text.
|
||||
sb.append(INLINE_IMAGE_PLACEHOLDER);
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
sb.length() - INLINE_IMAGE_PLACEHOLDER.length(),
|
||||
sb.length(),
|
||||
((ReactTextInlineImageShadowNode) child).buildInlineImageSpan()));
|
||||
} else {
|
||||
throw new IllegalViewOperationException("Unexpected view type nested under text node: "
|
||||
+ child.getClass());
|
||||
|
@ -154,36 +160,14 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
|||
}
|
||||
}
|
||||
|
||||
private static final void buildSpannedFromImageNode(
|
||||
ReactTextInlineImageShadowNode node,
|
||||
SpannableStringBuilder sb,
|
||||
List<SetSpanOperation> ops) {
|
||||
int start = sb.length();
|
||||
// Create our own internal ImageSpan which will allow us to correctly layout the Image
|
||||
Resources resources = node.getThemedContext().getResources();
|
||||
int height = (int) Math.ceil(node.getStyleHeight());
|
||||
int width = (int) Math.ceil(node.getStyleWidth());
|
||||
TextInlineImageSpan imageSpan = new TextInlineImageSpan(
|
||||
resources,
|
||||
height,
|
||||
width,
|
||||
node.getUri(),
|
||||
node.getDraweeControllerBuilder(),
|
||||
node.getCallerContext());
|
||||
// We make the image take up 1 character in the span and put a corresponding character into the
|
||||
// text so that the image doesn't run over any following text.
|
||||
sb.append(INLINE_IMAGE_PLACEHOLDER);
|
||||
ops.add(new SetSpanOperation(start, sb.length(), imageSpan));
|
||||
}
|
||||
|
||||
protected static final Spannable fromTextCSSNode(ReactTextShadowNode textCSSNode) {
|
||||
protected static Spannable fromTextCSSNode(ReactTextShadowNode textCSSNode) {
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
// TODO(5837930): Investigate whether it's worth optimizing this part and do it if so
|
||||
|
||||
// The {@link SpannableStringBuilder} implementation require setSpan operation to be called
|
||||
// up-to-bottom, otherwise all the spannables that are withing the region for which one may set
|
||||
// a new spannable will be wiped out
|
||||
List<SetSpanOperation> ops = new ArrayList<SetSpanOperation>();
|
||||
List<SetSpanOperation> ops = new ArrayList<>();
|
||||
buildSpannedFromTextCSSNode(textCSSNode, sb, ops);
|
||||
if (textCSSNode.mFontSize == UNSET) {
|
||||
sb.setSpan(
|
||||
|
@ -330,6 +314,13 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
|||
|
||||
protected boolean mContainsImages = false;
|
||||
|
||||
public ReactTextShadowNode(boolean isVirtual) {
|
||||
mIsVirtual = isVirtual;
|
||||
if (!isVirtual) {
|
||||
setMeasureFunction(TEXT_MEASURE_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeforeLayout() {
|
||||
if (mIsVirtual) {
|
||||
|
@ -483,11 +474,4 @@ public class ReactTextShadowNode extends LayoutShadowNode {
|
|||
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
public ReactTextShadowNode(boolean isVirtual) {
|
||||
mIsVirtual = isVirtual;
|
||||
if (!isVirtual) {
|
||||
setMeasureFunction(TEXT_MEASURE_FUNCTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,163 +7,64 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.text.Spannable;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.widget.TextView;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Spannable;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.interfaces.DraweeController;
|
||||
import com.facebook.drawee.view.DraweeHolder;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
/**
|
||||
* Base class for inline image spans.
|
||||
*/
|
||||
public abstract class TextInlineImageSpan extends ReplacementSpan {
|
||||
|
||||
/**
|
||||
* TextInlineImageSpan is a span for Images that are inside <Text/>. It computes it's size based
|
||||
* on the input size. When it is time to draw, it will use the Fresco framework to get the right
|
||||
* Drawable and let that draw.
|
||||
*
|
||||
* Since Fresco needs to callback to the TextView that contains this, in the ViewManager, you must
|
||||
* tell the Span about the TextView
|
||||
*
|
||||
* Note: It borrows code from DynamicDrawableSpan and if that code updates how it computes size or
|
||||
* draws, we need to update this as well.
|
||||
*/
|
||||
public class TextInlineImageSpan extends ReplacementSpan {
|
||||
/**
|
||||
* For TextInlineImageSpan we need to update the Span to know that the window is attached and
|
||||
* the TextView that we will set as the callback on the Drawable.
|
||||
*
|
||||
* @param spannable The spannable that may contain TextInlineImageSpans
|
||||
* @param view The view which will be set as the callback for the Drawable
|
||||
*/
|
||||
public static void possiblyUpdateInlineImageSpans(Spannable spannable, TextView view) {
|
||||
TextInlineImageSpan[] spans =
|
||||
spannable.getSpans(0, spannable.length(), TextInlineImageSpan.class);
|
||||
for (TextInlineImageSpan span : spans) {
|
||||
span.onAttachedToWindow();
|
||||
span.setTextView(view);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Drawable mDrawable;
|
||||
private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
|
||||
private final DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;
|
||||
private final @Nullable Object mCallerContext;
|
||||
/**
|
||||
* Get the drawable that is span represents.
|
||||
*/
|
||||
public abstract @Nullable Drawable getDrawable();
|
||||
|
||||
private int mHeight;
|
||||
private Uri mUri;
|
||||
private int mWidth;
|
||||
/**
|
||||
* Called by the text view from {@link View#onDetachedFromWindow()},
|
||||
*/
|
||||
public abstract void onDetachedFromWindow();
|
||||
|
||||
private @Nullable TextView mTextView;
|
||||
/**
|
||||
* Called by the text view from {@link View#onStartTemporaryDetach()}.
|
||||
*/
|
||||
public abstract void onStartTemporaryDetach();
|
||||
|
||||
public TextInlineImageSpan(
|
||||
Resources resources,
|
||||
int height,
|
||||
int width,
|
||||
@Nullable Uri uri,
|
||||
AbstractDraweeControllerBuilder draweeControllerBuilder,
|
||||
@Nullable Object callerContext) {
|
||||
mDraweeHolder = new DraweeHolder(
|
||||
GenericDraweeHierarchyBuilder.newInstance(resources)
|
||||
.build()
|
||||
);
|
||||
mDraweeControllerBuilder = draweeControllerBuilder;
|
||||
mCallerContext = callerContext;
|
||||
/**
|
||||
* Called by the text view from {@link View#onAttachedToWindow()}.
|
||||
*/
|
||||
public abstract void onAttachedToWindow();
|
||||
|
||||
mHeight = height;
|
||||
mWidth = width;
|
||||
mUri = (uri != null) ? uri : Uri.EMPTY;
|
||||
}
|
||||
/**
|
||||
* Called by the text view from {@link View#onFinishTemporaryDetach()}.
|
||||
*/
|
||||
public abstract void onFinishTemporaryDetach();
|
||||
|
||||
/**
|
||||
* The ReactTextView that holds this ImageSpan is responsible for passing these methods on so
|
||||
* that we can do proper lifetime management for Fresco
|
||||
*/
|
||||
public void onDetachedFromWindow() {
|
||||
mDraweeHolder.onDetach();
|
||||
}
|
||||
|
||||
public void onStartTemporaryDetach() {
|
||||
mDraweeHolder.onDetach();
|
||||
}
|
||||
|
||||
public void onAttachedToWindow() {
|
||||
mDraweeHolder.onAttach();
|
||||
}
|
||||
|
||||
public void onFinishTemporaryDetach() {
|
||||
mDraweeHolder.onAttach();
|
||||
}
|
||||
|
||||
public @Nullable Drawable getDrawable() {
|
||||
return mDrawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(
|
||||
Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
|
||||
// NOTE: This getSize code is copied from DynamicDrawableSpan and modified to not use a Drawable
|
||||
|
||||
if (fm != null) {
|
||||
fm.ascent = -mHeight;
|
||||
fm.descent = 0;
|
||||
|
||||
fm.top = fm.ascent;
|
||||
fm.bottom = 0;
|
||||
}
|
||||
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(
|
||||
Canvas canvas,
|
||||
CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
float x,
|
||||
int top,
|
||||
int y,
|
||||
int bottom,
|
||||
Paint paint) {
|
||||
if (mDrawable == null) {
|
||||
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(mUri)
|
||||
.build();
|
||||
|
||||
DraweeController draweeController = mDraweeControllerBuilder
|
||||
.reset()
|
||||
.setOldController(mDraweeHolder.getController())
|
||||
.setCallerContext(mCallerContext)
|
||||
.setImageRequest(imageRequest)
|
||||
.build();
|
||||
mDraweeHolder.setController(draweeController);
|
||||
|
||||
mDrawable = mDraweeHolder.getTopLevelDrawable();
|
||||
mDrawable.setBounds(0, 0, mWidth, mHeight);
|
||||
mDrawable.setCallback(mTextView);
|
||||
}
|
||||
|
||||
// NOTE: This drawing code is copied from DynamicDrawableSpan
|
||||
|
||||
canvas.save();
|
||||
|
||||
int transY = bottom - mDrawable.getBounds().bottom;
|
||||
|
||||
canvas.translate(x, transY);
|
||||
mDrawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* For TextInlineImageSpan we need to update the Span to know that the window is attached and
|
||||
* the TextView that we will set as the callback on the Drawable.
|
||||
*
|
||||
* @param spannable The spannable that may contain TextInlineImageSpans
|
||||
* @param view The view which will be set as the callback for the Drawable
|
||||
*/
|
||||
public static void possiblyUpdateInlineImageSpans(Spannable spannable, TextView view) {
|
||||
TextInlineImageSpan[] spans =
|
||||
spannable.getSpans(0, spannable.length(), TextInlineImageSpan.class);
|
||||
for (TextInlineImageSpan span : spans) {
|
||||
span.onAttachedToWindow();
|
||||
span.mTextView = view;
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Set the textview that will contain this span.
|
||||
*/
|
||||
public abstract void setTextView(TextView textView);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
android_library(
|
||||
name = 'frescosupport',
|
||||
srcs = glob(['*.java']),
|
||||
deps = [
|
||||
react_native_target('java/com/facebook/csslayout:csslayout'),
|
||||
react_native_target('java/com/facebook/react/bridge:bridge'),
|
||||
react_native_target('java/com/facebook/react/common:common'),
|
||||
react_native_target('java/com/facebook/react/uimanager:uimanager'),
|
||||
react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
|
||||
react_native_target('java/com/facebook/react/views/text:text'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
||||
|
||||
project_config(
|
||||
src_target = ':frescosupport',
|
||||
)
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* 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.views.textfrescosupport;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.facebook.common.util.UriUtil;
|
||||
import com.facebook.csslayout.CSSNode;
|
||||
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.views.text.ReactTextInlineImageShadowNode;
|
||||
import com.facebook.react.views.text.TextInlineImageSpan;
|
||||
|
||||
/**
|
||||
* {@link CSSNode} that represents an inline image. Loading is done using Fresco.
|
||||
*
|
||||
*/
|
||||
public class FrescoBasedReactTextInlineImageShadowNode extends ReactTextInlineImageShadowNode {
|
||||
|
||||
private @Nullable Uri mUri;
|
||||
private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
|
||||
private final @Nullable Object mCallerContext;
|
||||
|
||||
public FrescoBasedReactTextInlineImageShadowNode(
|
||||
AbstractDraweeControllerBuilder draweeControllerBuilder,
|
||||
@Nullable Object callerContext) {
|
||||
mDraweeControllerBuilder = draweeControllerBuilder;
|
||||
mCallerContext = callerContext;
|
||||
}
|
||||
|
||||
@ReactProp(name = "src")
|
||||
public void setSource(@Nullable String source) {
|
||||
Uri uri = null;
|
||||
if (source != null) {
|
||||
try {
|
||||
uri = Uri.parse(source);
|
||||
// Verify scheme is set, so that relative uri (used by static resources) are not handled.
|
||||
if (uri.getScheme() == null) {
|
||||
uri = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore malformed uri, then attempt to extract resource ID.
|
||||
}
|
||||
if (uri == null) {
|
||||
uri = getResourceDrawableUri(getThemedContext(), source);
|
||||
}
|
||||
}
|
||||
if (uri != mUri) {
|
||||
markUpdated();
|
||||
}
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
public @Nullable Uri getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
// TODO: t9053573 is tracking that this code should be shared
|
||||
private static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
name = name.toLowerCase(Locale.getDefault()).replace("-", "_");
|
||||
int resId = context.getResources().getIdentifier(
|
||||
name,
|
||||
"drawable",
|
||||
context.getPackageName());
|
||||
return new Uri.Builder()
|
||||
.scheme(UriUtil.LOCAL_RESOURCE_SCHEME)
|
||||
.path(String.valueOf(resId))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextInlineImageSpan buildInlineImageSpan() {
|
||||
Resources resources = getThemedContext().getResources();
|
||||
int height = (int) Math.ceil(getStyleHeight());
|
||||
int width = (int) Math.ceil(getStyleWidth());
|
||||
return new FrescoBasedReactTextInlineImageSpan(
|
||||
resources,
|
||||
height,
|
||||
width,
|
||||
getUri(),
|
||||
getDraweeControllerBuilder(),
|
||||
getCallerContext());
|
||||
}
|
||||
|
||||
public AbstractDraweeControllerBuilder getDraweeControllerBuilder() {
|
||||
return mDraweeControllerBuilder;
|
||||
}
|
||||
|
||||
public @Nullable Object getCallerContext() {
|
||||
return mCallerContext;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* 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.views.textfrescosupport;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.interfaces.DraweeController;
|
||||
import com.facebook.drawee.view.DraweeHolder;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
import com.facebook.react.views.text.TextInlineImageSpan;
|
||||
|
||||
/**
|
||||
* FrescoBasedTextInlineImageSpan is a span for Images that are inside <Text/>. It computes
|
||||
* its size based on the input size. When it is time to draw, it will use the Fresco framework to
|
||||
* get the right Drawable and let that draw.
|
||||
*
|
||||
* Since Fresco needs to callback to the TextView that contains this, in the ViewManager, you must
|
||||
* tell the Span about the TextView
|
||||
*
|
||||
* Note: It borrows code from DynamicDrawableSpan and if that code updates how it computes size or
|
||||
* draws, we need to update this as well.
|
||||
*/
|
||||
public class FrescoBasedReactTextInlineImageSpan extends TextInlineImageSpan {
|
||||
|
||||
private @Nullable Drawable mDrawable;
|
||||
private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
|
||||
private final DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;
|
||||
private final @Nullable Object mCallerContext;
|
||||
|
||||
private int mHeight;
|
||||
private Uri mUri;
|
||||
private int mWidth;
|
||||
|
||||
private @Nullable TextView mTextView;
|
||||
|
||||
public FrescoBasedReactTextInlineImageSpan(
|
||||
Resources resources,
|
||||
int height,
|
||||
int width,
|
||||
@Nullable Uri uri,
|
||||
AbstractDraweeControllerBuilder draweeControllerBuilder,
|
||||
@Nullable Object callerContext) {
|
||||
mDraweeHolder = new DraweeHolder(
|
||||
GenericDraweeHierarchyBuilder.newInstance(resources)
|
||||
.build()
|
||||
);
|
||||
mDraweeControllerBuilder = draweeControllerBuilder;
|
||||
mCallerContext = callerContext;
|
||||
|
||||
mHeight = height;
|
||||
mWidth = width;
|
||||
mUri = (uri != null) ? uri : Uri.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ReactTextView that holds this ImageSpan is responsible for passing these methods on so
|
||||
* that we can do proper lifetime management for Fresco
|
||||
*/
|
||||
public void onDetachedFromWindow() {
|
||||
mDraweeHolder.onDetach();
|
||||
}
|
||||
|
||||
public void onStartTemporaryDetach() {
|
||||
mDraweeHolder.onDetach();
|
||||
}
|
||||
|
||||
public void onAttachedToWindow() {
|
||||
mDraweeHolder.onAttach();
|
||||
}
|
||||
|
||||
public void onFinishTemporaryDetach() {
|
||||
mDraweeHolder.onAttach();
|
||||
}
|
||||
|
||||
public @Nullable Drawable getDrawable() {
|
||||
return mDrawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(
|
||||
Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
|
||||
// NOTE: This getSize code is copied from DynamicDrawableSpan and modified to not use a Drawable
|
||||
|
||||
if (fm != null) {
|
||||
fm.ascent = -mHeight;
|
||||
fm.descent = 0;
|
||||
|
||||
fm.top = fm.ascent;
|
||||
fm.bottom = 0;
|
||||
}
|
||||
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
public void setTextView(TextView textView) {
|
||||
mTextView = textView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(
|
||||
Canvas canvas,
|
||||
CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
float x,
|
||||
int top,
|
||||
int y,
|
||||
int bottom,
|
||||
Paint paint) {
|
||||
if (mDrawable == null) {
|
||||
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(mUri)
|
||||
.build();
|
||||
|
||||
DraweeController draweeController = mDraweeControllerBuilder
|
||||
.reset()
|
||||
.setOldController(mDraweeHolder.getController())
|
||||
.setCallerContext(mCallerContext)
|
||||
.setImageRequest(imageRequest)
|
||||
.build();
|
||||
mDraweeHolder.setController(draweeController);
|
||||
|
||||
mDrawable = mDraweeHolder.getTopLevelDrawable();
|
||||
mDrawable.setBounds(0, 0, mWidth, mHeight);
|
||||
mDrawable.setCallback(mTextView);
|
||||
}
|
||||
|
||||
// NOTE: This drawing code is copied from DynamicDrawableSpan
|
||||
|
||||
canvas.save();
|
||||
|
||||
int transY = bottom - mDrawable.getBounds().bottom;
|
||||
|
||||
canvas.translate(x, transY);
|
||||
mDrawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
package com.facebook.react.views.textfrescosupport;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -19,24 +19,24 @@ import com.facebook.react.uimanager.ThemedReactContext;
|
|||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
/**
|
||||
* Manages Images embedded in Text nodes. Since they are used only as a virtual nodes any type of
|
||||
* native view operation will throw an {@link IllegalStateException}
|
||||
* Manages Images embedded in Text nodes using Fresco. Since they are used only as a virtual nodes
|
||||
* any type of native view operation will throw an {@link IllegalStateException}.
|
||||
*/
|
||||
public class ReactTextInlineImageViewManager
|
||||
extends ViewManager<View, ReactTextInlineImageShadowNode> {
|
||||
public class FrescoBasedReactTextInlineImageViewManager
|
||||
extends ViewManager<View, FrescoBasedReactTextInlineImageShadowNode> {
|
||||
|
||||
static final String REACT_CLASS = "RCTTextInlineImage";
|
||||
|
||||
private final @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder;
|
||||
private final @Nullable Object mCallerContext;
|
||||
|
||||
public ReactTextInlineImageViewManager() {
|
||||
public FrescoBasedReactTextInlineImageViewManager() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public ReactTextInlineImageViewManager(
|
||||
@Nullable AbstractDraweeControllerBuilder draweeControllerBuilder,
|
||||
@Nullable Object callerContext) {
|
||||
public FrescoBasedReactTextInlineImageViewManager(
|
||||
@Nullable AbstractDraweeControllerBuilder draweeControllerBuilder,
|
||||
@Nullable Object callerContext) {
|
||||
mDraweeControllerBuilder = draweeControllerBuilder;
|
||||
mCallerContext = callerContext;
|
||||
}
|
||||
|
@ -52,18 +52,18 @@ public class ReactTextInlineImageViewManager
|
|||
}
|
||||
|
||||
@Override
|
||||
public ReactTextInlineImageShadowNode createShadowNodeInstance() {
|
||||
return new ReactTextInlineImageShadowNode(
|
||||
(mDraweeControllerBuilder != null) ?
|
||||
mDraweeControllerBuilder :
|
||||
Fresco.newDraweeControllerBuilder(),
|
||||
mCallerContext
|
||||
public FrescoBasedReactTextInlineImageShadowNode createShadowNodeInstance() {
|
||||
return new FrescoBasedReactTextInlineImageShadowNode(
|
||||
(mDraweeControllerBuilder != null) ?
|
||||
mDraweeControllerBuilder :
|
||||
Fresco.newDraweeControllerBuilder(),
|
||||
mCallerContext
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ReactTextInlineImageShadowNode> getShadowNodeClass() {
|
||||
return ReactTextInlineImageShadowNode.class;
|
||||
public Class<FrescoBasedReactTextInlineImageShadowNode> getShadowNodeClass() {
|
||||
return FrescoBasedReactTextInlineImageShadowNode.class;
|
||||
}
|
||||
|
||||
@Override
|
|
@ -43,8 +43,8 @@ import com.facebook.react.uimanager.ViewProps;
|
|||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.views.text.DefaultStyleValuesUtil;
|
||||
import com.facebook.react.views.text.ReactTextUpdate;
|
||||
import com.facebook.react.views.text.TextInlineImageSpan;
|
||||
import com.facebook.react.views.text.ReactTextUpdate;
|
||||
|
||||
/**
|
||||
* Manages instances of TextInput.
|
||||
|
|
Loading…
Reference in New Issue