Implement `adjustsFontSizeToFit` on Android (#26389)
Summary: This adds support for `adjustsFontSizeToFit` and `minimumFontScale` on Android. The implementation tries to match closely the behaviour on iOS (hardcoded 4px min size for example). It uses a simpler linear algorithm for now, opened to improving it now if it is a deal breaker or in a follow up. See https://twitter.com/janicduplessis/status/1171147709979516929 for a more detailed thread about the implementation ## Changelog [Android] [Added] - Implement `adjustsFontSizeToFit` on Android Pull Request resolved: https://github.com/facebook/react-native/pull/26389 Test Plan: Tested by adding the existing `adjustsFontSizeToFit` example from the iOS text page to android. Also added a case for limiting size by using `maxHeight` instead of `numberOfLines`. Reviewed By: mdvacca Differential Revision: D17285473 Pulled By: JoshuaGross fbshipit-source-id: 43dbdb05e2d6418e9a390d11f921518bfa58e697
This commit is contained in:
Родитель
9f8e4accfa
Коммит
2c1913f0b3
|
@ -16,7 +16,7 @@ const React = require('react');
|
|||
const TextInlineView = require('../../components/TextInlineView');
|
||||
const TextLegend = require('../../components/TextLegend');
|
||||
|
||||
const {StyleSheet, Text, View} = require('react-native');
|
||||
const {LayoutAnimation, StyleSheet, Text, View} = require('react-native');
|
||||
|
||||
class Entity extends React.Component<{|children: React.Node|}> {
|
||||
render() {
|
||||
|
@ -70,10 +70,137 @@ class AttributeToggler extends React.Component<{...}, $FlowFixMeState> {
|
|||
}
|
||||
}
|
||||
|
||||
type AdjustingFontSizeProps = $ReadOnly<{||}>;
|
||||
|
||||
type AdjustingFontSizeState = {|
|
||||
dynamicText: string,
|
||||
shouldRender: boolean,
|
||||
|};
|
||||
|
||||
class AdjustingFontSize extends React.Component<
|
||||
AdjustingFontSizeProps,
|
||||
AdjustingFontSizeState,
|
||||
> {
|
||||
state = {
|
||||
dynamicText: '',
|
||||
shouldRender: true,
|
||||
};
|
||||
|
||||
reset = () => {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
this.setState({
|
||||
shouldRender: false,
|
||||
});
|
||||
setTimeout(() => {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
this.setState({
|
||||
dynamicText: '',
|
||||
shouldRender: true,
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
|
||||
addText = () => {
|
||||
this.setState({
|
||||
dynamicText:
|
||||
this.state.dynamicText +
|
||||
(Math.floor((Math.random() * 10) % 2) ? ' foo' : ' bar'),
|
||||
});
|
||||
};
|
||||
|
||||
removeText = () => {
|
||||
this.setState({
|
||||
dynamicText: this.state.dynamicText.slice(
|
||||
0,
|
||||
this.state.dynamicText.length - 4,
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.shouldRender) {
|
||||
return <View />;
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={{fontSize: 36, marginVertical: 6}}>
|
||||
Truncated text is baaaaad.
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit={true}
|
||||
style={{fontSize: 40, marginVertical: 6}}>
|
||||
Shrinking to fit available space is much better!
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
adjustsFontSizeToFit={true}
|
||||
numberOfLines={1}
|
||||
style={{fontSize: 30, marginVertical: 6}}>
|
||||
{'Add text to me to watch me shrink!' + ' ' + this.state.dynamicText}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
adjustsFontSizeToFit={true}
|
||||
numberOfLines={4}
|
||||
style={{fontSize: 20, marginVertical: 6}}>
|
||||
{'Multiline text component shrinking is supported, watch as this reeeeaaaally loooooong teeeeeeext grooooows and then shriiiinks as you add text to me! ioahsdia soady auydoa aoisyd aosdy ' +
|
||||
' ' +
|
||||
this.state.dynamicText}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
adjustsFontSizeToFit={true}
|
||||
style={{fontSize: 20, marginVertical: 6, maxHeight: 50}}>
|
||||
{'Text limited by height, watch as this reeeeaaaally loooooong teeeeeeext grooooows and then shriiiinks as you add text to me! ioahsdia soady auydoa aoisyd aosdy ' +
|
||||
' ' +
|
||||
this.state.dynamicText}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
adjustsFontSizeToFit={true}
|
||||
numberOfLines={1}
|
||||
style={{marginVertical: 6}}>
|
||||
<Text style={{fontSize: 14}}>
|
||||
{'Differently sized nested elements will shrink together. '}
|
||||
</Text>
|
||||
<Text style={{fontSize: 20}}>
|
||||
{'LARGE TEXT! ' + this.state.dynamicText}
|
||||
</Text>
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
marginTop: 5,
|
||||
marginVertical: 6,
|
||||
}}>
|
||||
<Text style={{backgroundColor: '#ffaaaa'}} onPress={this.reset}>
|
||||
Reset
|
||||
</Text>
|
||||
<Text style={{backgroundColor: '#aaaaff'}} onPress={this.removeText}>
|
||||
Remove Text
|
||||
</Text>
|
||||
<Text style={{backgroundColor: '#aaffaa'}} onPress={this.addText}>
|
||||
Add Text
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TextExample extends React.Component<{...}> {
|
||||
render(): React.Node {
|
||||
return (
|
||||
<RNTesterPage title="<Text>">
|
||||
<RNTesterBlock title="Dynamic Font Size Adjustment">
|
||||
<AdjustingFontSize />
|
||||
</RNTesterBlock>
|
||||
<RNTesterBlock title="Wrap">
|
||||
<Text>
|
||||
The text should wrap if it goes on multiple lines. See, this is
|
||||
|
|
|
@ -222,6 +222,14 @@ class AdjustingFontSize extends React.Component<
|
|||
this.state.dynamicText}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
adjustsFontSizeToFit={true}
|
||||
style={{fontSize: 20, marginVertical: 6, maxHeight: 50}}>
|
||||
{'Text limited by height, watch as this reeeeaaaally loooooong teeeeeeext grooooows and then shriiiinks as you add text to me! ioahsdia soady auydoa aoisyd aosdy ' +
|
||||
' ' +
|
||||
this.state.dynamicText}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
adjustsFontSizeToFit={true}
|
||||
numberOfLines={1}
|
||||
|
|
|
@ -90,6 +90,8 @@ public class ViewProps {
|
|||
public static final String NEEDS_OFFSCREEN_ALPHA_COMPOSITING = "needsOffscreenAlphaCompositing";
|
||||
public static final String NUMBER_OF_LINES = "numberOfLines";
|
||||
public static final String ELLIPSIZE_MODE = "ellipsizeMode";
|
||||
public static final String ADJUSTS_FONT_SIZE_TO_FIT = "adjustsFontSizeToFit";
|
||||
public static final String MINIMUM_FONT_SCALE = "minimumFontScale";
|
||||
public static final String ON = "on";
|
||||
public static final String RESIZE_MODE = "resizeMode";
|
||||
public static final String RESIZE_METHOD = "resizeMethod";
|
||||
|
|
|
@ -337,7 +337,6 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
|||
(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
protected int mJustificationMode =
|
||||
(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE;
|
||||
protected TextTransform mTextTransform = TextTransform.UNSET;
|
||||
|
||||
protected float mTextShadowOffsetDx = 0;
|
||||
protected float mTextShadowOffsetDy = 0;
|
||||
|
@ -347,6 +346,8 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
|||
protected boolean mIsUnderlineTextDecorationSet = false;
|
||||
protected boolean mIsLineThroughTextDecorationSet = false;
|
||||
protected boolean mIncludeFontPadding = true;
|
||||
protected boolean mAdjustsFontSizeToFit = false;
|
||||
protected float mMinimumFontScale = 0;
|
||||
|
||||
/**
|
||||
* mFontStyle can be {@link Typeface#NORMAL} or {@link Typeface#ITALIC}. mFontWeight can be {@link
|
||||
|
@ -627,4 +628,20 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
|||
}
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.ADJUSTS_FONT_SIZE_TO_FIT)
|
||||
public void setAdjustFontSizeToFit(boolean adjustsFontSizeToFit) {
|
||||
if (adjustsFontSizeToFit != mAdjustsFontSizeToFit) {
|
||||
mAdjustsFontSizeToFit = adjustsFontSizeToFit;
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.MINIMUM_FONT_SCALE)
|
||||
public void setMinimumFontScale(float minimumFontScale) {
|
||||
if (minimumFontScale != mMinimumFontScale) {
|
||||
mMinimumFontScale = minimumFontScale;
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,11 @@ public abstract class ReactTextAnchorViewManager<T extends View, C extends React
|
|||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.ADJUSTS_FONT_SIZE_TO_FIT)
|
||||
public void setAdjustFontSizeToFit(ReactTextView view, boolean adjustsFontSizeToFit) {
|
||||
view.setAdjustFontSizeToFit(adjustsFontSizeToFit);
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.TEXT_ALIGN_VERTICAL)
|
||||
public void setTextAlignVertical(ReactTextView view, @Nullable String textAlignVertical) {
|
||||
if (textAlignVertical == null || "auto".equals(textAlignVertical)) {
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.facebook.react.bridge.ReactSoftException;
|
|||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyOptimizer;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
|
@ -66,96 +67,40 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
|
|||
YogaMeasureMode widthMode,
|
||||
float height,
|
||||
YogaMeasureMode heightMode) {
|
||||
|
||||
// TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic)
|
||||
TextPaint textPaint = sTextPaintInstance;
|
||||
textPaint.setTextSize(mTextAttributes.getEffectiveFontSize());
|
||||
Layout layout;
|
||||
Spanned text =
|
||||
Spannable text =
|
||||
Assertions.assertNotNull(
|
||||
mPreparedSpannableText,
|
||||
"Spannable element has not been prepared in onBeforeLayout");
|
||||
BoringLayout.Metrics boring = BoringLayout.isBoring(text, textPaint);
|
||||
float desiredWidth = boring == null ? Layout.getDesiredWidth(text, textPaint) : Float.NaN;
|
||||
|
||||
// technically, width should never be negative, but there is currently a bug in
|
||||
boolean unconstrainedWidth = widthMode == YogaMeasureMode.UNDEFINED || width < 0;
|
||||
Layout layout = measureSpannedText(text, width, widthMode);
|
||||
|
||||
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
|
||||
switch (getTextAlign()) {
|
||||
case Gravity.LEFT:
|
||||
alignment = Layout.Alignment.ALIGN_NORMAL;
|
||||
break;
|
||||
case Gravity.RIGHT:
|
||||
alignment = Layout.Alignment.ALIGN_OPPOSITE;
|
||||
break;
|
||||
case Gravity.CENTER_HORIZONTAL:
|
||||
alignment = Layout.Alignment.ALIGN_CENTER;
|
||||
break;
|
||||
}
|
||||
if (mAdjustsFontSizeToFit) {
|
||||
int initialFontSize = mTextAttributes.getEffectiveFontSize();
|
||||
int currentFontSize = mTextAttributes.getEffectiveFontSize();
|
||||
// Minimum font size is 4pts to match the iOS implementation.
|
||||
int minimumFontSize =
|
||||
(int) Math.max(mMinimumFontScale * initialFontSize, PixelUtil.toPixelFromDIP(4));
|
||||
while (currentFontSize > minimumFontSize
|
||||
&& (mNumberOfLines != UNSET && layout.getLineCount() > mNumberOfLines
|
||||
|| heightMode != YogaMeasureMode.UNDEFINED && layout.getHeight() > height)) {
|
||||
// TODO: We could probably use a smarter algorithm here. This will require 0(n)
|
||||
// measurements
|
||||
// based on the number of points the font size needs to be reduced by.
|
||||
currentFontSize = currentFontSize - (int) PixelUtil.toPixelFromDIP(1);
|
||||
|
||||
if (boring == null
|
||||
&& (unconstrainedWidth
|
||||
|| (!YogaConstants.isUndefined(desiredWidth) && desiredWidth <= width))) {
|
||||
// Is used when the width is not known and the text is not boring, ie. if it contains
|
||||
// unicode characters.
|
||||
|
||||
int hintWidth = (int) Math.ceil(desiredWidth);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
layout =
|
||||
new StaticLayout(
|
||||
text, textPaint, hintWidth, alignment, 1.f, 0.f, mIncludeFontPadding);
|
||||
} else {
|
||||
StaticLayout.Builder builder =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth)
|
||||
.setAlignment(alignment)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(mIncludeFontPadding)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder.setJustificationMode(mJustificationMode);
|
||||
float ratio = (float) currentFontSize / (float) initialFontSize;
|
||||
ReactAbsoluteSizeSpan[] sizeSpans =
|
||||
text.getSpans(0, text.length(), ReactAbsoluteSizeSpan.class);
|
||||
for (ReactAbsoluteSizeSpan span : sizeSpans) {
|
||||
text.setSpan(
|
||||
new ReactAbsoluteSizeSpan(
|
||||
(int) Math.max((span.getSize() * ratio), minimumFontSize)),
|
||||
text.getSpanStart(span),
|
||||
text.getSpanEnd(span),
|
||||
text.getSpanFlags(span));
|
||||
text.removeSpan(span);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setUseLineSpacingFromFallbacks(true);
|
||||
}
|
||||
layout = builder.build();
|
||||
}
|
||||
|
||||
} else if (boring != null && (unconstrainedWidth || boring.width <= width)) {
|
||||
// Is used for single-line, boring text when the width is either unknown or bigger
|
||||
// than the width of the text.
|
||||
layout =
|
||||
BoringLayout.make(
|
||||
text,
|
||||
textPaint,
|
||||
boring.width,
|
||||
alignment,
|
||||
1.f,
|
||||
0.f,
|
||||
boring,
|
||||
mIncludeFontPadding);
|
||||
} else {
|
||||
// Is used for multiline, boring text and the width is known.
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
layout =
|
||||
new StaticLayout(
|
||||
text, textPaint, (int) width, alignment, 1.f, 0.f, mIncludeFontPadding);
|
||||
} else {
|
||||
StaticLayout.Builder builder =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, (int) width)
|
||||
.setAlignment(alignment)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(mIncludeFontPadding)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setUseLineSpacingFromFallbacks(true);
|
||||
}
|
||||
layout = builder.build();
|
||||
layout = measureSpannedText(text, width, widthMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,6 +146,89 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
|
|||
}
|
||||
}
|
||||
|
||||
private Layout measureSpannedText(Spannable text, float width, YogaMeasureMode widthMode) {
|
||||
// TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic)
|
||||
TextPaint textPaint = sTextPaintInstance;
|
||||
textPaint.setTextSize(mTextAttributes.getEffectiveFontSize());
|
||||
Layout layout;
|
||||
BoringLayout.Metrics boring = BoringLayout.isBoring(text, textPaint);
|
||||
float desiredWidth = boring == null ? Layout.getDesiredWidth(text, textPaint) : Float.NaN;
|
||||
|
||||
// technically, width should never be negative, but there is currently a bug in
|
||||
boolean unconstrainedWidth = widthMode == YogaMeasureMode.UNDEFINED || width < 0;
|
||||
|
||||
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
|
||||
switch (getTextAlign()) {
|
||||
case Gravity.LEFT:
|
||||
alignment = Layout.Alignment.ALIGN_NORMAL;
|
||||
break;
|
||||
case Gravity.RIGHT:
|
||||
alignment = Layout.Alignment.ALIGN_OPPOSITE;
|
||||
break;
|
||||
case Gravity.CENTER_HORIZONTAL:
|
||||
alignment = Layout.Alignment.ALIGN_CENTER;
|
||||
break;
|
||||
}
|
||||
|
||||
if (boring == null
|
||||
&& (unconstrainedWidth
|
||||
|| (!YogaConstants.isUndefined(desiredWidth) && desiredWidth <= width))) {
|
||||
// Is used when the width is not known and the text is not boring, ie. if it contains
|
||||
// unicode characters.
|
||||
|
||||
int hintWidth = (int) Math.ceil(desiredWidth);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
layout =
|
||||
new StaticLayout(text, textPaint, hintWidth, alignment, 1.f, 0.f, mIncludeFontPadding);
|
||||
} else {
|
||||
StaticLayout.Builder builder =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth)
|
||||
.setAlignment(alignment)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(mIncludeFontPadding)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder.setJustificationMode(mJustificationMode);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setUseLineSpacingFromFallbacks(true);
|
||||
}
|
||||
layout = builder.build();
|
||||
}
|
||||
|
||||
} else if (boring != null && (unconstrainedWidth || boring.width <= width)) {
|
||||
// Is used for single-line, boring text when the width is either unknown or bigger
|
||||
// than the width of the text.
|
||||
layout =
|
||||
BoringLayout.make(
|
||||
text, textPaint, boring.width, alignment, 1.f, 0.f, boring, mIncludeFontPadding);
|
||||
} else {
|
||||
// Is used for multiline, boring text and the width is known.
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
layout =
|
||||
new StaticLayout(
|
||||
text, textPaint, (int) width, alignment, 1.f, 0.f, mIncludeFontPadding);
|
||||
} else {
|
||||
StaticLayout.Builder builder =
|
||||
StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, (int) width)
|
||||
.setAlignment(alignment)
|
||||
.setLineSpacing(0.f, 1.f)
|
||||
.setIncludePad(mIncludeFontPadding)
|
||||
.setBreakStrategy(mTextBreakStrategy)
|
||||
.setHyphenationFrequency(mHyphenationFrequency);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setUseLineSpacingFromFallbacks(true);
|
||||
}
|
||||
layout = builder.build();
|
||||
}
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
// Return text alignment according to LTR or RTL style
|
||||
private int getTextAlign() {
|
||||
int textAlign = mTextAlign;
|
||||
|
|
|
@ -51,6 +51,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
|
|||
private int mTextAlign = Gravity.NO_GRAVITY;
|
||||
private int mNumberOfLines = ViewDefaults.NUMBER_OF_LINES;
|
||||
private TextUtils.TruncateAt mEllipsizeLocation = TextUtils.TruncateAt.END;
|
||||
private boolean mAdjustsFontSizeToFit = false;
|
||||
private int mLinkifyMaskType = 0;
|
||||
private boolean mNotifyOnInlineViewLayout;
|
||||
|
||||
|
@ -474,6 +475,10 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
|
|||
setMaxLines(mNumberOfLines);
|
||||
}
|
||||
|
||||
public void setAdjustFontSizeToFit(boolean adjustsFontSizeToFit) {
|
||||
mAdjustsFontSizeToFit = adjustsFontSizeToFit;
|
||||
}
|
||||
|
||||
public void setEllipsizeLocation(TextUtils.TruncateAt ellipsizeLocation) {
|
||||
mEllipsizeLocation = ellipsizeLocation;
|
||||
}
|
||||
|
@ -485,7 +490,9 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
|
|||
public void updateView() {
|
||||
@Nullable
|
||||
TextUtils.TruncateAt ellipsizeLocation =
|
||||
mNumberOfLines == ViewDefaults.NUMBER_OF_LINES ? null : mEllipsizeLocation;
|
||||
mNumberOfLines == ViewDefaults.NUMBER_OF_LINES || mAdjustsFontSizeToFit
|
||||
? null
|
||||
: mEllipsizeLocation;
|
||||
setEllipsize(ellipsizeLocation);
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче