Text to Spannable conversion is now using PRIORITY flag to enforce the order of spans
Summary: When we convert nested <Text> components to Spannable object we must enforce the order of spans somehow, otherwise we will have Spannable object with unpredictable order of spans, which will produce unpredictalbe text layout. We can do it only using `Spannable.SPAN_PRIORITY` feature because Spannable objects do not maintain the order of spans internally. We also have to fix this to implement autoexpandable <TextInput>. Reviewed By: achen1 Differential Revision: D5811172 fbshipit-source-id: 5bc68b869e58aba27d6986581af9fe3343d116a7
This commit is contained in:
parent
3ff463f6a0
commit
7efd4fabfd
|
@ -231,6 +231,27 @@ class TextExample extends React.Component<{}> {
|
|||
<RNTesterBlock title="Nested">
|
||||
<Text onPress={() => console.log('1st')}>
|
||||
(Normal text,
|
||||
<Text style={{color: 'red', fontWeight: 'bold'}}>
|
||||
(R)red
|
||||
<Text style={{color: 'green', fontWeight: 'normal'}}>
|
||||
(G)green
|
||||
<Text style={{color: 'blue', fontWeight: 'bold'}}>
|
||||
(B)blue
|
||||
<Text style={{color: 'cyan', fontWeight: 'normal'}}>
|
||||
(C)cyan
|
||||
<Text style={{color: 'magenta', fontWeight: 'bold'}}>
|
||||
(M)magenta
|
||||
<Text style={{color: 'yellow', fontWeight: 'normal'}}>
|
||||
(Y)yellow
|
||||
<Text style={{color: 'black', fontWeight: 'bold'}}>
|
||||
(K)black
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={{fontWeight: 'bold'}} onPress={() => console.log('2nd')}>
|
||||
(and bold
|
||||
<Text style={{fontStyle: 'italic', fontSize: 11, color: '#527fe4'}} onPress={() => console.log('3rd')}>
|
||||
|
|
|
@ -65,13 +65,17 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
|||
this.what = what;
|
||||
}
|
||||
|
||||
public void execute(SpannableStringBuilder sb) {
|
||||
public void execute(SpannableStringBuilder sb, int priority) {
|
||||
// All spans will automatically extend to the right of the text, but not the left - except
|
||||
// for spans that start at the beginning of the text.
|
||||
int spanFlags = Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
|
||||
if (start == 0) {
|
||||
spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE;
|
||||
}
|
||||
|
||||
spanFlags &= ~Spannable.SPAN_PRIORITY;
|
||||
spanFlags |= (priority << Spannable.SPAN_PRIORITY_SHIFT) & Spannable.SPAN_PRIORITY;
|
||||
|
||||
sb.setSpan(what, start, end, spanFlags);
|
||||
}
|
||||
}
|
||||
|
@ -167,6 +171,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
|||
// 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<>();
|
||||
|
||||
buildSpannedFromShadowNode(textShadowNode, sb, ops);
|
||||
|
||||
if (text != null) {
|
||||
|
@ -174,22 +179,20 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
|||
}
|
||||
|
||||
if (textShadowNode.mFontSize == UNSET) {
|
||||
sb.setSpan(
|
||||
new AbsoluteSizeSpan(
|
||||
textShadowNode.mAllowFontScaling
|
||||
? (int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP))
|
||||
: (int) Math.ceil(PixelUtil.toPixelFromDIP(ViewDefaults.FONT_SIZE_SP))),
|
||||
0,
|
||||
sb.length(),
|
||||
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
int defaultFontSize =
|
||||
textShadowNode.mAllowFontScaling
|
||||
? (int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP))
|
||||
: (int) Math.ceil(PixelUtil.toPixelFromDIP(ViewDefaults.FONT_SIZE_SP));
|
||||
|
||||
ops.add(new SetSpanOperation(0, sb.length(), new AbsoluteSizeSpan(defaultFontSize)));
|
||||
}
|
||||
|
||||
textShadowNode.mContainsImages = false;
|
||||
textShadowNode.mHeightOfTallestInlineImage = Float.NaN;
|
||||
|
||||
// While setting the Spans on the final text, we also check whether any of them are images
|
||||
for (int i = ops.size() - 1; i >= 0; i--) {
|
||||
SetSpanOperation op = ops.get(i);
|
||||
// While setting the Spans on the final text, we also check whether any of them are images.
|
||||
int priority = 0;
|
||||
for (SetSpanOperation op : ops) {
|
||||
if (op.what instanceof TextInlineImageSpan) {
|
||||
int height = ((TextInlineImageSpan) op.what).getHeight();
|
||||
textShadowNode.mContainsImages = true;
|
||||
|
@ -198,8 +201,13 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
|||
textShadowNode.mHeightOfTallestInlineImage = height;
|
||||
}
|
||||
}
|
||||
op.execute(sb);
|
||||
|
||||
// Actual order of calling {@code execute} does NOT matter,
|
||||
// but the {@code priority} DOES matter.
|
||||
op.execute(sb, priority);
|
||||
priority++;
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue