ReactTextView View recycling

Summary:
See previous diff for details on general approach and benchmarks.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D36676887

fbshipit-source-id: b177dcd19f1ea687bf7d2d4f2f637d2924723340
This commit is contained in:
Joshua Gross
2022-05-26 03:40:21 -07:00
committed by Facebook GitHub Bot
parent 7b778fbebb
commit b1701bd20f
4 changed files with 135 additions and 23 deletions
@@ -100,6 +100,8 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
view.setRight(0);
view.setElevation(0);
view.setAnimationMatrix(null);
// setShadowColor
view.setOutlineAmbientShadowColor(Color.BLACK);
view.setOutlineSpotShadowColor(Color.BLACK);
@@ -193,24 +193,25 @@ public abstract class ReactTextAnchorViewManager<T extends View, C extends React
@ReactProp(name = "dataDetectorType")
public void setDataDetectorType(ReactTextView view, @Nullable String type) {
switch (type) {
case "phoneNumber":
view.setLinkifyMask(Linkify.PHONE_NUMBERS);
break;
case "link":
view.setLinkifyMask(Linkify.WEB_URLS);
break;
case "email":
view.setLinkifyMask(Linkify.EMAIL_ADDRESSES);
break;
case "all":
view.setLinkifyMask(Linkify.ALL);
break;
case "none":
default:
view.setLinkifyMask(0);
break;
if (type != null) {
switch (type) {
case "phoneNumber":
view.setLinkifyMask(Linkify.PHONE_NUMBERS);
return;
case "link":
view.setLinkifyMask(Linkify.WEB_URLS);
return;
case "email":
view.setLinkifyMask(Linkify.EMAIL_ADDRESSES);
return;
case "all":
view.setLinkifyMask(Linkify.ALL);
return;
}
}
// "none" case, default, and null type are equivalent.
view.setLinkifyMask(0);
}
@ReactProp(name = "onInlineViewLayout")
@@ -54,23 +54,102 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
private boolean mContainsImages;
private int mDefaultGravityHorizontal;
private int mDefaultGravityVertical;
private int mTextAlign = Gravity.NO_GRAVITY;
private int mNumberOfLines = ViewDefaults.NUMBER_OF_LINES;
private TextUtils.TruncateAt mEllipsizeLocation = TextUtils.TruncateAt.END;
private boolean mAdjustsFontSizeToFit = false;
private int mLinkifyMaskType = 0;
private int mTextAlign;
private int mNumberOfLines;
private TextUtils.TruncateAt mEllipsizeLocation;
private boolean mAdjustsFontSizeToFit;
private int mLinkifyMaskType;
private boolean mNotifyOnInlineViewLayout;
private boolean mTextIsSelectable = false;
private boolean mTextIsSelectable;
private ReactViewBackgroundManager mReactBackgroundManager;
private Spannable mSpanned;
public ReactTextView(Context context) {
super(context);
initView();
}
/**
* Set all default values here as opposed to in the constructor or field defaults. It is important
* that these properties are set during the constructor, but also on-demand whenever an existing
* ReactTextView is recycled.
*/
private void initView() {
if (mReactBackgroundManager != null) {
// make sure old background manager doesn't have any references back to this View
mReactBackgroundManager.cleanup();
}
mReactBackgroundManager = new ReactViewBackgroundManager(this);
mDefaultGravityHorizontal =
getGravity() & (Gravity.HORIZONTAL_GRAVITY_MASK | Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK);
mDefaultGravityVertical = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
mTextAlign = Gravity.NO_GRAVITY;
mNumberOfLines = ViewDefaults.NUMBER_OF_LINES;
mAdjustsFontSizeToFit = false;
mLinkifyMaskType = 0;
mTextIsSelectable = false;
mEllipsizeLocation = TextUtils.TruncateAt.END;
mSpanned = null;
}
/* package */ void recycleView(ReactTextView defaultView) {
// Set default field values
initView();
setForeground(null);
// reset text
setLayoutParams(EMPTY_LAYOUT_PARAMS);
setMovementMethod(defaultView.getMovementMethod());
setBreakStrategy(defaultView.getBreakStrategy());
super.setText(null);
// Call setters to ensure that any super setters are called
setGravityHorizontal(mDefaultGravityHorizontal);
setGravityVertical(mDefaultGravityVertical);
setNumberOfLines(mNumberOfLines);
setAdjustFontSizeToFit(mAdjustsFontSizeToFit);
setLinkifyMask(mLinkifyMaskType);
setTextIsSelectable(mTextIsSelectable);
// Default true:
// https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java#L9347
setIncludeFontPadding(true);
setEnabled(true);
// reset data detectors
setLinkifyMask(0);
setJustificationMode(defaultView.getJustificationMode());
setEllipsizeLocation(mEllipsizeLocation);
// Focus IDs
// Also see in AOSP source:
// https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#4493
setNextFocusDownId(View.NO_ID);
setNextFocusForwardId(View.NO_ID);
setNextFocusRightId(View.NO_ID);
setNextFocusUpId(View.NO_ID);
// https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r96/core/java/android/view/View.java#5491
setElevation(0);
// View flags - defaults are here:
// https://android.googlesource.com/platform/frameworks/base/+/98e54bb941cb6feb07127b75da37833281951d52/core/java/android/view/View.java#5311
// mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED |
// LAYOUT_DIRECTION_INHERIT;
setEnabled(true);
setFocusable(View.FOCUSABLE_AUTO);
// Things that could be set as a result of updateText/setText
setPadding(0, 0, 0, 0);
updateView(); // call after changing ellipsizeLocation in particular
}
private static WritableMap inlineViewJson(
@@ -9,6 +9,7 @@ package com.facebook.react.views.text;
import android.content.Context;
import android.text.Spannable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.R;
import com.facebook.react.bridge.ReadableMap;
@@ -44,8 +45,37 @@ public class ReactTextViewManager
@VisibleForTesting public static final String REACT_CLASS = "RCTText";
private ReactTextView mDefaultViewForRecycling = null;
protected @Nullable ReactTextViewManagerCallback mReactTextViewManagerCallback;
public ReactTextViewManager() {
super();
enableViewRecycling();
}
@Override
protected ReactTextView prepareToRecycleView(
@NonNull ThemedReactContext reactContext, ReactTextView view) {
// TODO: use context as key
if (mDefaultViewForRecycling == null) {
mDefaultViewForRecycling = createViewInstance(reactContext);
}
// BaseViewManager
super.prepareToRecycleView(reactContext, view);
// Resets background and borders
view.recycleView(mDefaultViewForRecycling);
// Defaults from ReactTextAnchorViewManager
setSelectionColor(view, null);
setAndroidHyphenationFrequency(view, null);
return view;
}
@Override
public String getName() {
return REACT_CLASS;