mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Android: Add a maxFontSizeMultiplier prop to <Text> and <TextInput> (#23069)
Summary: Equivalent of this iOS PR: https://github.com/facebook/react-native/pull/20915 Motivation: ---------- Whenever a user changes the system font size to its maximum allowable setting, React Native apps that allow font scaling can become unusable because the text gets too big. Experimenting with a native app like iMessage on iOS, the font size used for non-body text (e.g. header, navigational elements) is capped while the body text (e.g. text in the message bubbles) is allowed to grow. This PR introduces a new prop on `<Text>` and `<TextInput>` called `maxFontSizeMultiplier`. This enables devs to set the maximum allowed text scale factor on a Text/TextInput. The default is 0 which means no limit. Pull Request resolved: https://github.com/facebook/react-native/pull/23069 Differential Revision: D13748513 Pulled By: mdvacca fbshipit-source-id: 8dd5d6d97bf79387d9a2236fa2e586ccb01afde9
This commit is contained in:
committed by
Facebook Github Bot
parent
5bc709d517
commit
4936d284df
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
|
||||
/**
|
||||
@@ -42,10 +43,21 @@ public class PixelUtil {
|
||||
* Convert from SP to PX
|
||||
*/
|
||||
public static float toPixelFromSP(float value) {
|
||||
return TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_SP,
|
||||
value,
|
||||
DisplayMetricsHolder.getWindowDisplayMetrics());
|
||||
return toPixelFromSP(value, Float.NaN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from SP to PX
|
||||
*/
|
||||
public static float toPixelFromSP(float value, float maxFontScale) {
|
||||
DisplayMetrics displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics();
|
||||
float scaledDensity = displayMetrics.scaledDensity;
|
||||
float currentFontScale = scaledDensity / displayMetrics.density;
|
||||
if (maxFontScale >= 1 && maxFontScale < currentFontScale) {
|
||||
scaledDensity = displayMetrics.density * maxFontScale;
|
||||
}
|
||||
|
||||
return value * scaledDensity;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -106,6 +106,7 @@ public class ViewProps {
|
||||
public static final String VISIBLE = "visible";
|
||||
|
||||
public static final String ALLOW_FONT_SCALING = "allowFontScaling";
|
||||
public static final String MAX_FONT_SIZE_MULTIPLIER = "maxFontSizeMultiplier";
|
||||
public static final String INCLUDE_FONT_PADDING = "includeFontPadding";
|
||||
|
||||
public static final String BORDER_WIDTH = "borderWidth";
|
||||
|
||||
@@ -349,6 +349,14 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.MAX_FONT_SIZE_MULTIPLIER, defaultFloat = Float.NaN)
|
||||
public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
|
||||
if (maxFontSizeMultiplier != mTextAttributes.getMaxFontSizeMultiplier()) {
|
||||
mTextAttributes.setMaxFontSizeMultiplier(maxFontSizeMultiplier);
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.TEXT_ALIGN)
|
||||
public void setTextAlign(@Nullable String textAlign) {
|
||||
if (textAlign == null || "auto".equals(textAlign)) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ViewDefaults;
|
||||
|
||||
@@ -15,13 +16,17 @@ import com.facebook.react.uimanager.ViewDefaults;
|
||||
* to child so inheritance can be implemented correctly. An example complexity that causes a prop
|
||||
* to end up in TextAttributes is when multiple props need to be considered together to determine
|
||||
* the rendered aka effective value. For example, to figure out the rendered/effective font size,
|
||||
* you need to take into account the fontSize and allowFontScaling props.
|
||||
* you need to take into account the fontSize, maxFontSizeMultiplier, and allowFontScaling props.
|
||||
*/
|
||||
public class TextAttributes {
|
||||
// Setting the default to 0 indicates that there is no max.
|
||||
public static final float DEFAULT_MAX_FONT_SIZE_MULTIPLIER = 0.0f;
|
||||
|
||||
private boolean mAllowFontScaling = true;
|
||||
private float mFontSize = Float.NaN;
|
||||
private float mLineHeight = Float.NaN;
|
||||
private float mLetterSpacing = Float.NaN;
|
||||
private float mMaxFontSizeMultiplier = Float.NaN;
|
||||
private float mHeightOfTallestInlineImage = Float.NaN;
|
||||
|
||||
public TextAttributes() {
|
||||
@@ -37,6 +42,7 @@ public class TextAttributes {
|
||||
result.mFontSize = !Float.isNaN(child.mFontSize) ? child.mFontSize : mFontSize;
|
||||
result.mLineHeight = !Float.isNaN(child.mLineHeight) ? child.mLineHeight : mLineHeight;
|
||||
result.mLetterSpacing = !Float.isNaN(child.mLetterSpacing) ? child.mLetterSpacing : mLetterSpacing;
|
||||
result.mMaxFontSizeMultiplier = !Float.isNaN(child.mMaxFontSizeMultiplier) ? child.mMaxFontSizeMultiplier : mMaxFontSizeMultiplier;
|
||||
result.mHeightOfTallestInlineImage = !Float.isNaN(child.mHeightOfTallestInlineImage) ? child.mHeightOfTallestInlineImage : mHeightOfTallestInlineImage;
|
||||
|
||||
return result;
|
||||
@@ -77,6 +83,17 @@ public class TextAttributes {
|
||||
mLetterSpacing = value;
|
||||
}
|
||||
|
||||
public float getMaxFontSizeMultiplier() {
|
||||
return mMaxFontSizeMultiplier;
|
||||
}
|
||||
|
||||
public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
|
||||
if (maxFontSizeMultiplier != 0 && maxFontSizeMultiplier < 1) {
|
||||
throw new JSApplicationIllegalArgumentException("maxFontSizeMultiplier must be NaN, 0, or >= 1");
|
||||
}
|
||||
mMaxFontSizeMultiplier = maxFontSizeMultiplier;
|
||||
}
|
||||
|
||||
public float getHeightOfTallestInlineImage() {
|
||||
return mHeightOfTallestInlineImage;
|
||||
}
|
||||
@@ -94,7 +111,7 @@ public class TextAttributes {
|
||||
public int getEffectiveFontSize() {
|
||||
float fontSize = !Float.isNaN(mFontSize) ? mFontSize : ViewDefaults.FONT_SIZE_SP;
|
||||
return mAllowFontScaling
|
||||
? (int) Math.ceil(PixelUtil.toPixelFromSP(fontSize))
|
||||
? (int) Math.ceil(PixelUtil.toPixelFromSP(fontSize, getEffectiveMaxFontSizeMultiplier()))
|
||||
: (int) Math.ceil(PixelUtil.toPixelFromDIP(fontSize));
|
||||
}
|
||||
|
||||
@@ -104,7 +121,7 @@ public class TextAttributes {
|
||||
}
|
||||
|
||||
float lineHeight = mAllowFontScaling
|
||||
? PixelUtil.toPixelFromSP(mLineHeight)
|
||||
? PixelUtil.toPixelFromSP(mLineHeight, getEffectiveMaxFontSizeMultiplier())
|
||||
: PixelUtil.toPixelFromDIP(mLineHeight);
|
||||
|
||||
// Take into account the requested line height
|
||||
@@ -121,7 +138,7 @@ public class TextAttributes {
|
||||
}
|
||||
|
||||
float letterSpacingPixels = mAllowFontScaling
|
||||
? PixelUtil.toPixelFromSP(mLetterSpacing)
|
||||
? PixelUtil.toPixelFromSP(mLetterSpacing, getEffectiveMaxFontSizeMultiplier())
|
||||
: PixelUtil.toPixelFromDIP(mLetterSpacing);
|
||||
|
||||
// `letterSpacingPixels` and `getEffectiveFontSize` are both in pixels,
|
||||
@@ -129,6 +146,13 @@ public class TextAttributes {
|
||||
return letterSpacingPixels / getEffectiveFontSize();
|
||||
}
|
||||
|
||||
// Never returns NaN
|
||||
public float getEffectiveMaxFontSizeMultiplier() {
|
||||
return !Float.isNaN(mMaxFontSizeMultiplier)
|
||||
? mMaxFontSizeMultiplier
|
||||
: DEFAULT_MAX_FONT_SIZE_MULTIPLIER;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return (
|
||||
"TextAttributes {"
|
||||
@@ -140,6 +164,8 @@ public class TextAttributes {
|
||||
+ "\n getEffectiveLetterSpacing(): " + getEffectiveLetterSpacing()
|
||||
+ "\n getLineHeight(): " + getLineHeight()
|
||||
+ "\n getEffectiveLineHeight(): " + getEffectiveLineHeight()
|
||||
+ "\n getMaxFontSizeMultiplier(): " + getMaxFontSizeMultiplier()
|
||||
+ "\n getEffectiveMaxFontSizeMultiplier(): " + getEffectiveMaxFontSizeMultiplier()
|
||||
+ "\n}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -647,6 +647,13 @@ public class ReactEditText extends EditText {
|
||||
applyTextAttributes();
|
||||
}
|
||||
|
||||
public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
|
||||
if (maxFontSizeMultiplier != mTextAttributes.getMaxFontSizeMultiplier()) {
|
||||
mTextAttributes.setMaxFontSizeMultiplier(maxFontSizeMultiplier);
|
||||
applyTextAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyTextAttributes() {
|
||||
// In general, the `getEffective*` functions return `Float.NaN` if the
|
||||
// property hasn't been set.
|
||||
|
||||
+5
@@ -217,6 +217,11 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
||||
view.setTypeface(newTypeface);
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.MAX_FONT_SIZE_MULTIPLIER, defaultFloat = Float.NaN)
|
||||
public void setMaxFontSizeMultiplier(ReactEditText view, float maxFontSizeMultiplier) {
|
||||
view.setMaxFontSizeMultiplier(maxFontSizeMultiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
/* This code was taken from the method setFontWeight of the class ReactTextShadowNode
|
||||
/* TODO: Factor into a common place they can both use
|
||||
|
||||
Reference in New Issue
Block a user