Skip to content

Commit 1b36365

Browse files
committed
[CollapsingToolbarLayout] Added option to add extra height when title text spans across multiple lines
PiperOrigin-RevId: 382716405 (cherry picked from commit 655dde0)
1 parent 245ffe7 commit 1b36365

File tree

3 files changed

+99
-27
lines changed

3 files changed

+99
-27
lines changed

lib/java/com/google/android/material/appbar/CollapsingToolbarLayout.java

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,10 @@ public class CollapsingToolbarLayout extends FrameLayout {
174174

175175
@Nullable WindowInsetsCompat lastInsets;
176176
private int topInsetApplied = 0;
177-
private boolean forceApplySystemWindowInsetTop = false;
177+
private boolean forceApplySystemWindowInsetTop;
178+
179+
private int extraMultilineHeight = 0;
180+
private boolean extraMultilineHeightEnabled;
178181

179182
public CollapsingToolbarLayout(@NonNull Context context) {
180183
this(context, null);
@@ -278,6 +281,9 @@ public CollapsingToolbarLayout(@NonNull Context context, @Nullable AttributeSet
278281
forceApplySystemWindowInsetTop =
279282
a.getBoolean(R.styleable.CollapsingToolbarLayout_forceApplySystemWindowInsetTop, false);
280283

284+
extraMultilineHeightEnabled =
285+
a.getBoolean(R.styleable.CollapsingToolbarLayout_extraMultilineHeightEnabled, false);
286+
281287
a.recycle();
282288

283289
setWillNotDraw(false);
@@ -525,11 +531,27 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
525531
// If we have a top inset and we're set to wrap_content height or force apply,
526532
// we need to make sure we add the top inset to our height, therefore we re-measure
527533
topInsetApplied = topInset;
528-
heightMeasureSpec =
529-
MeasureSpec.makeMeasureSpec(getMeasuredHeight() + topInset, MeasureSpec.EXACTLY);
534+
int newHeight = getMeasuredHeight() + topInset;
535+
heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
530536
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
531537
}
532538

539+
if (extraMultilineHeightEnabled && collapsingTextHelper.getMaxLines() > 1) {
540+
// Need to update title and bounds in order to calculate line count and text height.
541+
updateTitleFromToolbarIfNeeded();
542+
updateTextBounds(0, 0, getMeasuredWidth(), getMeasuredHeight(), /* forceRecalculate= */ true);
543+
544+
int lineCount = collapsingTextHelper.getLineCount();
545+
if (lineCount > 1) {
546+
// Add extra height based on the amount of height beyond the first line of title text.
547+
int expandedTextHeight = Math.round(collapsingTextHelper.getExpandedTextFullHeight());
548+
extraMultilineHeight = expandedTextHeight * (lineCount - 1);
549+
int newHeight = getMeasuredHeight() + extraMultilineHeight;
550+
heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
551+
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
552+
}
553+
}
554+
533555
// Set our minimum height to enable proper AppBarLayout collapsing
534556
if (toolbar != null) {
535557
if (toolbarDirectChild == null || toolbarDirectChild == this) {
@@ -564,14 +586,28 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
564586
getViewOffsetHelper(getChildAt(i)).onViewLayout();
565587
}
566588

589+
updateTextBounds(left, top, right, bottom, /* forceRecalculate= */false);
590+
591+
updateTitleFromToolbarIfNeeded();
592+
593+
updateScrimVisibility();
594+
595+
// Apply any view offsets, this should be done at the very end of layout
596+
for (int i = 0, z = getChildCount(); i < z; i++) {
597+
getViewOffsetHelper(getChildAt(i)).applyOffsets();
598+
}
599+
}
600+
601+
private void updateTextBounds(
602+
int left, int top, int right, int bottom, boolean forceRecalculate) {
567603
// Update the collapsed bounds by getting its transformed bounds
568604
if (collapsingTitleEnabled && dummyView != null) {
569605
// We only draw the title if the dummy view is being displayed (Toolbar removes
570606
// views if there is no space)
571607
drawCollapsingTitle =
572608
ViewCompat.isAttachedToWindow(dummyView) && dummyView.getVisibility() == VISIBLE;
573609

574-
if (drawCollapsingTitle) {
610+
if (drawCollapsingTitle || forceRecalculate) {
575611
final boolean isRtl =
576612
ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
577613

@@ -586,23 +622,18 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
586622
bottom - top - expandedMarginBottom);
587623

588624
// Now recalculate using the new bounds
589-
collapsingTextHelper.recalculate();
625+
collapsingTextHelper.recalculate(forceRecalculate);
590626
}
591627
}
628+
}
592629

630+
private void updateTitleFromToolbarIfNeeded() {
593631
if (toolbar != null) {
594632
if (collapsingTitleEnabled && TextUtils.isEmpty(collapsingTextHelper.getText())) {
595633
// If we do not currently have a title, try and grab it from the Toolbar
596634
setTitle(getToolbarTitle(toolbar));
597635
}
598636
}
599-
600-
updateScrimVisibility();
601-
602-
// Apply any view offsets, this should be done at the very end of layout
603-
for (int i = 0, z = getChildCount(); i < z; i++) {
604-
getViewOffsetHelper(getChildAt(i)).applyOffsets();
605-
}
606637
}
607638

608639
private void updateCollapsedBounds(boolean isRtl) {
@@ -1334,6 +1365,24 @@ public boolean isForceApplySystemWindowInsetTop() {
13341365
return forceApplySystemWindowInsetTop;
13351366
}
13361367

1368+
/**
1369+
* Sets whether extra height should be added when the title text spans across multiple lines.
1370+
* Experimental Feature.
1371+
*/
1372+
@RestrictTo(LIBRARY_GROUP)
1373+
public void setExtraMultilineHeightEnabled(boolean extraMultilineHeightEnabled) {
1374+
this.extraMultilineHeightEnabled = extraMultilineHeightEnabled;
1375+
}
1376+
1377+
/**
1378+
* Gets whether extra height should be added when the title text spans across multiple lines.
1379+
* Experimental Feature.
1380+
*/
1381+
@RestrictTo(LIBRARY_GROUP)
1382+
public boolean isExtraMultilineHeightEnabled() {
1383+
return extraMultilineHeightEnabled;
1384+
}
1385+
13371386
/**
13381387
* Set the amount of visible height in pixels used to define when to trigger a scrim visibility
13391388
* change.
@@ -1362,7 +1411,7 @@ public void setScrimVisibleHeightTrigger(@IntRange(from = 0) final int height) {
13621411
public int getScrimVisibleHeightTrigger() {
13631412
if (scrimVisibleHeightTrigger >= 0) {
13641413
// If we have one explicitly set, return it
1365-
return scrimVisibleHeightTrigger + topInsetApplied;
1414+
return scrimVisibleHeightTrigger + topInsetApplied + extraMultilineHeight;
13661415
}
13671416

13681417
// Otherwise we'll use the default computed value

lib/java/com/google/android/material/appbar/res/values/attrs.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@
209209
<!-- Whether the system window inset top should be applied regardless of
210210
what the layout_height is set to. Experimental Feature. -->
211211
<attr name="forceApplySystemWindowInsetTop" format="boolean" />
212+
<!-- Whether extra height should be added when the title text spans across
213+
multiple lines. Experimental Feature. -->
214+
<attr name="extraMultilineHeightEnabled" format="boolean" />
212215
</declare-styleable>
213216

214217
<declare-styleable name="CollapsingToolbarLayout_Layout">

lib/java/com/google/android/material/internal/CollapsingTextHelper.java

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,12 @@ public float getExpandedTextHeight() {
281281
return -tmpPaint.ascent();
282282
}
283283

284+
public float getExpandedTextFullHeight() {
285+
getTextPaintExpanded(tmpPaint);
286+
// Return expanded height measured from the baseline.
287+
return -tmpPaint.ascent() + tmpPaint.descent();
288+
}
289+
284290
public float getCollapsedTextHeight() {
285291
getTextPaintCollapsed(tmpPaint);
286292
// Return collapsed height measured from the baseline.
@@ -644,11 +650,11 @@ private int getCurrentColor(@Nullable ColorStateList colorStateList) {
644650
return colorStateList.getDefaultColor();
645651
}
646652

647-
private void calculateBaseOffsets() {
653+
private void calculateBaseOffsets(boolean forceRecalculate) {
648654
final float currentTextSize = this.currentTextSize;
649655

650656
// We then calculate the collapsed text size, using the same logic
651-
calculateUsingTextSize(collapsedTextSize);
657+
calculateUsingTextSize(collapsedTextSize, forceRecalculate);
652658
if (textToDraw != null && textLayout != null) {
653659
textToDrawCollapsed =
654660
TextUtils.ellipsize(textToDraw, textPaint, textLayout.getWidth(), TruncateAt.END);
@@ -689,7 +695,7 @@ private void calculateBaseOffsets() {
689695
break;
690696
}
691697

692-
calculateUsingTextSize(expandedTextSize);
698+
calculateUsingTextSize(expandedTextSize, forceRecalculate);
693699
float expandedTextHeight = textLayout != null ? textLayout.getHeight() : 0;
694700

695701
float measuredWidth = textToDraw != null
@@ -880,8 +886,12 @@ private void setInterpolatedTextSize(float textSize) {
880886
ViewCompat.postInvalidateOnAnimation(view);
881887
}
882888

883-
@SuppressWarnings("ReferenceEquality") // Matches the Typeface comparison in TextView
884889
private void calculateUsingTextSize(final float textSize) {
890+
calculateUsingTextSize(textSize, /* forceRecalculate= */ false);
891+
}
892+
893+
@SuppressWarnings("ReferenceEquality") // Matches the Typeface comparison in TextView
894+
private void calculateUsingTextSize(final float textSize, boolean forceRecalculate) {
885895
if (text == null) {
886896
return;
887897
}
@@ -920,14 +930,20 @@ private void calculateUsingTextSize(final float textSize) {
920930
// collapsed text size
921931
float scaledDownWidth = expandedWidth * textSizeRatio;
922932

923-
// If the scaled down size is larger than the actual collapsed width, we need to
924-
// cap the available width so that when the expanded text scales down, it matches
925-
// the collapsed width
926-
// Otherwise we'll just use the expanded width
927-
928-
availableWidth = scaledDownWidth > collapsedWidth
929-
? min(collapsedWidth / textSizeRatio, expandedWidth)
930-
: expandedWidth;
933+
if (forceRecalculate) {
934+
// If we're forcing a recalculate during a measure pass, use the expanded width since the
935+
// collapsed width might not be ready yet
936+
availableWidth = expandedWidth;
937+
} else {
938+
// If the scaled down size is larger than the actual collapsed width, we need to
939+
// cap the available width so that when the expanded text scales down, it matches
940+
// the collapsed width
941+
// Otherwise we'll just use the expanded width
942+
943+
availableWidth = scaledDownWidth > collapsedWidth
944+
? min(collapsedWidth / textSizeRatio, expandedWidth)
945+
: expandedWidth;
946+
}
931947
}
932948

933949
if (availableWidth > 0) {
@@ -992,10 +1008,14 @@ private void ensureExpandedTexture() {
9921008
}
9931009

9941010
public void recalculate() {
995-
if (view.getHeight() > 0 && view.getWidth() > 0) {
1011+
recalculate(/* forceRecalculate= */ false);
1012+
}
1013+
1014+
public void recalculate(boolean forceRecalculate) {
1015+
if ((view.getHeight() > 0 && view.getWidth() > 0) || forceRecalculate) {
9961016
// If we've already been laid out, calculate everything now otherwise we'll wait
9971017
// until a layout
998-
calculateBaseOffsets();
1018+
calculateBaseOffsets(forceRecalculate);
9991019
calculateCurrentOffsets();
10001020
}
10011021
}

0 commit comments

Comments
 (0)