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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -8355,7 +8355,6 @@
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="MessagingLinearLayout">
|
||||
<attr name="maxHeight" />
|
||||
<attr name="spacing" />
|
||||
</declare-styleable>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user