From 64aed1a21bfbf0ffbc75b20c67232174995e8803 Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Wed, 8 Feb 2017 14:10:03 -0800 Subject: [PATCH] Improved collapsed messaging notifications Previously the messaging layout was working on a fixed Height, which didn't scale well as we were now using it also in the collapsed version. For big text notifications, the view would be clipped ugly, since we were using a fixed number of lines. Instead we're now calculating this dynamically. Fixes: 35126759 Test: runtest systemui Change-Id: Ie3736d807d7a162ee51ad65794bf2f1cfb2248be --- core/java/android/app/Notification.java | 13 --- .../internal/widget/ImageFloatingTextView.java | 43 +++++++- .../internal/widget/MessagingLinearLayout.java | 113 +++++++++++---------- .../notification_template_material_big_text.xml | 3 +- .../notification_template_material_messaging.xml | 3 +- core/res/res/values/attrs.xml | 1 - 6 files changed, 100 insertions(+), 76 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index d1226f225ed0..056aff04375e 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -4962,9 +4962,6 @@ public class Notification implements Parcelable */ public static class BigTextStyle extends Style { - private static final int MAX_LINES = 13; - private static final int LINES_CONSUMED_BY_ACTIONS = 4; - private CharSequence mBigText; public BigTextStyle() { @@ -5070,18 +5067,8 @@ public class Notification implements Parcelable builder.setTextViewColorSecondary(contentView, R.id.big_text); contentView.setViewVisibility(R.id.big_text, TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE); - contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines(builder)); contentView.setBoolean(R.id.big_text, "setHasImage", builder.mN.hasLargeIcon()); } - - private static int calculateMaxLines(Builder builder) { - int lineCount = MAX_LINES; - boolean hasActions = builder.mActions.size() > 0; - if (hasActions) { - lineCount -= LINES_CONSUMED_BY_ACTIONS; - } - return lineCount; - } } /** diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java index a5d2bf3b68d3..80207ee854b8 100644 --- a/core/java/com/android/internal/widget/ImageFloatingTextView.java +++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java @@ -42,6 +42,10 @@ public class ImageFloatingTextView extends TextView { /** Resolved layout direction */ private int mResolvedDirection = LAYOUT_DIRECTION_UNDEFINED; + private int mMaxLinesForHeight = -1; + private boolean mFirstMeasure = true; + private int mLayoutMaxLines = -1; + private boolean mBlockLayouts; public ImageFloatingTextView(Context context) { this(context, null); @@ -72,8 +76,15 @@ public class ImageFloatingTextView extends TextView { .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL) - .setMaxLines(getMaxLines() >= 0 ? getMaxLines() : Integer.MAX_VALUE); + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + int maxLines; + if (mMaxLinesForHeight > 0) { + maxLines = mMaxLinesForHeight; + } else { + maxLines = getMaxLines() >= 0 ? getMaxLines() : Integer.MAX_VALUE; + } + builder.setMaxLines(maxLines); + mLayoutMaxLines = maxLines; if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -99,6 +110,34 @@ public class ImageFloatingTextView extends TextView { } @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int height = MeasureSpec.getSize(heightMeasureSpec); + // Lets calculate how many lines the given measurement allows us. + int availableHeight = height - mPaddingTop - mPaddingBottom; + int maxLines = availableHeight / getLineHeight(); + if (getMaxLines() > 0) { + maxLines = Math.min(getMaxLines(), maxLines); + } + if (maxLines != mMaxLinesForHeight) { + mMaxLinesForHeight = maxLines; + if (getLayout() != null && mMaxLinesForHeight != mLayoutMaxLines) { + // Invalidate layout. + mBlockLayouts = true; + setHint(getHint()); + mBlockLayouts = false; + } + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public void requestLayout() { + if (!mBlockLayouts) { + super.requestLayout(); + } + } + + @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index cb123a13ad1d..b259ad1643f8 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -36,6 +36,7 @@ import android.widget.RemoteViews; @RemoteViews.RemoteView public class MessagingLinearLayout extends ViewGroup { + private static final int NOT_MEASURED_BEFORE = -1; /** * Spacing to be applied between views. */ @@ -52,6 +53,11 @@ public class MessagingLinearLayout extends ViewGroup { * Id of the child that's also visible in the contracted layout. */ private int mContractedChildId; + /** + * The last measured with in a layout pass if it was measured before or + * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass. + */ + private int mLastMeasuredWidth = NOT_MEASURED_BEFORE; public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -64,20 +70,12 @@ public class MessagingLinearLayout extends ViewGroup { for (int i = 0; i < N; i++) { int attr = a.getIndex(i); switch (attr) { - case R.styleable.MessagingLinearLayout_maxHeight: - mMaxHeight = a.getDimensionPixelSize(i, 0); - break; case R.styleable.MessagingLinearLayout_spacing: mSpacing = a.getDimensionPixelSize(i, 0); break; } } - if (mMaxHeight <= 0) { - throw new IllegalStateException( - "MessagingLinearLayout: Must specify positive maxHeight"); - } - a.recycle(); } @@ -86,62 +84,63 @@ public class MessagingLinearLayout extends ViewGroup { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // This is essentially a bottom-up linear layout that only adds children that fit entirely // up to a maximum height. - + int targetHeight = MeasureSpec.getSize(heightMeasureSpec); switch (MeasureSpec.getMode(heightMeasureSpec)) { - case MeasureSpec.AT_MOST: - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - Math.min(mMaxHeight, MeasureSpec.getSize(heightMeasureSpec)), - MeasureSpec.AT_MOST); - break; case MeasureSpec.UNSPECIFIED: - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - mMaxHeight, - MeasureSpec.AT_MOST); - break; - case MeasureSpec.EXACTLY: + targetHeight = Integer.MAX_VALUE; break; } - final int targetHeight = MeasureSpec.getSize(heightMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE + || getMeasuredHeight() != targetHeight + || mLastMeasuredWidth != widthSize; + final int count = getChildCount(); + if (recalculateVisibility) { + // We only need to recalculate the view visibilities if the view wasn't measured already + // in this pass, otherwise we may drop messages here already since we are measured + // exactly with what we returned before, which was optimized already with the + // line-indents. + for (int i = 0; i < count; ++i) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.hide = true; + } - for (int i = 0; i < count; ++i) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - lp.hide = true; - } + int totalHeight = mPaddingTop + mPaddingBottom; + boolean first = true; - int totalHeight = mPaddingTop + mPaddingBottom; - boolean first = true; + // Starting from the bottom: we measure every view as if it were the only one. If it still - // Starting from the bottom: we measure every view as if it were the only one. If it still - // fits, we take it, otherwise we stop there. - for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) { - if (getChildAt(i).getVisibility() == GONE) { - continue; - } - final View child = getChildAt(i); - LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); + // fits, we take it, otherwise we stop there. + for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) { + if (getChildAt(i).getVisibility() == GONE) { + continue; + } + final View child = getChildAt(i); + LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); + + if (child instanceof ImageFloatingTextView) { + // Pretend we need the image padding for all views, we don't know which + // one will end up needing to do this (might end up not using all the space, + // but calculating this exactly would be more expensive). + ((ImageFloatingTextView) child).setNumIndentLines( + mIndentLines == 2 ? 3 : mIndentLines); + } - if (child instanceof ImageFloatingTextView) { - // Pretend we need the image padding for all views, we don't know which - // one will end up needing to do this (might end up not using all the space, - // but calculating this exactly would be more expensive). - ((ImageFloatingTextView) child).setNumIndentLines( - mIndentLines == 2 ? 3 : mIndentLines); - } + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); - measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + final int childHeight = child.getMeasuredHeight(); + int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin + + lp.bottomMargin + (first ? 0 : mSpacing)); + first = false; - final int childHeight = child.getMeasuredHeight(); - int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin + - lp.bottomMargin + (first ? 0 : mSpacing)); - first = false; - - if (newHeight <= targetHeight) { - totalHeight = newHeight; - lp.hide = false; - } else { - break; + if (newHeight <= targetHeight) { + totalHeight = newHeight; + lp.hide = false; + } else { + break; + } } } @@ -149,8 +148,8 @@ public class MessagingLinearLayout extends ViewGroup { int measuredWidth = mPaddingLeft + mPaddingRight; int imageLines = mIndentLines; // Need to redo the height because it may change due to changing indents. - totalHeight = mPaddingTop + mPaddingBottom; - first = true; + int totalHeight = mPaddingTop + mPaddingBottom; + boolean first = true; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); @@ -168,7 +167,7 @@ public class MessagingLinearLayout extends ViewGroup { imageLines = 3; } boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines)); - if (changed) { + if (changed || !recalculateVisibility) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); } imageLines -= textChild.getLineCount(); @@ -188,6 +187,7 @@ public class MessagingLinearLayout extends ViewGroup { widthMeasureSpec), resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight), heightMeasureSpec)); + mLastMeasuredWidth = widthSize; } @Override @@ -236,6 +236,7 @@ public class MessagingLinearLayout extends ViewGroup { first = false; } + mLastMeasuredWidth = NOT_MEASURED_BEFORE; } @Override diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml index f4f783ecc6fb..1aca99feb872 100644 --- a/core/res/res/layout/notification_template_material_big_text.xml +++ b/core/res/res/layout/notification_template_material_big_text.xml @@ -48,12 +48,11 @@ diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml index 07b1100ee93b..b91021172f90 100644 --- a/core/res/res/layout/notification_template_material_messaging.xml +++ b/core/res/res/layout/notification_template_material_messaging.xml @@ -50,8 +50,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="@dimen/notification_content_margin_bottom" - android:spacing="@dimen/notification_messaging_spacing" - android:maxHeight="165dp"> + android:spacing="@dimen/notification_messaging_spacing" > diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 34f78f3947bb..f1f3409a2a24 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8355,7 +8355,6 @@ - -- 2.11.0