Android: Send <Text> metrics in onTextLayout events
Summary:
@public
As we're doing in D9440914 (OSS 64a52532fe
), send text metrics in an onTextLayout callback. These can be used by surrounding views for doing complicated layout like:
- displaying a cursor at the end of text
- vertical centering using capheight-baseline
This right now isn't very performant but is only done when `onTextLayout` is set. I plan to optimize it with a capheight and xheight cache in a follow up diff.
Reviewed By: achen1
Differential Revision: D9585613
fbshipit-source-id: aa20535b8371d5aecf15822d66a0d973c9a7eeda
This commit is contained in:
parent
36199d3dda
commit
737f93705c
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.text.Layout;
|
||||
import android.text.TextPaint;
|
||||
import android.util.DisplayMetrics;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
public class FontMetricsUtil {
|
||||
public static WritableArray getFontMetrics(CharSequence text, Layout layout, TextPaint paint, Context context) {
|
||||
DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
WritableArray lines = Arguments.createArray();
|
||||
for (int i = 0; i < layout.getLineCount(); i++) {
|
||||
Rect bounds = new Rect();
|
||||
layout.getLineBounds(i, bounds);
|
||||
|
||||
WritableMap line = Arguments.createMap();
|
||||
TextPaint paintCopy = new TextPaint(paint);
|
||||
paintCopy.setTextSize(paintCopy.getTextSize() * 100);
|
||||
Rect capHeightBounds = new Rect();
|
||||
paintCopy.getTextBounds("T", 0, 1, capHeightBounds);
|
||||
Rect xHeightBounds = new Rect();
|
||||
paintCopy.getTextBounds("x", 0, 1, xHeightBounds);
|
||||
line.putDouble("x", bounds.left / dm.density);
|
||||
line.putDouble("y", bounds.top / dm.density);
|
||||
line.putDouble("width", layout.getLineWidth(i) / dm.density);
|
||||
line.putDouble("height", bounds.height() / dm.density);
|
||||
line.putDouble("descender", layout.getLineDescent(i) / dm.density);
|
||||
line.putDouble("ascender", -layout.getLineAscent(i) / dm.density);
|
||||
line.putDouble("baseline", layout.getLineBaseline(i) / dm.density);
|
||||
line.putDouble(
|
||||
"capHeight", capHeightBounds.height() / 100 * paint.getTextSize() / dm.density);
|
||||
line.putDouble("xHeight", xHeightBounds.height() / 100 * paint.getTextSize() / dm.density);
|
||||
line.putString(
|
||||
"text", text.subSequence(layout.getLineStart(i), layout.getLineEnd(i)).toString());
|
||||
lines.pushMap(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.text.BoringLayout;
|
||||
import android.text.Layout;
|
||||
|
@ -14,13 +15,19 @@ import android.text.Spannable;
|
|||
import android.text.Spanned;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Gravity;
|
||||
import android.widget.TextView;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.uimanager.ReactShadowNodeImpl;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.yoga.YogaConstants;
|
||||
import com.facebook.yoga.YogaDirection;
|
||||
import com.facebook.yoga.YogaMeasureFunction;
|
||||
|
@ -44,6 +51,8 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
|
|||
|
||||
private @Nullable Spannable mPreparedSpannableText;
|
||||
|
||||
private boolean mShouldNotifyOnTextLayout;
|
||||
|
||||
private final YogaMeasureFunction mTextMeasureFunction =
|
||||
new YogaMeasureFunction() {
|
||||
@Override
|
||||
|
@ -127,11 +136,18 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
|
|||
}
|
||||
}
|
||||
|
||||
if (mNumberOfLines != UNSET &&
|
||||
mNumberOfLines < layout.getLineCount()) {
|
||||
return YogaMeasureOutput.make(
|
||||
layout.getWidth(),
|
||||
layout.getLineBottom(mNumberOfLines - 1));
|
||||
if (mShouldNotifyOnTextLayout) {
|
||||
WritableArray lines =
|
||||
FontMetricsUtil.getFontMetrics(text, layout, sTextPaintInstance, getThemedContext());
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putArray("lines", lines);
|
||||
getThemedContext()
|
||||
.getJSModule(RCTEventEmitter.class)
|
||||
.receiveEvent(getReactTag(), "topTextLayout", event);
|
||||
}
|
||||
|
||||
if (mNumberOfLines != UNSET && mNumberOfLines < layout.getLineCount()) {
|
||||
return YogaMeasureOutput.make(layout.getWidth(), layout.getLineBottom(mNumberOfLines - 1));
|
||||
} else {
|
||||
return YogaMeasureOutput.make(layout.getWidth(), layout.getHeight());
|
||||
}
|
||||
|
@ -223,4 +239,9 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
|
|||
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "onTextLayout")
|
||||
public void setShouldNotifyOnTextLayout(boolean shouldNotifyOnTextLayout) {
|
||||
mShouldNotifyOnTextLayout = shouldNotifyOnTextLayout;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,12 @@
|
|||
package com.facebook.react.views.text;
|
||||
|
||||
import android.text.Spannable;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Concrete class for {@link ReactTextAnchorViewManager} which represents view managers of anchor
|
||||
|
@ -58,4 +61,9 @@ public class ReactTextViewManager
|
|||
super.onAfterUpdateTransaction(view);
|
||||
view.updateView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.of("topTextLayout", MapBuilder.of("registrationName", "onTextLayout"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue