Draw vertical data usage sweep labels.
Using template string, ask axis to build label for sweeps. Also
clean up margins exposed to parent view, since we offset when label
is larger than sweep drawable.
Bug: 4598460
Change-Id: If71bc8ec8c952023325c80b9bc00b63c23609c7a
diff --git a/res/layout/data_usage_chart.xml b/res/layout/data_usage_chart.xml
index 199a38e..a942bb3 100644
--- a/res/layout/data_usage_chart.xml
+++ b/res/layout/data_usage_chart.xml
@@ -55,24 +55,14 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
settings:sweepDrawable="@drawable/data_sweep_left"
- settings:followAxis="horizontal"
- settings:showLabel="false" />
+ settings:followAxis="horizontal" />
<com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_right"
android:layout_width="wrap_content"
android:layout_height="match_parent"
settings:sweepDrawable="@drawable/data_sweep_right"
- settings:followAxis="horizontal"
- settings:showLabel="false" />
-
- <com.android.settings.widget.ChartSweepView
- android:id="@+id/sweep_limit"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- settings:sweepDrawable="@drawable/data_sweep_limit"
- settings:followAxis="vertical"
- settings:showLabel="true" />
+ settings:followAxis="horizontal" />
<com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_warning"
@@ -80,6 +70,18 @@
android:layout_height="wrap_content"
settings:sweepDrawable="@drawable/data_sweep_warning"
settings:followAxis="vertical"
- settings:showLabel="true" />
+ settings:labelSize="60dip"
+ settings:labelTemplate="@string/data_usage_sweep_warning"
+ settings:labelColor="#f7931d" />
+
+ <com.android.settings.widget.ChartSweepView
+ android:id="@+id/sweep_limit"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ settings:sweepDrawable="@drawable/data_sweep_limit"
+ settings:followAxis="vertical"
+ settings:labelSize="60dip"
+ settings:labelTemplate="@string/data_usage_sweep_limit"
+ settings:labelColor="#c01a2c" />
</com.android.settings.widget.DataUsageChartView>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 06d2650..a0a6c77 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -56,14 +56,16 @@
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
- <attr name="showLabel" format="boolean" />
+ <attr name="labelSize" format="dimension" />
+ <attr name="labelTemplate" format="reference" />
+ <attr name="labelColor" format="color" />
</declare-styleable>
<declare-styleable name="ChartGridView">
<attr name="primaryDrawable" format="reference" />
<attr name="secondaryDrawable" format="reference" />
<attr name="borderDrawable" format="reference" />
- <attr name="labelColor" format="color" />
+ <attr name="labelColor" />
</declare-styleable>
<declare-styleable name="ChartNetworkSeriesView">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0378aeb..7454396 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3420,7 +3420,9 @@
<string name="data_usage_disabled_dialog_enable">Re-enable data</string>
<!-- Label displaying current network data usage warning threshold. [CHAR LIMIT=18] -->
- <string name="data_usage_sweep_warning"><font size="32"><xliff:g id="number" example="128">%1$s</xliff:g></font> <font size="12"><xliff:g id="unit" example="KB">%2$s</xliff:g></font>\n<font size="12">warning</font></string>
+ <string name="data_usage_sweep_warning"><font size="21"><xliff:g id="number" example="128">^1</xliff:g></font> <font size="9"><xliff:g id="unit" example="KB">^2</xliff:g></font>\n<font size="12">warning</font></string>
+ <!-- Label displaying current network data usage limit threshold. [CHAR LIMIT=18] -->
+ <string name="data_usage_sweep_limit"><font size="21"><xliff:g id="number" example="128">^1</xliff:g></font> <font size="9"><xliff:g id="unit" example="KB">^2</xliff:g></font>\n<font size="12">limit</font></string>
<!-- Button at the bottom of the CryptKeeper screen to make an emergency call. -->
<string name="cryptkeeper_emergency_call">Emergency call</string>
diff --git a/src/com/android/settings/widget/ChartAxis.java b/src/com/android/settings/widget/ChartAxis.java
index e761202..463541f 100644
--- a/src/com/android/settings/widget/ChartAxis.java
+++ b/src/com/android/settings/widget/ChartAxis.java
@@ -16,6 +16,9 @@
package com.android.settings.widget;
+import android.content.res.Resources;
+import android.text.SpannableStringBuilder;
+
/**
* Axis along a {@link ChartView} that knows how to convert between raw point
* and screen coordinate systems.
@@ -28,8 +31,7 @@
public float convertToPoint(long value);
public long convertToValue(float point);
- public CharSequence getLabel(long value);
- public CharSequence getShortLabel(long value);
+ public void buildLabel(Resources res, SpannableStringBuilder builder, long value);
public float[] getTickPoints();
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 6c9ded4..b0d00bb 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -19,8 +19,15 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.text.DynamicLayout;
+import android.text.Layout.Alignment;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.MotionEvent;
@@ -36,13 +43,20 @@
*/
public class ChartSweepView extends FrameLayout {
- // TODO: paint label when requested
-
private Drawable mSweep;
- private Rect mSweepMargins = new Rect();
+ private Rect mSweepPadding = new Rect();
+ private Point mSweepOffset = new Point();
+
+ private Rect mMargins = new Rect();
private int mFollowAxis;
- private boolean mShowLabel;
+
+ private int mLabelSize;
+ private int mLabelTemplateRes;
+ private int mLabelColor;
+
+ private SpannableStringBuilder mLabelTemplate;
+ private DynamicLayout mLabelLayout;
private ChartAxis mAxis;
private long mValue;
@@ -73,7 +87,10 @@
setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
- setShowLabel(a.getBoolean(R.styleable.ChartSweepView_showLabel, false));
+
+ setLabelSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
+ setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
+ setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
a.recycle();
@@ -90,27 +107,23 @@
return mFollowAxis;
}
- /**
- * Return margins of {@link #setSweepDrawable(Drawable)}, indicating how the
- * sweep should be displayed around a content region.
- */
- public Rect getSweepMargins() {
- return mSweepMargins;
+ public Rect getMargins() {
+ return mMargins;
}
/**
* Return the number of pixels that the "target" area is inset from the
* {@link View} edge, along the current {@link #setFollowAxis(int)}.
*/
- public float getTargetInset() {
+ private float getTargetInset() {
if (mFollowAxis == VERTICAL) {
- final float targetHeight = mSweep.getIntrinsicHeight() - mSweepMargins.top
- - mSweepMargins.bottom;
- return mSweepMargins.top + (targetHeight / 2);
+ final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
+ - mSweepPadding.bottom;
+ return mSweepPadding.top + (targetHeight / 2);
} else {
- final float targetWidth = mSweep.getIntrinsicWidth() - mSweepMargins.left
- - mSweepMargins.right;
- return mSweepMargins.left + (targetWidth / 2);
+ final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
+ - mSweepPadding.right;
+ return mSweepPadding.left + (targetWidth / 2);
}
}
@@ -124,6 +137,12 @@
}
}
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ requestLayout();
+ }
+
public void setSweepDrawable(Drawable sweep) {
if (mSweep != null) {
mSweep.setCallback(null);
@@ -137,7 +156,7 @@
}
sweep.setVisible(getVisibility() == VISIBLE, false);
mSweep = sweep;
- sweep.getPadding(mSweepMargins);
+ sweep.getPadding(mSweepPadding);
} else {
mSweep = null;
}
@@ -149,9 +168,49 @@
mFollowAxis = followAxis;
}
- public void setShowLabel(boolean showLabel) {
- mShowLabel = showLabel;
+ public void setLabelSize(int size) {
+ mLabelSize = size;
+ invalidateLabelTemplate();
+ }
+
+ public void setLabelTemplate(int resId) {
+ mLabelTemplateRes = resId;
+ invalidateLabelTemplate();
+ }
+
+ public void setLabelColor(int color) {
+ mLabelColor = color;
+ invalidateLabelTemplate();
+ }
+
+ private void invalidateLabelTemplate() {
+ if (mLabelTemplateRes != 0) {
+ final CharSequence template = getResources().getText(mLabelTemplateRes);
+
+ final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+ paint.density = getResources().getDisplayMetrics().density;
+ paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
+ paint.setColor(mLabelColor);
+
+ mLabelTemplate = new SpannableStringBuilder(template);
+ mLabelLayout = new DynamicLayout(
+ mLabelTemplate, paint, mLabelSize, Alignment.ALIGN_RIGHT, 1f, 0f, false);
+ invalidateLabel();
+
+ } else {
+ mLabelTemplate = null;
+ mLabelLayout = null;
+ }
+
invalidate();
+ requestLayout();
+ }
+
+ private void invalidateLabel() {
+ if (mLabelTemplate != null && mAxis != null) {
+ mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
+ invalidate();
+ }
}
@Override
@@ -181,6 +240,7 @@
public void setValue(long value) {
mValue = value;
+ invalidateLabel();
}
public long getValue() {
@@ -207,9 +267,9 @@
// only start tracking when in sweet spot
final boolean accept;
if (mFollowAxis == VERTICAL) {
- accept = event.getX() > getWidth() - (mSweepMargins.right * 2);
+ accept = event.getX() > getWidth() - (mSweepPadding.right * 2);
} else {
- accept = event.getY() > getHeight() - (mSweepMargins.bottom * 2);
+ accept = event.getY() > getHeight() - (mSweepPadding.bottom * 2);
}
if (accept) {
@@ -222,42 +282,40 @@
case MotionEvent.ACTION_MOVE: {
getParent().requestDisallowInterceptTouchEvent(true);
- final Rect sweepMargins = mSweepMargins;
-
// content area of parent
final Rect parentContent = new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
parent.getWidth() - parent.getPaddingRight(),
parent.getHeight() - parent.getPaddingBottom());
if (mFollowAxis == VERTICAL) {
- final float currentTargetY = getTop() + getTargetInset();
+ final float currentTargetY = getTop() - mMargins.top;
final float requestedTargetY = currentTargetY
+ (event.getRawY() - mTracking.getRawY());
final float clampedTargetY = MathUtils.constrain(
requestedTargetY, parentContent.top, parentContent.bottom);
setTranslationY(clampedTargetY - currentTargetY);
- mValue = mAxis.convertToValue(clampedTargetY - parentContent.top);
- dispatchOnSweep(false);
+ setValue(mAxis.convertToValue(clampedTargetY - parentContent.top));
} else {
- final float currentTargetX = getLeft() + getTargetInset();
+ final float currentTargetX = getLeft() - mMargins.left;
final float requestedTargetX = currentTargetX
+ (event.getRawX() - mTracking.getRawX());
final float clampedTargetX = MathUtils.constrain(
requestedTargetX, parentContent.left, parentContent.right);
setTranslationX(clampedTargetX - currentTargetX);
- mValue = mAxis.convertToValue(clampedTargetX - parentContent.left);
- dispatchOnSweep(false);
+ setValue(mAxis.convertToValue(clampedTargetX - parentContent.left));
}
+
+ dispatchOnSweep(false);
return true;
}
case MotionEvent.ACTION_UP: {
mTracking = null;
+ dispatchOnSweep(true);
setTranslationX(0);
setTranslationY(0);
requestLayout();
- dispatchOnSweep(true);
return true;
}
default: {
@@ -276,7 +334,39 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(mSweep.getIntrinsicWidth(), mSweep.getIntrinsicHeight());
+
+ // TODO: handle vertical labels
+ if (isEnabled() && mLabelLayout != null) {
+ final int sweepHeight = mSweep.getIntrinsicHeight();
+ final int templateHeight = mLabelLayout.getHeight();
+
+ mSweepOffset.x = 0;
+ mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
+ setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
+
+ } else {
+ mSweepOffset.x = 0;
+ mSweepOffset.y = 0;
+ setMeasuredDimension(mSweep.getIntrinsicWidth(), mSweep.getIntrinsicHeight());
+ }
+
+ if (mFollowAxis == VERTICAL) {
+ final int targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
+ - mSweepPadding.bottom;
+ mMargins.top = -(mSweepPadding.top + (targetHeight / 2));
+ mMargins.bottom = 0;
+ mMargins.left = -mSweepPadding.left;
+ mMargins.right = mSweepPadding.right;
+ } else {
+ final int targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
+ - mSweepPadding.right;
+ mMargins.left = -(mSweepPadding.left + (targetWidth / 2));
+ mMargins.right = 0;
+ mMargins.top = -mSweepPadding.top;
+ mMargins.bottom = mSweepPadding.bottom;
+ }
+
+ mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
}
@Override
@@ -284,7 +374,22 @@
final int width = getWidth();
final int height = getHeight();
- mSweep.setBounds(0, 0, width, height);
+ final int labelSize;
+ if (isEnabled() && mLabelLayout != null) {
+ mLabelLayout.draw(canvas);
+ labelSize = mLabelSize;
+ } else {
+ labelSize = 0;
+ }
+
+ if (mFollowAxis == VERTICAL) {
+ mSweep.setBounds(labelSize, mSweepOffset.y, width,
+ mSweepOffset.y + mSweep.getIntrinsicHeight());
+ } else {
+ mSweep.setBounds(mSweepOffset.x, labelSize,
+ mSweepOffset.x + mSweep.getIntrinsicWidth(), height);
+ }
+
mSweep.draw(canvas);
}
diff --git a/src/com/android/settings/widget/ChartView.java b/src/com/android/settings/widget/ChartView.java
index 9223ca6..a5b8b09 100644
--- a/src/com/android/settings/widget/ChartView.java
+++ b/src/com/android/settings/widget/ChartView.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
@@ -93,22 +92,22 @@
} else if (child instanceof ChartSweepView) {
// sweep is always placed along specific dimension
final ChartSweepView sweep = (ChartSweepView) child;
- final Rect sweepMargins = sweep.getSweepMargins();
+ final Rect sweepMargins = sweep.getMargins();
- if (sweep.getFollowAxis() == ChartSweepView.HORIZONTAL) {
- parentRect.left = parentRect.right =
- (int) (sweep.getPoint() - sweep.getTargetInset()) + getPaddingLeft();
- parentRect.top -= sweepMargins.top;
- parentRect.bottom += sweepMargins.bottom;
- Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(),
+ if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
+ parentRect.top += sweepMargins.top + (int) sweep.getPoint();
+ parentRect.bottom = parentRect.top;
+ parentRect.left += sweepMargins.left;
+ parentRect.right += sweepMargins.right;
+ Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(),
parentRect, childRect);
} else {
- parentRect.top = parentRect.bottom =
- (int) (sweep.getPoint() - sweep.getTargetInset()) + getPaddingTop();
- parentRect.left -= sweepMargins.left;
- parentRect.right += sweepMargins.right;
- Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(),
+ parentRect.left += sweepMargins.left + (int) sweep.getPoint();
+ parentRect.right = parentRect.left;
+ parentRect.top += sweepMargins.top;
+ parentRect.bottom += sweepMargins.bottom;
+ Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(),
parentRect, childRect);
}
}
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java
index a8bdaa6..89caef1 100644
--- a/src/com/android/settings/widget/DataUsageChartView.java
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -17,8 +17,12 @@
package com.android.settings.widget;
import android.content.Context;
+import android.content.res.Resources;
import android.net.NetworkPolicy;
import android.net.NetworkStatsHistory;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
@@ -283,15 +287,9 @@
}
/** {@inheritDoc} */
- public CharSequence getLabel(long value) {
- // TODO: convert to string
- return Long.toString(value);
- }
-
- /** {@inheritDoc} */
- public CharSequence getShortLabel(long value) {
- // TODO: convert to string
- return Long.toString(value);
+ public void buildLabel(Resources res, SpannableStringBuilder builder, long value) {
+ // TODO: convert to better string
+ builder.replace(0, builder.length(), Long.toString(value));
}
/** {@inheritDoc} */
@@ -345,16 +343,33 @@
return (long) fraction;
}
- /** {@inheritDoc} */
- public CharSequence getLabel(long value) {
- // TODO: use exploded string here
- return Long.toString(value);
- }
+ private static final Object sSpanSize = new Object();
+ private static final Object sSpanUnit = new Object();
/** {@inheritDoc} */
- public CharSequence getShortLabel(long value) {
- // TODO: convert to string
- return Long.toString(value);
+ public void buildLabel(Resources res, SpannableStringBuilder builder, long value) {
+
+ float result = value;
+ final CharSequence unit;
+ if (result <= 100 * MB_IN_BYTES) {
+ unit = res.getText(com.android.internal.R.string.megabyteShort);
+ result /= MB_IN_BYTES;
+ } else {
+ unit = res.getText(com.android.internal.R.string.gigabyteShort);
+ result /= GB_IN_BYTES;
+ }
+
+ final CharSequence size;
+ if (result < 10) {
+ size = String.format("%.1f", result);
+ } else {
+ size = String.format("%.0f", result);
+ }
+
+ final int[] sizeBounds = findOrCreateSpan(builder, sSpanSize, "^1");
+ builder.replace(sizeBounds[0], sizeBounds[1], size);
+ final int[] unitBounds = findOrCreateSpan(builder, sSpanUnit, "^2");
+ builder.replace(unitBounds[0], unitBounds[1], unit);
}
/** {@inheritDoc} */
@@ -372,4 +387,16 @@
}
}
+ private static int[] findOrCreateSpan(
+ SpannableStringBuilder builder, Object key, CharSequence bootstrap) {
+ int start = builder.getSpanStart(key);
+ int end = builder.getSpanEnd(key);
+ if (start == -1) {
+ start = TextUtils.indexOf(builder, bootstrap);
+ end = start + bootstrap.length();
+ builder.setSpan(key, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ return new int[] { start, end };
+ }
+
}
diff --git a/src/com/android/settings/widget/InvertedChartAxis.java b/src/com/android/settings/widget/InvertedChartAxis.java
index a30d24c..e589da9 100644
--- a/src/com/android/settings/widget/InvertedChartAxis.java
+++ b/src/com/android/settings/widget/InvertedChartAxis.java
@@ -16,6 +16,9 @@
package com.android.settings.widget;
+import android.content.res.Resources;
+import android.text.SpannableStringBuilder;
+
/**
* Utility to invert another {@link ChartAxis}.
*/
@@ -49,13 +52,8 @@
}
/** {@inheritDoc} */
- public CharSequence getLabel(long value) {
- return mWrapped.getLabel(value);
- }
-
- /** {@inheritDoc} */
- public CharSequence getShortLabel(long value) {
- return mWrapped.getShortLabel(value);
+ public void buildLabel(Resources res, SpannableStringBuilder builder, long value) {
+ mWrapped.buildLabel(res, builder, value);
}
/** {@inheritDoc} */