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
This commit is contained in:
Denis Koroskin 2015-12-07 17:32:16 -08:00 committed by Ahmed El-Helw
parent 007318eb52
commit fbe1b61fe1
2 changed files with 109 additions and 29 deletions

View File

@ -17,16 +17,22 @@ import android.text.style.MetricAffectingSpan;
/* package */ final class FontStylingSpan extends MetricAffectingSpan { /* package */ final class FontStylingSpan extends MetricAffectingSpan {
// text property // text property
private final double mTextColor; private double mTextColor = Double.NaN;
private final int mBackgroundColor; private int mBackgroundColor;
// font properties // font properties
private final int mFontSize; private int mFontSize = -1;
private final int mFontStyle; private int mFontStyle = -1;
private final int mFontWeight; private int mFontWeight = -1;
private final @Nullable String mFontFamily; private @Nullable String mFontFamily;
FontStylingSpan( // whether or not mutation is allowed.
private boolean mFrozen = false;
FontStylingSpan() {
}
private FontStylingSpan(
double textColor, double textColor,
int backgroundColor, int backgroundColor,
int fontSize, int fontSize,
@ -41,6 +47,72 @@ import android.text.style.MetricAffectingSpan;
mFontFamily = fontFamily; 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 @Override
public void updateDrawState(TextPaint ds) { public void updateDrawState(TextPaint ds) {
if (!Double.isNaN(mTextColor)) { if (!Double.isNaN(mTextColor)) {

View File

@ -14,6 +14,7 @@ import javax.annotation.Nullable;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ReactProp; 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 ITALIC = "italic";
private static final String NORMAL = "normal"; private static final String NORMAL = "normal";
// TODO: cache CustomStyleSpan and move remove these values from here private FontStylingSpan mFontStylingSpan = new FontStylingSpan();
// (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;
// these 2 are only used between collectText() and applySpans() calls. // these 2 are only used between collectText() and applySpans() calls.
private int mTextBegin; private int mTextBegin;
private int mTextEnd; private int mTextEnd;
RCTVirtualText() {
mFontStylingSpan.setFontSize(getDefaultFontSize());
}
@Override @Override
protected void collectText(SpannableStringBuilder builder) { protected void collectText(SpannableStringBuilder builder) {
int childCount = getChildCount(); int childCount = getChildCount();
@ -59,9 +57,10 @@ import com.facebook.react.uimanager.ViewProps;
return; return;
} }
mFontStylingSpan.freeze();
builder.setSpan( builder.setSpan(
// Future patch: cache last custom style span with a frozen flag mFontStylingSpan,
new FontStylingSpan(mTextColor, mBgColor, mFontSize, mFontStyle, mFontWeight, mFontFamily),
mTextBegin, mTextBegin,
mTextEnd, mTextEnd,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE); Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
@ -83,32 +82,34 @@ import com.facebook.react.uimanager.ViewProps;
fontSize = fontSizeFromSp(fontSizeSp); fontSize = fontSizeFromSp(fontSizeSp);
} }
if (mFontSize != fontSize) { if (mFontStylingSpan.getFontSize() != fontSize) {
mFontSize = fontSize; getSpan().setFontSize(fontSize);
notifyChanged(true); notifyChanged(true);
} }
} }
@ReactProp(name = ViewProps.COLOR, defaultDouble = Double.NaN) @ReactProp(name = ViewProps.COLOR, defaultDouble = Double.NaN)
public void setColor(double textColor) { public void setColor(double textColor) {
if (mTextColor != textColor) { if (mFontStylingSpan.getTextColor() != textColor) {
mTextColor = textColor; getSpan().setTextColor(textColor);
notifyChanged(false); notifyChanged(false);
} }
} }
@ReactProp(name = ViewProps.BACKGROUND_COLOR) @ReactProp(name = ViewProps.BACKGROUND_COLOR)
public void setBackgroundColor(int backgroundColor) { public void setBackgroundColor(int backgroundColor) {
if (mBgColor != backgroundColor) { if (mFontStylingSpan.getBackgroundColor() != backgroundColor) {
mBgColor = backgroundColor; getSpan().setBackgroundColor(backgroundColor);
notifyChanged(false); notifyChanged(false);
} }
} }
@ReactProp(name = ViewProps.FONT_FAMILY) @ReactProp(name = ViewProps.FONT_FAMILY)
public void setFontFamily(@Nullable String fontFamily) { public void setFontFamily(@Nullable String fontFamily) {
mFontFamily = fontFamily; if (!TextUtils.equals(mFontStylingSpan.getFontFamily(), fontFamily)) {
notifyChanged(true); getSpan().setFontFamily(fontFamily);
notifyChanged(true);
}
} }
@ReactProp(name = ViewProps.FONT_WEIGHT) @ReactProp(name = ViewProps.FONT_WEIGHT)
@ -128,8 +129,8 @@ import com.facebook.react.uimanager.ViewProps;
fontWeight = fontWeightNumeric >= 500 ? Typeface.BOLD : Typeface.NORMAL; fontWeight = fontWeightNumeric >= 500 ? Typeface.BOLD : Typeface.NORMAL;
} }
if (mFontWeight != fontWeight) { if (mFontStylingSpan.getFontWeight() != fontWeight) {
mFontWeight = fontWeight; getSpan().setFontWeight(fontWeight);
notifyChanged(true); notifyChanged(true);
} }
} }
@ -147,8 +148,8 @@ import com.facebook.react.uimanager.ViewProps;
throw new RuntimeException("invalid font style " + fontStyleString); throw new RuntimeException("invalid font style " + fontStyleString);
} }
if (mFontStyle != fontStyle) { if (mFontStylingSpan.getFontStyle() != fontStyle) {
mFontStyle = fontStyle; getSpan().setFontStyle(fontStyle);
notifyChanged(true); notifyChanged(true);
} }
} }
@ -161,6 +162,13 @@ import com.facebook.react.uimanager.ViewProps;
return (int) Math.ceil(PixelUtil.toPixelFromSP(sp)); 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 -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
* return the weight. * return the weight.