From fbe1b61fe140a08dd247cfb9c3ce6f8ddf199b18 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Mon, 7 Dec 2015 17:32:16 -0800 Subject: [PATCH] Cache CustomStyleSpan in RCTVirtualText Summary: @public `RCTVirtualText` is creating a new `CustomStyleSpan` on every `applySpans()` call, which is not very efficient. We can cache and reuse unchanged `CustomStyleSpans` for efficiency. This patch is doing just that. Reviewed By: sriramramani Differential Revision: D2564366 --- .../facebook/react/flat/FontStylingSpan.java | 86 +++++++++++++++++-- .../facebook/react/flat/RCTVirtualText.java | 52 ++++++----- 2 files changed, 109 insertions(+), 29 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FontStylingSpan.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FontStylingSpan.java index 5145e335c..8d364fed7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FontStylingSpan.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FontStylingSpan.java @@ -17,16 +17,22 @@ import android.text.style.MetricAffectingSpan; /* package */ final class FontStylingSpan extends MetricAffectingSpan { // text property - private final double mTextColor; - private final int mBackgroundColor; + private double mTextColor = Double.NaN; + private int mBackgroundColor; // font properties - private final int mFontSize; - private final int mFontStyle; - private final int mFontWeight; - private final @Nullable String mFontFamily; + private int mFontSize = -1; + private int mFontStyle = -1; + private int mFontWeight = -1; + private @Nullable String mFontFamily; - FontStylingSpan( + // whether or not mutation is allowed. + private boolean mFrozen = false; + + FontStylingSpan() { + } + + private FontStylingSpan( double textColor, int backgroundColor, int fontSize, @@ -41,6 +47,72 @@ import android.text.style.MetricAffectingSpan; mFontFamily = fontFamily; } + /* package */ FontStylingSpan mutableCopy() { + return new FontStylingSpan( + mTextColor, + mBackgroundColor, + mFontSize, + mFontStyle, + mFontWeight, + mFontFamily); + } + + /* package */ boolean isFrozen() { + return mFrozen; + } + + /* package */ void freeze() { + mFrozen = true; + } + + /* package */ double getTextColor() { + return mTextColor; + } + + /* package */ void setTextColor(double textColor) { + mTextColor = textColor; + } + + /* package */ int getBackgroundColor() { + return mBackgroundColor; + } + + /* package */ void setBackgroundColor(int backgroundColor) { + mBackgroundColor = backgroundColor; + } + + /* package */ int getFontSize() { + return mFontSize; + } + + /* package */ void setFontSize(int fontSize) { + mFontSize = fontSize; + } + + /* package */ int getFontStyle() { + return mFontStyle; + } + + /* package */ void setFontStyle(int fontStyle) { + mFontStyle = fontStyle; + } + + /* package */ int getFontWeight() { + return mFontWeight; + } + + /* package */ void setFontWeight(int fontWeight) { + mFontWeight = fontWeight; + } + + /* package */ @Nullable String getFontFamily() { + return mFontFamily; + } + + /* package */ void setFontFamily(@Nullable String fontFamily) { + mFontFamily = fontFamily; + } + @Override public void updateDrawState(TextPaint ds) { if (!Double.isNaN(mTextColor)) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java index 5189947b5..95f28c98e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java @@ -14,6 +14,7 @@ import javax.annotation.Nullable; import android.graphics.Typeface; import android.text.Spannable; import android.text.SpannableStringBuilder; +import android.text.TextUtils; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ReactProp; @@ -28,19 +29,16 @@ import com.facebook.react.uimanager.ViewProps; private static final String ITALIC = "italic"; private static final String NORMAL = "normal"; - // TODO: cache CustomStyleSpan and move remove these values from here - // (implemented in a followup patch) - private double mTextColor = Double.NaN; - private int mBgColor; - private int mFontSize = getDefaultFontSize(); - private int mFontStyle = -1; // -1, Typeface.NORMAL or Typeface.ITALIC - private int mFontWeight = -1; // -1, Typeface.NORMAL or Typeface.BOLD - private @Nullable String mFontFamily; + private FontStylingSpan mFontStylingSpan = new FontStylingSpan(); // these 2 are only used between collectText() and applySpans() calls. private int mTextBegin; private int mTextEnd; + RCTVirtualText() { + mFontStylingSpan.setFontSize(getDefaultFontSize()); + } + @Override protected void collectText(SpannableStringBuilder builder) { int childCount = getChildCount(); @@ -59,9 +57,10 @@ import com.facebook.react.uimanager.ViewProps; return; } + mFontStylingSpan.freeze(); + builder.setSpan( - // Future patch: cache last custom style span with a frozen flag - new FontStylingSpan(mTextColor, mBgColor, mFontSize, mFontStyle, mFontWeight, mFontFamily), + mFontStylingSpan, mTextBegin, mTextEnd, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); @@ -83,32 +82,34 @@ import com.facebook.react.uimanager.ViewProps; fontSize = fontSizeFromSp(fontSizeSp); } - if (mFontSize != fontSize) { - mFontSize = fontSize; + if (mFontStylingSpan.getFontSize() != fontSize) { + getSpan().setFontSize(fontSize); notifyChanged(true); } } @ReactProp(name = ViewProps.COLOR, defaultDouble = Double.NaN) public void setColor(double textColor) { - if (mTextColor != textColor) { - mTextColor = textColor; + if (mFontStylingSpan.getTextColor() != textColor) { + getSpan().setTextColor(textColor); notifyChanged(false); } } @ReactProp(name = ViewProps.BACKGROUND_COLOR) public void setBackgroundColor(int backgroundColor) { - if (mBgColor != backgroundColor) { - mBgColor = backgroundColor; + if (mFontStylingSpan.getBackgroundColor() != backgroundColor) { + getSpan().setBackgroundColor(backgroundColor); notifyChanged(false); } } @ReactProp(name = ViewProps.FONT_FAMILY) public void setFontFamily(@Nullable String fontFamily) { - mFontFamily = fontFamily; - notifyChanged(true); + if (!TextUtils.equals(mFontStylingSpan.getFontFamily(), fontFamily)) { + getSpan().setFontFamily(fontFamily); + notifyChanged(true); + } } @ReactProp(name = ViewProps.FONT_WEIGHT) @@ -128,8 +129,8 @@ import com.facebook.react.uimanager.ViewProps; fontWeight = fontWeightNumeric >= 500 ? Typeface.BOLD : Typeface.NORMAL; } - if (mFontWeight != fontWeight) { - mFontWeight = fontWeight; + if (mFontStylingSpan.getFontWeight() != fontWeight) { + getSpan().setFontWeight(fontWeight); notifyChanged(true); } } @@ -147,8 +148,8 @@ import com.facebook.react.uimanager.ViewProps; throw new RuntimeException("invalid font style " + fontStyleString); } - if (mFontStyle != fontStyle) { - mFontStyle = fontStyle; + if (mFontStylingSpan.getFontStyle() != fontStyle) { + getSpan().setFontStyle(fontStyle); notifyChanged(true); } } @@ -161,6 +162,13 @@ import com.facebook.react.uimanager.ViewProps; return (int) Math.ceil(PixelUtil.toPixelFromSP(sp)); } + private FontStylingSpan getSpan() { + if (mFontStylingSpan.isFrozen()) { + mFontStylingSpan = mFontStylingSpan.mutableCopy(); + } + return mFontStylingSpan; + } + /** * Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise * return the weight.