Iterate on data usage chart UI.

Switched to inflating chart views from XML, using attributes for
configuration.  Start using drawable assets for chart components
instead of manually painting.  Include hand-cut assets, and animate
between states when touched to invoke.

Clamp sweeps to valid chart ranges and prepare for sweep labels.

Bug: 4768483, 4598460
Change-Id: Ic660c35bec826eb5e3f6a1dde3cc04d8c437ef2b
This commit is contained in:
Jeff Sharkey
2011-06-23 00:39:38 -07:00
parent aef3981e86
commit 52c3f4461b
27 changed files with 608 additions and 223 deletions

View File

@@ -17,63 +17,79 @@
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.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import com.android.settings.R;
import com.google.common.base.Preconditions;
/**
* Sweep across a {@link ChartView} at a specific {@link ChartAxis} value, which
* a user can drag.
*/
public class ChartSweepView extends View {
public class ChartSweepView extends FrameLayout {
private final Paint mPaintSweep;
private final Paint mPaintSweepDisabled;
private final Paint mPaintShadow;
// TODO: paint label when requested
private final ChartAxis mAxis;
private Drawable mSweep;
private int mFollowAxis;
private boolean mShowLabel;
private ChartAxis mAxis;
private long mValue;
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
public interface OnSweepListener {
public void onSweep(ChartSweepView sweep, boolean sweepDone);
}
private OnSweepListener mListener;
private boolean mHorizontal;
private MotionEvent mTracking;
public ChartSweepView(Context context, ChartAxis axis, long value, int color) {
super(context);
public ChartSweepView(Context context) {
this(context, null, 0);
}
public ChartSweepView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ChartSweepView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ChartSweepView, defStyle, 0);
setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
setShowLabel(a.getBoolean(R.styleable.ChartSweepView_showLabel, false));
a.recycle();
setClipToPadding(false);
setClipChildren(false);
setWillNotDraw(false);
}
void init(ChartAxis axis) {
mAxis = Preconditions.checkNotNull(axis, "missing axis");
mValue = value;
}
mPaintSweep = new Paint();
mPaintSweep.setColor(color);
mPaintSweep.setStrokeWidth(3.0f);
mPaintSweep.setStyle(Style.FILL_AND_STROKE);
mPaintSweep.setAntiAlias(true);
mPaintSweepDisabled = new Paint();
mPaintSweepDisabled.setColor(color);
mPaintSweepDisabled.setStrokeWidth(1.5f);
mPaintSweepDisabled.setStyle(Style.FILL_AND_STROKE);
mPaintSweepDisabled.setPathEffect(new DashPathEffect(new float[] { 5, 5 }, 0));
mPaintSweepDisabled.setAntiAlias(true);
mPaintShadow = new Paint();
mPaintShadow.setColor(Color.BLACK);
mPaintShadow.setStrokeWidth(6.0f);
mPaintShadow.setStyle(Style.FILL_AND_STROKE);
mPaintShadow.setAntiAlias(true);
public int getFollowAxis() {
return mFollowAxis;
}
public void getExtraMargins(Rect rect) {
mSweep.getPadding(rect);
}
public void addOnSweepListener(OnSweepListener listener) {
@@ -86,6 +102,56 @@ public class ChartSweepView extends View {
}
}
public void setSweepDrawable(Drawable sweep) {
if (mSweep != null) {
mSweep.setCallback(null);
unscheduleDrawable(mSweep);
}
if (sweep != null) {
sweep.setCallback(this);
if (sweep.isStateful()) {
sweep.setState(getDrawableState());
}
sweep.setVisible(getVisibility() == VISIBLE, false);
mSweep = sweep;
} else {
mSweep = null;
}
invalidate();
}
public void setFollowAxis(int followAxis) {
mFollowAxis = followAxis;
}
public void setShowLabel(boolean showLabel) {
mShowLabel = showLabel;
invalidate();
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mSweep != null) {
mSweep.jumpToCurrentState();
}
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (mSweep != null) {
mSweep.setVisible(visibility == VISIBLE, false);
}
}
@Override
protected boolean verifyDrawable(Drawable who) {
return who == mSweep || super.verifyDrawable(who);
}
public ChartAxis getAxis() {
return mAxis;
}
@@ -115,14 +181,24 @@ public class ChartSweepView extends View {
case MotionEvent.ACTION_MOVE: {
getParent().requestDisallowInterceptTouchEvent(true);
if (mHorizontal) {
setTranslationY(event.getRawY() - mTracking.getRawY());
if (mFollowAxis == VERTICAL) {
final float chartHeight = parent.getHeight() - parent.getPaddingTop()
- parent.getPaddingBottom();
final float translationY = MathUtils.constrain(
event.getRawY() - mTracking.getRawY(), -getTop(),
chartHeight - getTop());
setTranslationY(translationY);
final float point = (getTop() + getTranslationY() + (getHeight() / 2))
- parent.getPaddingTop();
mValue = mAxis.convertToValue(point);
dispatchOnSweep(false);
} else {
setTranslationX(event.getRawX() - mTracking.getRawX());
final float chartWidth = parent.getWidth() - parent.getPaddingLeft()
- parent.getPaddingRight();
final float translationX = MathUtils.constrain(
event.getRawX() - mTracking.getRawX(), -getLeft(),
chartWidth - getLeft());
setTranslationX(translationX);
final float point = (getLeft() + getTranslationX() + (getWidth() / 2))
- parent.getPaddingLeft();
mValue = mAxis.convertToValue(point);
@@ -144,41 +220,26 @@ public class ChartSweepView extends View {
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mSweep.isStateful()) {
mSweep.setState(getDrawableState());
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// need at least 50px in each direction for grippies
// TODO: provide this value through params
setMeasuredDimension(50, 50);
setMeasuredDimension(mSweep.getIntrinsicWidth(), mSweep.getIntrinsicHeight());
}
@Override
protected void onDraw(Canvas canvas) {
// draw line across larger dimension
final int width = getWidth();
final int height = getHeight();
mHorizontal = width > height;
final Paint linePaint = isEnabled() ? mPaintSweep : mPaintSweepDisabled;
if (mHorizontal) {
final int centerY = height / 2;
final int endX = width - height;
canvas.drawLine(0, centerY, endX, centerY, mPaintShadow);
canvas.drawLine(0, centerY, endX, centerY, linePaint);
canvas.drawCircle(endX, centerY, 4.0f, mPaintShadow);
canvas.drawCircle(endX, centerY, 4.0f, mPaintSweep);
} else {
final int centerX = width / 2;
final int endY = height - width;
canvas.drawLine(centerX, 0, centerX, endY, mPaintShadow);
canvas.drawLine(centerX, 0, centerX, endY, linePaint);
canvas.drawCircle(centerX, endY, 4.0f, mPaintShadow);
canvas.drawCircle(centerX, endY, 4.0f, mPaintSweep);
}
mSweep.setBounds(0, 0, width, height);
mSweep.draw(canvas);
}
}