OSDN Git Service

Draw vertical data usage sweep labels.
authorJeff Sharkey <jsharkey@android.com>
Sat, 9 Jul 2011 08:02:56 +0000 (01:02 -0700)
committerJeff Sharkey <jsharkey@android.com>
Sat, 9 Jul 2011 08:02:56 +0000 (01:02 -0700)
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

res/layout/data_usage_chart.xml
res/values/attrs.xml
res/values/strings.xml
src/com/android/settings/widget/ChartAxis.java
src/com/android/settings/widget/ChartSweepView.java
src/com/android/settings/widget/ChartView.java
src/com/android/settings/widget/DataUsageChartView.java
src/com/android/settings/widget/InvertedChartAxis.java

index 199a38e..a942bb3 100644 (file)
         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" />
+        settings:followAxis="horizontal" />
 
     <com.android.settings.widget.ChartSweepView
-        android:id="@+id/sweep_limit"
+        android:id="@+id/sweep_warning"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        settings:sweepDrawable="@drawable/data_sweep_limit"
+        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_warning"
+        android:id="@+id/sweep_limit"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        settings:sweepDrawable="@drawable/data_sweep_warning"
+        settings:sweepDrawable="@drawable/data_sweep_limit"
         settings:followAxis="vertical"
-        settings:showLabel="true" />
+        settings:labelSize="60dip"
+        settings:labelTemplate="@string/data_usage_sweep_limit"
+        settings:labelColor="#c01a2c" />
 
 </com.android.settings.widget.DataUsageChartView>
index 06d2650..a0a6c77 100644 (file)
             <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">
index 0378aeb..7454396 100644 (file)
@@ -3420,7 +3420,9 @@ found in the list of installed applications.</string>
     <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>
index e761202..463541f 100644 (file)
@@ -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 interface ChartAxis {
     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();
 
index 6c9ded4..b0d00bb 100644 (file)
@@ -19,8 +19,15 @@ package com.android.settings.widget;
 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 @@ import com.google.common.base.Preconditions;
  */
 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 @@ public class ChartSweepView extends FrameLayout {
 
         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 @@ public class ChartSweepView extends FrameLayout {
         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 @@ public class ChartSweepView extends FrameLayout {
         }
     }
 
+    @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 @@ public class ChartSweepView extends FrameLayout {
             }
             sweep.setVisible(getVisibility() == VISIBLE, false);
             mSweep = sweep;
-            sweep.getPadding(mSweepMargins);
+            sweep.getPadding(mSweepPadding);
         } else {
             mSweep = null;
         }
@@ -149,9 +168,49 @@ public class ChartSweepView extends FrameLayout {
         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 class ChartSweepView extends FrameLayout {
 
     public void setValue(long value) {
         mValue = value;
+        invalidateLabel();
     }
 
     public long getValue() {
@@ -207,9 +267,9 @@ public class ChartSweepView extends FrameLayout {
                 // 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 @@ public class ChartSweepView extends FrameLayout {
             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 @@ public class ChartSweepView extends FrameLayout {
 
     @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 @@ public class ChartSweepView extends FrameLayout {
         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);
     }
 
index 9223ca6..a5b8b09 100644 (file)
@@ -21,7 +21,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
 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 @@ public class ChartView extends FrameLayout {
             } 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);
                 }
             }
index a8bdaa6..89caef1 100644 (file)
 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 @@ public class DataUsageChartView extends ChartView {
         }
 
         /** {@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 @@ public class DataUsageChartView extends ChartView {
             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 @@ public class DataUsageChartView extends ChartView {
         }
     }
 
+    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 };
+    }
+
 }
index a30d24c..e589da9 100644 (file)
@@ -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 @@ public class InvertedChartAxis implements ChartAxis {
     }
 
     /** {@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} */