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
This commit is contained in:
Selim Cinek
2017-02-08 14:10:03 -08:00
parent 1b7ea1813d
commit 64aed1a21b
6 changed files with 102 additions and 78 deletions

View File

@@ -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;
}
}
/**

View File

@@ -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);
@@ -98,6 +109,34 @@ public class ImageFloatingTextView extends TextView {
return builder.build();
}
@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);

View File

@@ -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();
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;
// 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();
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 (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;
}
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
int totalHeight = mPaddingTop + mPaddingBottom;
boolean first = true;
final int childHeight = child.getMeasuredHeight();
int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
lp.bottomMargin + (first ? 0 : mSpacing));
first = false;
// Starting from the bottom: we measure every view as if it were the only one. If it still
if (newHeight <= targetHeight) {
totalHeight = newHeight;
lp.hide = false;
} else {
break;
// 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);
}
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;
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

View File

@@ -48,12 +48,11 @@
<include layout="@layout/notification_template_progress" />
<com.android.internal.widget.ImageFloatingTextView android:id="@+id/big_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/notification_text_margin_top"
android:paddingBottom="@dimen/notification_content_margin_bottom"
android:textAppearance="@style/TextAppearance.Material.Notification"
android:singleLine="false"
android:layout_weight="1"
android:gravity="top"
android:visibility="gone"
/>

View File

@@ -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" >
<com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0"
style="@style/Widget.Material.Notification.MessagingText"
/>

View File

@@ -8355,7 +8355,6 @@
</declare-styleable>
<declare-styleable name="MessagingLinearLayout">
<attr name="maxHeight" />
<attr name="spacing" />
</declare-styleable>