StaticLayout visual fix for maxLines
When maxLines is set on StaticLayout, the height calculation includes
the lineSpacing for the lastLine, which causes the ellipsized version
and non-ellipsized version to have different heights. With this CL:
* maxLines is always set on StaticLayout
* the correct line count for a given text is preserved, in other words a
text that would be n lines will not be cut at maxLines.
* The visual height for StaticLayout for ellipsized and non-ellipsized
cases are the same when maxLines is set.
Bug: 28988744
Bug: 18864800
Change-Id: I1e1cae31cf33d503a8cf1c942f422893efc480bb
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index bb131a0..c58894f 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -836,7 +836,7 @@
here = endPos;
breakIndex++;
- if (mLineCount >= mMaximumVisibleLineCount) {
+ if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
return;
}
}
@@ -920,7 +920,25 @@
boolean firstLine = (j == 0);
boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
- boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
+
+ if (ellipsize != null) {
+ // If there is only one line, then do any type of ellipsis except when it is MARQUEE
+ // if there are multiple lines, just allow END ellipsis on the last line
+ boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
+
+ boolean doEllipsis =
+ (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
+ ellipsize != TextUtils.TruncateAt.MARQUEE) ||
+ (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
+ ellipsize == TextUtils.TruncateAt.END);
+ if (doEllipsis) {
+ calculateEllipsis(start, end, widths, widthStart,
+ ellipsisWidth, ellipsize, j,
+ textWidth, paint, forceEllipsis);
+ }
+ }
+
+ boolean lastLine = mEllipsized || (end == bufEnd);
if (firstLine) {
if (trackPad) {
@@ -944,7 +962,6 @@
}
}
-
if (needMultiply && !lastLine) {
double ex = (below - above) * (spacingmult - 1) + spacingadd;
if (ex >= 0) {
@@ -960,6 +977,15 @@
lines[off + TOP] = v;
lines[off + DESCENT] = below + extra;
+ // special case for non-ellipsized last visible line when maxLines is set
+ // store the height as if it was ellipsized
+ if (!mEllipsized && currentLineIsTheLastVisibleOne) {
+ // below calculation as if it was the last line
+ int maxLineBelow = includePad ? bottom : below;
+ // similar to the calculation of v below, without the extra.
+ mMaxLineHeight = v + (maxLineBelow - above);
+ }
+
v += (below - above) + extra;
lines[off + mColumns + START] = end;
lines[off + mColumns + TOP] = v;
@@ -981,23 +1007,6 @@
start - widthStart, end - start);
}
- if (ellipsize != null) {
- // If there is only one line, then do any type of ellipsis except when it is MARQUEE
- // if there are multiple lines, just allow END ellipsis on the last line
- boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
-
- boolean doEllipsis =
- (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
- ellipsize != TextUtils.TruncateAt.MARQUEE) ||
- (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
- ellipsize == TextUtils.TruncateAt.END);
- if (doEllipsis) {
- calculateEllipsis(start, end, widths, widthStart,
- ellipsisWidth, ellipsize, j,
- textWidth, paint, forceEllipsis);
- }
- }
-
mLineCount++;
return v;
}
@@ -1101,7 +1110,7 @@
}
}
}
-
+ mEllipsized = true;
mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
}
@@ -1239,6 +1248,25 @@
return mEllipsizedWidth;
}
+ /**
+ * Return the total height of this layout.
+ *
+ * @param cap if true and max lines is set, returns the height of the layout at the max lines.
+ *
+ * @hide
+ */
+ public int getHeight(boolean cap) {
+ if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
+ Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "maxLineHeight should not be -1. "
+ + " maxLines:" + mMaximumVisibleLineCount
+ + " lineCount:" + mLineCount);
+ }
+
+ return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
+ mMaxLineHeight : super.getHeight();
+ }
+
private static native long nNewBuilder();
private static native void nFreeBuilder(long nativePtr);
private static native void nFinishBuilder(long nativePtr);
@@ -1277,6 +1305,21 @@
private int mColumns;
private int mEllipsizedWidth;
+ /**
+ * Keeps track if ellipsize is applied to the text.
+ */
+ private boolean mEllipsized;
+
+ /**
+ * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
+ * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
+ * starting from the top of the layout. If maxLines is not set its value will be -1.
+ *
+ * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
+ * more than maxLines is contained.
+ */
+ private int mMaxLineHeight = -1;
+
private static final int COLUMNS_NORMAL = 4;
private static final int COLUMNS_ELLIPSIZE = 6;
private static final int START = 0;