summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Roozbeh Pournader <roozbeh@google.com> 2017-08-24 17:39:14 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2017-08-24 17:39:14 +0000
commit612e67323feadfe2baea8181c0ad4c7cdf98ef1a (patch)
tree89656c8d77c5c63e72e581e56a282ea14796e290
parentd6fc25b757758e2b23423af95a8693bf661c2b44 (diff)
parent22a167cac8f585ffd3ca73e40b82a26c1e09df11 (diff)
Merge "Add a builder for DynamicLayout and switch TextView to it"
-rw-r--r--api/current.txt17
-rw-r--r--api/system-current.txt17
-rw-r--r--api/test-current.txt17
-rw-r--r--core/java/android/text/DynamicLayout.java438
-rw-r--r--core/java/android/text/Layout.java10
-rw-r--r--core/java/android/text/StaticLayout.java153
-rw-r--r--core/java/android/widget/TextView.java17
7 files changed, 523 insertions, 146 deletions
diff --git a/api/current.txt b/api/current.txt
index 27474d69aa97..83b446d6d233 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -41401,6 +41401,21 @@ package android.text {
method public int getTopPadding();
}
+ public static final class DynamicLayout.Builder {
+ method public android.text.DynamicLayout build();
+ method public static android.text.DynamicLayout.Builder obtain(java.lang.CharSequence, android.text.TextPaint, int);
+ method public android.text.DynamicLayout.Builder setAlignment(android.text.Layout.Alignment);
+ method public android.text.DynamicLayout.Builder setBreakStrategy(int);
+ method public android.text.DynamicLayout.Builder setDisplayText(java.lang.CharSequence);
+ method public android.text.DynamicLayout.Builder setEllipsize(android.text.TextUtils.TruncateAt);
+ method public android.text.DynamicLayout.Builder setEllipsizedWidth(int);
+ method public android.text.DynamicLayout.Builder setHyphenationFrequency(int);
+ method public android.text.DynamicLayout.Builder setIncludePad(boolean);
+ method public android.text.DynamicLayout.Builder setJustificationMode(int);
+ method public android.text.DynamicLayout.Builder setLineSpacing(float, float);
+ method public android.text.DynamicLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
+ }
+
public abstract interface Editable implements java.lang.Appendable java.lang.CharSequence android.text.GetChars android.text.Spannable {
method public abstract android.text.Editable append(java.lang.CharSequence);
method public abstract android.text.Editable append(java.lang.CharSequence, int, int);
@@ -41565,6 +41580,8 @@ package android.text {
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
field public static final int BREAK_STRATEGY_HIGH_QUALITY = 1; // 0x1
field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0
+ field public static final float DEFAULT_LINESPACING_ADDITION = 0.0f;
+ field public static final float DEFAULT_LINESPACING_MULTIPLIER = 1.0f;
field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 06c3ce0a4c8d..124f1d23db6d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -45005,6 +45005,21 @@ package android.text {
method public int getTopPadding();
}
+ public static final class DynamicLayout.Builder {
+ method public android.text.DynamicLayout build();
+ method public static android.text.DynamicLayout.Builder obtain(java.lang.CharSequence, android.text.TextPaint, int);
+ method public android.text.DynamicLayout.Builder setAlignment(android.text.Layout.Alignment);
+ method public android.text.DynamicLayout.Builder setBreakStrategy(int);
+ method public android.text.DynamicLayout.Builder setDisplayText(java.lang.CharSequence);
+ method public android.text.DynamicLayout.Builder setEllipsize(android.text.TextUtils.TruncateAt);
+ method public android.text.DynamicLayout.Builder setEllipsizedWidth(int);
+ method public android.text.DynamicLayout.Builder setHyphenationFrequency(int);
+ method public android.text.DynamicLayout.Builder setIncludePad(boolean);
+ method public android.text.DynamicLayout.Builder setJustificationMode(int);
+ method public android.text.DynamicLayout.Builder setLineSpacing(float, float);
+ method public android.text.DynamicLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
+ }
+
public abstract interface Editable implements java.lang.Appendable java.lang.CharSequence android.text.GetChars android.text.Spannable {
method public abstract android.text.Editable append(java.lang.CharSequence);
method public abstract android.text.Editable append(java.lang.CharSequence, int, int);
@@ -45169,6 +45184,8 @@ package android.text {
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
field public static final int BREAK_STRATEGY_HIGH_QUALITY = 1; // 0x1
field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0
+ field public static final float DEFAULT_LINESPACING_ADDITION = 0.0f;
+ field public static final float DEFAULT_LINESPACING_MULTIPLIER = 1.0f;
field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
diff --git a/api/test-current.txt b/api/test-current.txt
index 6847d8eed2f9..b8a606c5e976 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -41665,6 +41665,21 @@ package android.text {
method public int getTopPadding();
}
+ public static final class DynamicLayout.Builder {
+ method public android.text.DynamicLayout build();
+ method public static android.text.DynamicLayout.Builder obtain(java.lang.CharSequence, android.text.TextPaint, int);
+ method public android.text.DynamicLayout.Builder setAlignment(android.text.Layout.Alignment);
+ method public android.text.DynamicLayout.Builder setBreakStrategy(int);
+ method public android.text.DynamicLayout.Builder setDisplayText(java.lang.CharSequence);
+ method public android.text.DynamicLayout.Builder setEllipsize(android.text.TextUtils.TruncateAt);
+ method public android.text.DynamicLayout.Builder setEllipsizedWidth(int);
+ method public android.text.DynamicLayout.Builder setHyphenationFrequency(int);
+ method public android.text.DynamicLayout.Builder setIncludePad(boolean);
+ method public android.text.DynamicLayout.Builder setJustificationMode(int);
+ method public android.text.DynamicLayout.Builder setLineSpacing(float, float);
+ method public android.text.DynamicLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
+ }
+
public abstract interface Editable implements java.lang.Appendable java.lang.CharSequence android.text.GetChars android.text.Spannable {
method public abstract android.text.Editable append(java.lang.CharSequence);
method public abstract android.text.Editable append(java.lang.CharSequence, int, int);
@@ -41829,6 +41844,8 @@ package android.text {
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
field public static final int BREAK_STRATEGY_HIGH_QUALITY = 1; // 0x1
field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0
+ field public static final float DEFAULT_LINESPACING_ADDITION = 0.0f;
+ field public static final float DEFAULT_LINESPACING_MULTIPLIER = 1.0f;
field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index d6a68fb54b7c..661b6085e4f0 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -16,12 +16,17 @@
package android.text;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.style.ReplacementSpan;
import android.text.style.UpdateLayout;
import android.text.style.WrapTogetherSpan;
import android.util.ArraySet;
+import android.util.Pools.SynchronizedPool;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -43,45 +48,276 @@ public class DynamicLayout extends Layout
private static final int BLOCK_MINIMUM_CHARACTER_LENGTH = 400;
/**
- * Make a layout for the specified text that will be updated as
- * the text is changed.
+ * Builder for dynamic layouts. The builder is the preferred pattern for constructing
+ * DynamicLayout objects and should be preferred over the constructors, particularly to access
+ * newer features. To build a dynamic layout, first call {@link #obtain} with the required
+ * arguments (base, paint, and width), then call setters for optional parameters, and finally
+ * {@link #build} to build the DynamicLayout object. Parameters not explicitly set will get
+ * default values.
*/
- public DynamicLayout(CharSequence base,
- TextPaint paint,
- int width, Alignment align,
- float spacingmult, float spacingadd,
+ public static final class Builder {
+ private Builder() {
+ }
+
+ /**
+ * Obtain a builder for constructing DynamicLayout objects.
+ */
+ @NonNull
+ public static Builder obtain(@NonNull CharSequence base, @NonNull TextPaint paint,
+ @IntRange(from = 0) int width) {
+ Builder b = sPool.acquire();
+ if (b == null) {
+ b = new Builder();
+ }
+
+ // set default initial values
+ b.mBase = base;
+ b.mDisplay = base;
+ b.mPaint = paint;
+ b.mWidth = width;
+ b.mAlignment = Alignment.ALIGN_NORMAL;
+ b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
+ b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
+ b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
+ b.mIncludePad = true;
+ b.mEllipsizedWidth = width;
+ b.mEllipsize = null;
+ b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
+ b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
+ b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
+ return b;
+ }
+
+ /**
+ * This method should be called after the layout is finished getting constructed and the
+ * builder needs to be cleaned up and returned to the pool.
+ */
+ private static void recycle(@NonNull Builder b) {
+ b.mBase = null;
+ b.mDisplay = null;
+ b.mPaint = null;
+ sPool.release(b);
+ }
+
+ /**
+ * Set the transformed text (password transformation being the primary example of a
+ * transformation) that will be updated as the base text is changed. The default is the
+ * 'base' text passed to the builder's constructor.
+ *
+ * @param display the transformed text
+ * @return this builder, useful for chaining
+ */
+ @NonNull
+ public Builder setDisplayText(@NonNull CharSequence display) {
+ mDisplay = display;
+ return this;
+ }
+
+ /**
+ * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
+ *
+ * @param alignment Alignment for the resulting {@link DynamicLayout}
+ * @return this builder, useful for chaining
+ */
+ @NonNull
+ public Builder setAlignment(@NonNull Alignment alignment) {
+ mAlignment = alignment;
+ return this;
+ }
+
+ /**
+ * Set the text direction heuristic. The text direction heuristic is used to resolve text
+ * direction per-paragraph based on the input text. The default is
+ * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
+ *
+ * @param textDir text direction heuristic for resolving bidi behavior.
+ * @return this builder, useful for chaining
+ */
+ @NonNull
+ public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
+ mTextDir = textDir;
+ return this;
+ }
+
+ /**
+ * Set line spacing parameters. Each line will have its line spacing multiplied by
+ * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
+ * {@code spacingAdd} and 1.0 for {@code spacingMult}.
+ *
+ * @param spacingAdd the amount of line spacing addition
+ * @param spacingMult the line spacing multiplier
+ * @return this builder, useful for chaining
+ * @see android.widget.TextView#setLineSpacing
+ */
+ @NonNull
+ public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
+ mSpacingAdd = spacingAdd;
+ mSpacingMult = spacingMult;
+ return this;
+ }
+
+ /**
+ * Set whether to include extra space beyond font ascent and descent (which is needed to
+ * avoid clipping in some languages, such as Arabic and Kannada). The default is
+ * {@code true}.
+ *
+ * @param includePad whether to include padding
+ * @return this builder, useful for chaining
+ * @see android.widget.TextView#setIncludeFontPadding
+ */
+ @NonNull
+ public Builder setIncludePad(boolean includePad) {
+ mIncludePad = includePad;
+ return this;
+ }
+
+ /**
+ * Set the width as used for ellipsizing purposes, if it differs from the normal layout
+ * width. The default is the {@code width} passed to {@link #obtain}.
+ *
+ * @param ellipsizedWidth width used for ellipsizing, in pixels
+ * @return this builder, useful for chaining
+ * @see android.widget.TextView#setEllipsize
+ */
+ @NonNull
+ public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
+ mEllipsizedWidth = ellipsizedWidth;
+ return this;
+ }
+
+ /**
+ * Set ellipsizing on the layout. Causes words that are longer than the view is wide, or
+ * exceeding the number of lines (see #setMaxLines) in the case of
+ * {@link android.text.TextUtils.TruncateAt#END} or
+ * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead of broken.
+ * The default is {@code null}, indicating no ellipsis is to be applied.
+ *
+ * @param ellipsize type of ellipsis behavior
+ * @return this builder, useful for chaining
+ * @see android.widget.TextView#setEllipsize
+ */
+ public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
+ mEllipsize = ellipsize;
+ return this;
+ }
+
+ /**
+ * Set break strategy, useful for selecting high quality or balanced paragraph layout
+ * options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
+ *
+ * @param breakStrategy break strategy for paragraph layout
+ * @return this builder, useful for chaining
+ * @see android.widget.TextView#setBreakStrategy
+ */
+ @NonNull
+ public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
+ mBreakStrategy = breakStrategy;
+ return this;
+ }
+
+ /**
+ * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
+ * possible values are defined in {@link Layout}, by constants named with the pattern
+ * {@code HYPHENATION_FREQUENCY_*}. The default is
+ * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
+ *
+ * @param hyphenationFrequency hyphenation frequency for the paragraph
+ * @return this builder, useful for chaining
+ * @see android.widget.TextView#setHyphenationFrequency
+ */
+ @NonNull
+ public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
+ mHyphenationFrequency = hyphenationFrequency;
+ return this;
+ }
+
+ /**
+ * Set paragraph justification mode. The default value is
+ * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
+ * the last line will be displayed with the alignment set by {@link #setAlignment}.
+ *
+ * @param justificationMode justification mode for the paragraph.
+ * @return this builder, useful for chaining.
+ */
+ @NonNull
+ public Builder setJustificationMode(@JustificationMode int justificationMode) {
+ mJustificationMode = justificationMode;
+ return this;
+ }
+
+ /**
+ * Build the {@link DynamicLayout} after options have been set.
+ *
+ * <p>Note: the builder object must not be reused in any way after calling this method.
+ * Setting parameters after calling this method, or calling it a second time on the same
+ * builder object, will likely lead to unexpected results.
+ *
+ * @return the newly constructed {@link DynamicLayout} object
+ */
+ @NonNull
+ public DynamicLayout build() {
+ final DynamicLayout result = new DynamicLayout(this);
+ Builder.recycle(this);
+ return result;
+ }
+
+ private CharSequence mBase;
+ private CharSequence mDisplay;
+ private TextPaint mPaint;
+ private int mWidth;
+ private Alignment mAlignment;
+ private TextDirectionHeuristic mTextDir;
+ private float mSpacingMult;
+ private float mSpacingAdd;
+ private boolean mIncludePad;
+ private int mBreakStrategy;
+ private int mHyphenationFrequency;
+ private int mJustificationMode;
+ private TextUtils.TruncateAt mEllipsize;
+ private int mEllipsizedWidth;
+
+ private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
+
+ private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
+ }
+
+ /**
+ * Make a layout for the specified text that will be updated as the text is changed.
+ */
+ public DynamicLayout(@NonNull CharSequence base,
+ @NonNull TextPaint paint,
+ @IntRange(from = 0) int width, @NonNull Alignment align,
+ @FloatRange(from = 0.0) float spacingmult, float spacingadd,
boolean includepad) {
this(base, base, paint, width, align, spacingmult, spacingadd,
includepad);
}
/**
- * Make a layout for the transformed text (password transformation
- * being the primary example of a transformation)
- * that will be updated as the base text is changed.
+ * Make a layout for the transformed text (password transformation being the primary example of
+ * a transformation) that will be updated as the base text is changed.
*/
- public DynamicLayout(CharSequence base, CharSequence display,
- TextPaint paint,
- int width, Alignment align,
- float spacingmult, float spacingadd,
+ public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
+ @NonNull TextPaint paint,
+ @IntRange(from = 0) int width, @NonNull Alignment align,
+ @FloatRange(from = 0.0) float spacingmult, float spacingadd,
boolean includepad) {
this(base, display, paint, width, align, spacingmult, spacingadd,
includepad, null, 0);
}
/**
- * Make a layout for the transformed text (password transformation
- * being the primary example of a transformation)
- * that will be updated as the base text is changed.
- * If ellipsize is non-null, the Layout will ellipsize the text
- * down to ellipsizedWidth.
+ * Make a layout for the transformed text (password transformation being the primary example of
+ * a transformation) that will be updated as the base text is changed. If ellipsize is non-null,
+ * the Layout will ellipsize the text down to ellipsizedWidth.
*/
- public DynamicLayout(CharSequence base, CharSequence display,
- TextPaint paint,
- int width, Alignment align,
- float spacingmult, float spacingadd,
+ public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
+ @NonNull TextPaint paint,
+ @IntRange(from = 0) int width, @NonNull Alignment align,
+ @FloatRange(from = 0.0) float spacingmult, float spacingadd,
boolean includepad,
- TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
+ @Nullable TextUtils.TruncateAt ellipsize,
+ @IntRange(from = 0) int ellipsizedWidth) {
this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
spacingmult, spacingadd, includepad,
StaticLayout.BREAK_STRATEGY_SIMPLE, StaticLayout.HYPHENATION_FREQUENCY_NONE,
@@ -89,83 +325,119 @@ public class DynamicLayout extends Layout
}
/**
- * Make a layout for the transformed text (password transformation
- * being the primary example of a transformation)
- * that will be updated as the base text is changed.
- * If ellipsize is non-null, the Layout will ellipsize the text
- * down to ellipsizedWidth.
- * *
- * *@hide
+ * Make a layout for the transformed text (password transformation being the primary example of
+ * a transformation) that will be updated as the base text is changed. If ellipsize is non-null,
+ * the Layout will ellipsize the text down to ellipsizedWidth.
+ *
+ * @hide
*/
- public DynamicLayout(CharSequence base, CharSequence display,
- TextPaint paint,
- int width, Alignment align, TextDirectionHeuristic textDir,
- float spacingmult, float spacingadd,
- boolean includepad, int breakStrategy, int hyphenationFrequency,
- int justificationMode, TextUtils.TruncateAt ellipsize,
- int ellipsizedWidth) {
- super((ellipsize == null)
- ? display
- : (display instanceof Spanned)
- ? new SpannedEllipsizer(display)
- : new Ellipsizer(display),
+ public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
+ @NonNull TextPaint paint,
+ @IntRange(from = 0) int width,
+ @NonNull Alignment align, @NonNull TextDirectionHeuristic textDir,
+ @FloatRange(from = 0.0) float spacingmult, float spacingadd,
+ boolean includepad, @BreakStrategy int breakStrategy,
+ @HyphenationFrequency int hyphenationFrequency,
+ @JustificationMode int justificationMode,
+ @Nullable TextUtils.TruncateAt ellipsize,
+ @IntRange(from = 0) int ellipsizedWidth) {
+ super(createEllipsizer(ellipsize, display),
paint, width, align, textDir, spacingmult, spacingadd);
- mBase = base;
+ final Builder b = Builder.obtain(base, paint, width)
+ .setAlignment(align)
+ .setTextDirection(textDir)
+ .setLineSpacing(spacingadd, spacingmult)
+ .setEllipsizedWidth(ellipsizedWidth)
+ .setEllipsize(ellipsize);
mDisplay = display;
-
- if (ellipsize != null) {
- mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
- mEllipsizedWidth = ellipsizedWidth;
- mEllipsizeAt = ellipsize;
- } else {
- mInts = new PackedIntVector(COLUMNS_NORMAL);
- mEllipsizedWidth = width;
- mEllipsizeAt = null;
- }
-
- mObjects = new PackedObjectVector<Directions>(1);
-
mIncludePad = includepad;
mBreakStrategy = breakStrategy;
mJustificationMode = justificationMode;
mHyphenationFrequency = hyphenationFrequency;
- /*
- * This is annoying, but we can't refer to the layout until
- * superclass construction is finished, and the superclass
- * constructor wants the reference to the display text.
- *
- * This will break if the superclass constructor ever actually
- * cares about the content instead of just holding the reference.
- */
- if (ellipsize != null) {
- Ellipsizer e = (Ellipsizer) getText();
+ generate(b);
+
+ Builder.recycle(b);
+ }
+
+ private DynamicLayout(@NonNull Builder b) {
+ super(createEllipsizer(b.mEllipsize, b.mDisplay),
+ b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
+
+ mDisplay = b.mDisplay;
+ mIncludePad = b.mIncludePad;
+ mBreakStrategy = b.mBreakStrategy;
+ mJustificationMode = b.mJustificationMode;
+ mHyphenationFrequency = b.mHyphenationFrequency;
+
+ generate(b);
+ }
+ @NonNull
+ private static CharSequence createEllipsizer(@Nullable TextUtils.TruncateAt ellipsize,
+ @NonNull CharSequence display) {
+ if (ellipsize == null) {
+ return display;
+ } else if (display instanceof Spanned) {
+ return new SpannedEllipsizer(display);
+ } else {
+ return new Ellipsizer(display);
+ }
+ }
+
+ private void generate(@NonNull Builder b) {
+ mBase = b.mBase;
+ if (b.mEllipsize != null) {
+ mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
+ mEllipsizedWidth = b.mEllipsizedWidth;
+ mEllipsizeAt = b.mEllipsize;
+
+ /*
+ * This is annoying, but we can't refer to the layout until superclass construction is
+ * finished, and the superclass constructor wants the reference to the display text.
+ *
+ * In other words, the two Ellipsizer classes in Layout.java need a
+ * (Dynamic|Static)Layout as a parameter to do their calculations, but the Ellipsizers
+ * also need to be the input to the superclass's constructor (Layout). In order to go
+ * around the circular dependency, we construct the Ellipsizer with only one of the
+ * parameters, the text (in createEllipsizer). And we fill in the rest of the needed
+ * information (layout, width, and method) later, here.
+ *
+ * This will break if the superclass constructor ever actually cares about the content
+ * instead of just holding the reference.
+ */
+ final Ellipsizer e = (Ellipsizer) getText();
e.mLayout = this;
- e.mWidth = ellipsizedWidth;
- e.mMethod = ellipsize;
+ e.mWidth = b.mEllipsizedWidth;
+ e.mMethod = b.mEllipsize;
mEllipsize = true;
+ } else {
+ mInts = new PackedIntVector(COLUMNS_NORMAL);
+ mEllipsizedWidth = b.mWidth;
+ mEllipsizeAt = null;
}
- // Initial state is a single line with 0 characters (0 to 0),
- // with top at 0 and bottom at whatever is natural, and
- // undefined ellipsis.
+ mObjects = new PackedObjectVector<Directions>(1);
+
+ // Initial state is a single line with 0 characters (0 to 0), with top at 0 and bottom at
+ // whatever is natural, and undefined ellipsis.
int[] start;
- if (ellipsize != null) {
+ if (b.mEllipsize != null) {
start = new int[COLUMNS_ELLIPSIZE];
start[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
} else {
start = new int[COLUMNS_NORMAL];
}
- Directions[] dirs = new Directions[] { DIRS_ALL_LEFT_TO_RIGHT };
+ final Directions[] dirs = new Directions[] { DIRS_ALL_LEFT_TO_RIGHT };
- Paint.FontMetricsInt fm = paint.getFontMetricsInt();
- int asc = fm.ascent;
- int desc = fm.descent;
+ final Paint.FontMetricsInt fm = b.mFontMetricsInt;
+ b.mPaint.getFontMetricsInt(fm);
+ final int asc = fm.ascent;
+ final int desc = fm.descent;
start[DIR] = DIR_LEFT_TO_RIGHT << DIR_SHIFT;
start[TOP] = 0;
@@ -177,20 +449,22 @@ public class DynamicLayout extends Layout
mObjects.insertAt(0, dirs);
+ final int baseLength = mBase.length();
// Update from 0 characters to whatever the real text is
- reflow(base, 0, 0, base.length());
+ reflow(mBase, 0, 0, baseLength);
- if (base instanceof Spannable) {
+ if (mBase instanceof Spannable) {
if (mWatcher == null)
mWatcher = new ChangeWatcher(this);
// Strip out any watchers for other DynamicLayouts.
- Spannable sp = (Spannable) base;
- ChangeWatcher[] spans = sp.getSpans(0, sp.length(), ChangeWatcher.class);
- for (int i = 0; i < spans.length; i++)
+ final Spannable sp = (Spannable) mBase;
+ final ChangeWatcher[] spans = sp.getSpans(0, baseLength, ChangeWatcher.class);
+ for (int i = 0; i < spans.length; i++) {
sp.removeSpan(spans[i]);
+ }
- sp.setSpan(mWatcher, 0, base.length(),
+ sp.setSpan(mWatcher, 0, baseLength,
Spannable.SPAN_INCLUSIVE_INCLUSIVE |
(PRIORITY << Spannable.SPAN_PRIORITY_SHIFT));
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 5d9c8d88fd1b..04dadc8eb0f4 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -119,6 +119,16 @@ public abstract class Layout {
*/
public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
+ /*
+ * Line spacing multiplier for default line spacing.
+ */
+ public static final float DEFAULT_LINESPACING_MULTIPLIER = 1.0f;
+
+ /*
+ * Line spacing addition for default line spacing.
+ */
+ public static final float DEFAULT_LINESPACING_ADDITION = 0.0f;
+
/**
* Return how wide a layout must be in order to display the specified text with one line per
* paragraph.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 69c04b33dc71..1725d40eec71 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,6 +16,9 @@
package android.text;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Paint;
import android.os.LocaleList;
@@ -49,12 +52,11 @@ public class StaticLayout extends Layout {
static final String TAG = "StaticLayout";
/**
- * Builder for static layouts. The builder is a newer pattern for constructing
- * StaticLayout objects and should be preferred over the constructors,
- * particularly to access newer features. To build a static layout, first
- * call {@link #obtain} with the required arguments (text, paint, and width),
- * then call setters for optional parameters, and finally {@link #build}
- * to build the StaticLayout object. Parameters not explicitly set will get
+ * Builder for static layouts. The builder is the preferred pattern for constructing
+ * StaticLayout objects and should be preferred over the constructors, particularly to access
+ * newer features. To build a static layout, first call {@link #obtain} with the required
+ * arguments (text, paint, and width), then call setters for optional parameters, and finally
+ * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
* default values.
*/
public final static class Builder {
@@ -63,7 +65,7 @@ public class StaticLayout extends Layout {
}
/**
- * Obtain a builder for constructing StaticLayout objects
+ * Obtain a builder for constructing StaticLayout objects.
*
* @param source The text to be laid out, optionally with spans
* @param start The index of the start of the text
@@ -72,8 +74,10 @@ public class StaticLayout extends Layout {
* @param width The width in pixels
* @return a builder object used for constructing the StaticLayout
*/
- public static Builder obtain(CharSequence source, int start, int end, TextPaint paint,
- int width) {
+ @NonNull
+ public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end, @NonNull TextPaint paint,
+ @IntRange(from = 0) int width) {
Builder b = sPool.acquire();
if (b == null) {
b = new Builder();
@@ -87,8 +91,8 @@ public class StaticLayout extends Layout {
b.mWidth = width;
b.mAlignment = Alignment.ALIGN_NORMAL;
b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
- b.mSpacingMult = 1.0f;
- b.mSpacingAdd = 0.0f;
+ b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
+ b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
b.mIncludePad = true;
b.mFallbackLineSpacing = false;
b.mEllipsizedWidth = width;
@@ -102,7 +106,11 @@ public class StaticLayout extends Layout {
return b;
}
- private static void recycle(Builder b) {
+ /**
+ * This method should be called after the layout is finished getting constructed and the
+ * builder needs to be cleaned up and returned to the pool.
+ */
+ private static void recycle(@NonNull Builder b) {
b.mPaint = null;
b.mText = null;
MeasuredText.recycle(b.mMeasuredText);
@@ -139,7 +147,8 @@ public class StaticLayout extends Layout {
*
* @hide
*/
- public Builder setText(CharSequence source, int start, int end) {
+ @NonNull
+ public Builder setText(@NonNull CharSequence source, int start, int end) {
mText = source;
mStart = start;
mEnd = end;
@@ -154,7 +163,8 @@ public class StaticLayout extends Layout {
*
* @hide
*/
- public Builder setPaint(TextPaint paint) {
+ @NonNull
+ public Builder setPaint(@NonNull TextPaint paint) {
mPaint = paint;
return this;
}
@@ -167,7 +177,8 @@ public class StaticLayout extends Layout {
*
* @hide
*/
- public Builder setWidth(int width) {
+ @NonNull
+ public Builder setWidth(@IntRange(from = 0) int width) {
mWidth = width;
if (mEllipsize == null) {
mEllipsizedWidth = width;
@@ -181,34 +192,38 @@ public class StaticLayout extends Layout {
* @param alignment Alignment for the resulting {@link StaticLayout}
* @return this builder, useful for chaining
*/
- public Builder setAlignment(Alignment alignment) {
+ @NonNull
+ public Builder setAlignment(@NonNull Alignment alignment) {
mAlignment = alignment;
return this;
}
/**
* Set the text direction heuristic. The text direction heuristic is used to
- * resolve text direction based per-paragraph based on the input text. The default is
+ * resolve text direction per-paragraph based on the input text. The default is
* {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
*
- * @param textDir text direction heuristic for resolving BiDi behavior.
+ * @param textDir text direction heuristic for resolving bidi behavior.
* @return this builder, useful for chaining
*/
- public Builder setTextDirection(TextDirectionHeuristic textDir) {
+ @NonNull
+ public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
mTextDir = textDir;
return this;
}
/**
- * Set line spacing parameters. The default is 0.0 for {@code spacingAdd}
- * and 1.0 for {@code spacingMult}.
+ * Set line spacing parameters. Each line will have its line spacing multiplied by
+ * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
+ * {@code spacingAdd} and 1.0 for {@code spacingMult}.
*
- * @param spacingAdd line spacing add
- * @param spacingMult line spacing multiplier
+ * @param spacingAdd the amount of line spacing addition
+ * @param spacingMult the line spacing multiplier
* @return this builder, useful for chaining
* @see android.widget.TextView#setLineSpacing
*/
- public Builder setLineSpacing(float spacingAdd, float spacingMult) {
+ @NonNull
+ public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
mSpacingAdd = spacingAdd;
mSpacingMult = spacingMult;
return this;
@@ -223,6 +238,7 @@ public class StaticLayout extends Layout {
* @return this builder, useful for chaining
* @see android.widget.TextView#setIncludeFontPadding
*/
+ @NonNull
public Builder setIncludePad(boolean includePad) {
mIncludePad = includePad;
return this;
@@ -241,6 +257,7 @@ public class StaticLayout extends Layout {
* @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
* @return this builder, useful for chaining
*/
+ @NonNull
public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
mFallbackLineSpacing = useLineSpacingFromFallbacks;
return this;
@@ -255,7 +272,8 @@ public class StaticLayout extends Layout {
* @return this builder, useful for chaining
* @see android.widget.TextView#setEllipsize
*/
- public Builder setEllipsizedWidth(int ellipsizedWidth) {
+ @NonNull
+ public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
mEllipsizedWidth = ellipsizedWidth;
return this;
}
@@ -265,13 +283,13 @@ public class StaticLayout extends Layout {
* is wide, or exceeding the number of lines (see #setMaxLines) in the case
* of {@link android.text.TextUtils.TruncateAt#END} or
* {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
- * of broken. The default is
- * {@code null}, indicating no ellipsis is to be applied.
+ * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
*
* @param ellipsize type of ellipsis behavior
* @return this builder, useful for chaining
* @see android.widget.TextView#setEllipsize
*/
+ @NonNull
public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
mEllipsize = ellipsize;
return this;
@@ -286,7 +304,8 @@ public class StaticLayout extends Layout {
* @return this builder, useful for chaining
* @see android.widget.TextView#setMaxLines
*/
- public Builder setMaxLines(int maxLines) {
+ @NonNull
+ public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
mMaxLines = maxLines;
return this;
}
@@ -299,6 +318,7 @@ public class StaticLayout extends Layout {
* @return this builder, useful for chaining
* @see android.widget.TextView#setBreakStrategy
*/
+ @NonNull
public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
mBreakStrategy = breakStrategy;
return this;
@@ -306,12 +326,15 @@ public class StaticLayout extends Layout {
/**
* Set hyphenation frequency, to control the amount of automatic hyphenation used. The
- * default is {@link Layout#HYPHENATION_FREQUENCY_NONE}.
+ * possible values are defined in {@link Layout}, by constants named with the pattern
+ * {@code HYPHENATION_FREQUENCY_*}. The default is
+ * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
*
* @param hyphenationFrequency hyphenation frequency for the paragraph
* @return this builder, useful for chaining
* @see android.widget.TextView#setHyphenationFrequency
*/
+ @NonNull
public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
mHyphenationFrequency = hyphenationFrequency;
return this;
@@ -325,7 +348,8 @@ public class StaticLayout extends Layout {
* @param rightIndents array of indent values for right margin, in pixels
* @return this builder, useful for chaining
*/
- public Builder setIndents(int[] leftIndents, int[] rightIndents) {
+ @NonNull
+ public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
mLeftIndents = leftIndents;
mRightIndents = rightIndents;
int leftLen = leftIndents == null ? 0 : leftIndents.length;
@@ -348,6 +372,7 @@ public class StaticLayout extends Layout {
* @param justificationMode justification mode for the paragraph.
* @return this builder, useful for chaining.
*/
+ @NonNull
public Builder setJustificationMode(@JustificationMode int justificationMode) {
mJustificationMode = justificationMode;
return this;
@@ -359,12 +384,14 @@ public class StaticLayout extends Layout {
*
* @hide
*/
+ @NonNull
/* package */ Builder setAddLastLineLineSpacing(boolean value) {
mAddLastLineLineSpacing = value;
return this;
}
- private long[] getHyphenators(LocaleList locales) {
+ @NonNull
+ private long[] getHyphenators(@NonNull LocaleList locales) {
final int length = locales.size();
final long[] result = new long[length];
for (int i = 0; i < length; i++) {
@@ -424,6 +451,7 @@ public class StaticLayout extends Layout {
*
* @return the newly constructed {@link StaticLayout} object
*/
+ @NonNull
public StaticLayout build() {
StaticLayout result = new StaticLayout(this);
Builder.recycle(this);
@@ -441,33 +469,33 @@ public class StaticLayout extends Layout {
/* package */ long mNativePtr;
- CharSequence mText;
- int mStart;
- int mEnd;
- TextPaint mPaint;
- int mWidth;
- Alignment mAlignment;
- TextDirectionHeuristic mTextDir;
- float mSpacingMult;
- float mSpacingAdd;
- boolean mIncludePad;
- boolean mFallbackLineSpacing;
- int mEllipsizedWidth;
- TextUtils.TruncateAt mEllipsize;
- int mMaxLines;
- int mBreakStrategy;
- int mHyphenationFrequency;
- int[] mLeftIndents;
- int[] mRightIndents;
- int mJustificationMode;
- boolean mAddLastLineLineSpacing;
-
- Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
+ private CharSequence mText;
+ private int mStart;
+ private int mEnd;
+ private TextPaint mPaint;
+ private int mWidth;
+ private Alignment mAlignment;
+ private TextDirectionHeuristic mTextDir;
+ private float mSpacingMult;
+ private float mSpacingAdd;
+ private boolean mIncludePad;
+ private boolean mFallbackLineSpacing;
+ private int mEllipsizedWidth;
+ private TextUtils.TruncateAt mEllipsize;
+ private int mMaxLines;
+ private int mBreakStrategy;
+ private int mHyphenationFrequency;
+ private int[] mLeftIndents;
+ private int[] mRightIndents;
+ private int mJustificationMode;
+ private boolean mAddLastLineLineSpacing;
+
+ private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
// This will go away and be subsumed by native builder code
- MeasuredText mMeasuredText;
+ private MeasuredText mMeasuredText;
- LocaleList mLocales;
+ private LocaleList mLocales;
private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
}
@@ -548,12 +576,17 @@ public class StaticLayout extends Layout {
.setEllipsize(ellipsize)
.setMaxLines(maxLines);
/*
- * This is annoying, but we can't refer to the layout until
- * superclass construction is finished, and the superclass
- * constructor wants the reference to the display text.
+ * This is annoying, but we can't refer to the layout until superclass construction is
+ * finished, and the superclass constructor wants the reference to the display text.
+ *
+ * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
+ * as a parameter to do their calculations, but the Ellipsizers also need to be the input
+ * to the superclass's constructor (Layout). In order to go around the circular
+ * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
+ * we fill in the rest of the needed information (layout, width, and method) later, here.
*
- * This will break if the superclass constructor ever actually
- * cares about the content instead of just holding the reference.
+ * This will break if the superclass constructor ever actually cares about the content
+ * instead of just holding the reference.
*/
if (ellipsize != null) {
Ellipsizer e = (Ellipsizer) getText();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7cf84615d08d..2417b0fd81fd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7956,10 +7956,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean useSaved) {
Layout result = null;
if (mText instanceof Spannable) {
- result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
- alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
- mBreakStrategy, mHyphenationFrequency, mJustificationMode,
- getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
+ final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
+ wantWidth)
+ .setDisplayText(mTransformed)
+ .setAlignment(alignment)
+ .setTextDirection(mTextDir)
+ .setLineSpacing(mSpacingAdd, mSpacingMult)
+ .setIncludePad(mIncludePad)
+ .setBreakStrategy(mBreakStrategy)
+ .setHyphenationFrequency(mHyphenationFrequency)
+ .setJustificationMode(mJustificationMode)
+ .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
+ .setEllipsizedWidth(ellipsisWidth);
+ result = builder.build();
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);