QS: Add a caret to the dual tile labels.

Also tweak some of the vertical spacing between the tiles
to get closer to the redlines.

Bug:15852139
Change-Id: I251fde261a74335c16b37ba07ab554f6db05d367
This commit is contained in:
John Spurlock
2014-06-29 12:54:24 -04:00
parent 824d2fd885
commit 92d9b19e1b
6 changed files with 249 additions and 42 deletions

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/spinner_mtrl_am_alpha"
android:tint="@color/qs_tile_text"
android:autoMirrored="true" />

View File

@@ -159,14 +159,18 @@
<!-- For phones, this is close_handle_height + header_height -->
<dimen name="peek_height">84dp</dimen>
<dimen name="qs_tile_height">84dp</dimen>
<dimen name="qs_tile_padding">8dp</dimen>
<dimen name="qs_tile_height">88dp</dimen>
<dimen name="qs_tile_icon_size">28dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
<dimen name="qs_tile_divider_height">1dp</dimen>
<dimen name="qs_panel_padding">16dp</dimen>
<dimen name="qs_dual_tile_height">109dp</dimen>
<dimen name="qs_dual_tile_padding">12dp</dimen>
<dimen name="qs_dual_tile_height">104dp</dimen>
<dimen name="qs_dual_tile_padding_below_divider">4dp</dimen>
<dimen name="qs_tile_padding_top">16dp</dimen>
<dimen name="qs_tile_padding_below_icon">12dp</dimen>
<dimen name="qs_tile_padding_bottom">16dp</dimen>
<dimen name="qs_tile_spacing">4dp</dimen>
<dimen name="qs_panel_padding_bottom">8dp</dimen>
<!-- How far the expanded QS panel peeks from the header in collapsed state. -->
<dimen name="qs_peek_height">8dp</dimen>

View File

@@ -18,5 +18,6 @@
<dimen name="status_bar_height">@*android:dimen/status_bar_height</dimen>
<dimen name="navigation_bar_height">@*android:dimen/navigation_bar_height</dimen>
<drawable name="notification_material_bg">@*android:drawable/notification_material_bg</drawable>
<drawable name="spinner_mtrl_am_alpha">@*android:drawable/spinner_mtrl_am_alpha</drawable>
</resources>

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.qs;
import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import java.util.Objects;
/**
* Text displayed over one or two lines, centered horizontally. A caret is always drawn at the end
* of the first line, and considered part of the content for centering purposes.
*
* Text overflow rules:
* First line: break on a word, unless a single word takes up the entire line - in which case
* truncate.
* Second line: ellipsis if necessary
*/
public class QSDualTileLabel extends FrameLayout {
private static final String SPACING_TEXT = " ";
private final Context mContext;
private final TextView mFirstLine;
private final TextView mSecondLine;
private String mText;
public QSDualTileLabel(Context context) {
super(context);
mContext = context;
mFirstLine = initTextView();
mSecondLine = initTextView();
mSecondLine.setEllipsize(TruncateAt.END);
addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if ((oldRight - oldLeft) != (right - left)) {
updateText();
}
}
});
}
public void setFirstLineBackground(Drawable d) {
mFirstLine.setBackground(d);
if (d != null) {
final LayoutParams lp = (LayoutParams) mSecondLine.getLayoutParams();
lp.topMargin = d.getIntrinsicHeight() * 3 / 4;
mSecondLine.setLayoutParams(lp);
}
}
private TextView initTextView() {
final TextView tv = new TextView(mContext);
tv.setPadding(0, 0, 0, 0);
tv.setSingleLine(true);
tv.setClickable(false);
tv.setBackground(null);
final LayoutParams lp =
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
addView(tv, lp);
return tv;
}
public void setText(CharSequence text) {
final String newText = text == null ? null : text.toString().trim();
if (Objects.equals(newText, mText)) return;
mText = newText;
updateText();
}
public String getText() {
return mText;
}
public void setTextSize(int unit, float size) {
mFirstLine.setTextSize(unit, size);
mSecondLine.setTextSize(unit, size);
}
public void setTextColor(int color) {
mFirstLine.setTextColor(color);
mSecondLine.setTextColor(color);
}
public void setTypeface(Typeface tf) {
mFirstLine.setTypeface(tf);
mSecondLine.setTypeface(tf);
}
private void updateText() {
if (getWidth() == 0) return;
if (TextUtils.isEmpty(mText)) {
mFirstLine.setText(null);
mSecondLine.setText(null);
return;
}
final float maxWidth = getWidth() - mFirstLine.getBackground().getIntrinsicWidth()
- getPaddingLeft() - getPaddingRight();
float width = mFirstLine.getPaint().measureText(mText + SPACING_TEXT);
if (width <= maxWidth) {
mFirstLine.setText(mText + SPACING_TEXT);
mSecondLine.setText(null);
return;
}
final int n = mText.length();
int lastWordBoundary = -1;
boolean inWhitespace = false;
int i = 0;
for (i = 1; i < n; i++) {
if (Character.isWhitespace(mText.charAt(i))) {
if (!inWhitespace) {
lastWordBoundary = i;
}
inWhitespace = true;
} else {
inWhitespace = false;
}
width = mFirstLine.getPaint().measureText(mText.substring(0, i) + SPACING_TEXT);
if (width > maxWidth) {
break;
}
}
if (lastWordBoundary == -1) {
lastWordBoundary = i - 1;
}
mFirstLine.setText(mText.substring(0, lastWordBoundary) + SPACING_TEXT);
mSecondLine.setText(mText.substring(lastWordBoundary).trim());
}
}

View File

@@ -47,6 +47,7 @@ public class QSPanel extends ViewGroup {
private int mCellHeight;
private int mLargeCellWidth;
private int mLargeCellHeight;
private int mPanelPaddingBottom;
private boolean mExpanded;
private TileRecord mDetailRecord;
@@ -80,6 +81,7 @@ public class QSPanel extends ViewGroup {
mCellWidth = (int)(mCellHeight * TILE_ASPECT);
mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height);
mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT);
mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
if (mColumns != columns) {
mColumns = columns;
postInvalidate();
@@ -204,7 +206,7 @@ public class QSPanel extends ViewGroup {
final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
record.tileView.measure(exactly(cw), exactly(ch));
}
int h = rows == 0 ? 0 : getRowTop(rows);
int h = rows == 0 ? 0 : (getRowTop(rows) + mPanelPaddingBottom);
mDetail.measure(exactly(width), unspecified());
if (mDetail.getVisibility() == VISIBLE && mDetail.getChildCount() > 0) {
final int dmh = mDetail.getMeasuredHeight();

View File

@@ -47,9 +47,13 @@ public class QSTileView extends ViewGroup {
private final View mDivider;
private final H mHandler = new H();
private final int mIconSizePx;
private final int mTileSpacingPx;
private final int mTilePaddingTopPx;
private final int mTilePaddingBelowIconPx;
private final int mDualTilePaddingBelowDividerPx;
private int mTilePaddingPx;
private TextView mLabel;
private QSDualTileLabel mDualLabel;
private boolean mDual;
private OnClickListener mClickPrimary;
private OnClickListener mClickSecondary;
@@ -61,6 +65,11 @@ public class QSTileView extends ViewGroup {
mContext = context;
final Resources res = context.getResources();
mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size);
mTileSpacingPx = res.getDimensionPixelSize(R.dimen.qs_tile_spacing);
mTilePaddingTopPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top);
mTilePaddingBelowIconPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon);
mDualTilePaddingBelowDividerPx =
res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_below_divider);
recreateLabel();
setClipChildren(false);
@@ -82,26 +91,46 @@ public class QSTileView extends ViewGroup {
if (mLabel != null) {
labelText = mLabel.getText();
removeView(mLabel);
mLabel = null;
}
if (mDualLabel != null) {
labelText = mDualLabel.getText();
removeView(mDualLabel);
mDualLabel = null;
}
final Resources res = mContext.getResources();
mLabel = new TextView(mDual
? new ContextThemeWrapper(mContext, R.style.BorderlessButton_Tiny)
: mContext);
mLabel.setId(android.R.id.title);
mLabel.setTextColor(res.getColor(R.color.qs_tile_text));
mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
mLabel.setMinLines(2);
mTilePaddingPx = res.getDimensionPixelSize(
mDual ? R.dimen.qs_dual_tile_padding : R.dimen.qs_tile_padding);
final int bottomPadding = mDual ? 0 : mTilePaddingPx;
mLabel.setPadding(mTilePaddingPx, mTilePaddingPx, mTilePaddingPx, bottomPadding);
mLabel.setTypeface(CONDENSED);
mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
if (labelText != null) {
mLabel.setText(labelText);
if (mDual) {
final Context c = new ContextThemeWrapper(mContext, R.style.BorderlessButton_Tiny);
mDualLabel = new QSDualTileLabel(c);
mDualLabel.setId(android.R.id.title);
mDualLabel.setFirstLineBackground(res.getDrawable(R.drawable.qs_dual_tile_caret));
mDualLabel.setTextColor(res.getColor(R.color.qs_tile_text));
mDualLabel.setPadding(0, mDualTilePaddingBelowDividerPx, 0, 0);
mDualLabel.setTypeface(CONDENSED);
mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
mDualLabel.setClickable(true);
mDualLabel.setOnClickListener(mClickSecondary);
if (labelText != null) {
mDualLabel.setText(labelText);
}
addView(mDualLabel);
} else {
mLabel = new TextView(mContext);
mLabel.setId(android.R.id.title);
mLabel.setTextColor(res.getColor(R.color.qs_tile_text));
mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
mLabel.setMinLines(2);
mLabel.setPadding(0, 0, 0, 0);
mLabel.setTypeface(CONDENSED);
mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
mLabel.setClickable(false);
if (labelText != null) {
mLabel.setText(labelText);
}
addView(mLabel);
}
addView(mLabel);
}
public void setDual(boolean dual) {
@@ -110,14 +139,7 @@ public class QSTileView extends ViewGroup {
if (changed) {
recreateLabel();
}
if (mDual) {
setOnClickListener(mClickPrimary);
mLabel.setClickable(true);
mLabel.setOnClickListener(mClickSecondary);
} else {
mLabel.setClickable(false);
setOnClickListener(mClickPrimary);
}
setOnClickListener(mClickPrimary);
mDivider.setVisibility(dual ? VISIBLE : GONE);
postInvalidate();
}
@@ -145,13 +167,17 @@ public class QSTileView extends ViewGroup {
return d;
}
private View labelView() {
return mDual ? mDualLabel : mLabel;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int w = MeasureSpec.getSize(widthMeasureSpec);
final int h = MeasureSpec.getSize(heightMeasureSpec);
final int iconSpec = exactly(mIconSizePx);
mIcon.measure(iconSpec, iconSpec);
mLabel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
labelView().measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
if (mDual) {
mDivider.measure(widthMeasureSpec, exactly(mDivider.getLayoutParams().height));
}
@@ -165,14 +191,10 @@ public class QSTileView extends ViewGroup {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int w = getMeasuredWidth();
final int h = getMeasuredHeight();
final int contentHeight = mTilePaddingPx + mIcon.getMeasuredHeight()
+ mLabel.getMeasuredHeight()
+ (mDual ? (mTilePaddingPx + mDivider.getMeasuredHeight()) : 0);
int top = Math.max(0, (h - contentHeight) / 2);
top += mTilePaddingPx;
int top = 0;
top += mTileSpacingPx;
top += mTilePaddingTopPx;
final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
layout(mIcon, iconLeft, top);
if (mRipple != null) {
@@ -183,12 +205,12 @@ public class QSTileView extends ViewGroup {
mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
}
top = mIcon.getBottom();
top += mTilePaddingBelowIconPx;
if (mDual) {
top += mTilePaddingPx;
layout(mDivider, 0, top);
top = mDivider.getBottom();
}
layout(mLabel, 0, top);
layout(labelView(), 0, top);
}
private static void layout(View child, int left, int top) {
@@ -204,7 +226,11 @@ public class QSTileView extends ViewGroup {
iv.setImageResource(state.iconId);
}
}
mLabel.setText(state.label);
if (mDual) {
mDualLabel.setText(state.label);
} else {
mLabel.setText(state.label);
}
setContentDescription(state.contentDescription);
}