summaryrefslogtreecommitdiffstats
path: root/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java')
-rwxr-xr-xsrc/org/cyanogenmod/themes/provider/view/BatteryMeterView.java733
1 files changed, 733 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java b/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java
new file mode 100755
index 0000000..4b772fa
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2013 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 org.cyanogenmod.themes.provider.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+import static org.cyanogenmod.themes.provider.util.SystemUiPreviewGenerator.SYSTEMUI_PACKAGE;
+
+public class BatteryMeterView extends View {
+ private static final String TAG = BatteryMeterView.class.getSimpleName();
+ private static final boolean ENABLE_PERCENT = true;
+
+ private static final String BATTERYMETER_COLOR_LEVELS = "batterymeter_color_levels";
+ private static final String BATTERYMETER_COLOR_VALUES = "batterymeter_color_values";
+ private static final String BATTERYMETER_FRAME_COLOR = "batterymeter_frame_color";
+ private static final String BATTERYMETER_BOLT_COLOR = "batterymeter_bolt_color";
+ private static final String BATTERYMETER_BOLT_POINTS = "batterymeter_bolt_points";
+ private static final String STATUS_BAR_CLOCK_COLOR = "status_bar_clock_color";
+
+ public static enum BatteryMeterMode {
+ BATTERY_METER_GONE,
+ BATTERY_METER_ICON_PORTRAIT,
+ BATTERY_METER_ICON_LANDSCAPE,
+ BATTERY_METER_CIRCLE,
+ BATTERY_METER_TEXT
+ }
+
+ private final Runnable mInvalidate = new Runnable() {
+ public void run() {
+ invalidateIfVisible();
+ }
+ };
+
+ private final Handler mHandler;
+
+ protected BatteryMeterMode mMeterMode = null;
+ protected boolean mShowPercent = false;
+ protected boolean mAttached;
+
+ private int mHeight;
+ private int mWidth;
+
+ final int[] mColors;
+ final int[] mBoltPoints;
+ final int mFrameColor;
+ final int mBoltColor;
+ final int mTextColor;
+
+ private BatteryMeterDrawable mBatteryMeterDrawable;
+ private final Object mLock = new Object();
+
+ private Resources mResources;
+
+ public BatteryMeterView(Context context, Resources res) {
+ this(context, null, 0, res);
+ }
+
+ public BatteryMeterView(Context context, AttributeSet attrs, Resources res) {
+ this(context, attrs, 0, res);
+ }
+
+ public BatteryMeterView(Context context, AttributeSet attrs, int defStyle, Resources res) {
+ super(context, attrs, defStyle);
+ mHandler = new Handler();
+
+ TypedArray levels = res.obtainTypedArray(res.getIdentifier(BATTERYMETER_COLOR_LEVELS,
+ "array", SYSTEMUI_PACKAGE));
+ TypedArray colors = res.obtainTypedArray(res.getIdentifier(BATTERYMETER_COLOR_VALUES,
+ "array", SYSTEMUI_PACKAGE));
+ TypedArray boltPoints = res.obtainTypedArray(res.getIdentifier(BATTERYMETER_BOLT_POINTS,
+ "array", SYSTEMUI_PACKAGE));
+
+ mResources = res;
+
+ int N = levels.length();
+ mColors = new int[2*N];
+ for (int i=0; i<N; i++) {
+ mColors[2*i] = levels.getInt(i, 0);
+ mColors[2*i+1] = colors.getColor(i, 0);
+ }
+ levels.recycle();
+ colors.recycle();
+
+ N = boltPoints.length();
+ mBoltPoints = new int[N];
+ for (int i = 0; i < N; i++) {
+ mBoltPoints[i] = boltPoints.getInt(i, 0);
+ }
+ boltPoints.recycle();
+
+ mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt(
+ context.getContentResolver(), "status_bar_show_battery_percent", 0);
+
+ mMeterMode = BatteryMeterMode.BATTERY_METER_CIRCLE;
+ mBatteryMeterDrawable = createBatteryMeterDrawable(mMeterMode);
+
+ mFrameColor = res.getColor(res.getIdentifier(BATTERYMETER_FRAME_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+ mBoltColor = res.getColor(res.getIdentifier(BATTERYMETER_BOLT_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+ mTextColor = res.getColor(res.getIdentifier(STATUS_BAR_CLOCK_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ protected BatteryMeterDrawable createBatteryMeterDrawable(BatteryMeterMode mode) {
+ Resources res = mResources;
+ switch (mode) {
+ case BATTERY_METER_CIRCLE:
+ return new CircleBatteryMeterDrawable(res);
+
+ case BATTERY_METER_TEXT:
+ return new TextBatteryMeterDrawable(res);
+
+ case BATTERY_METER_ICON_LANDSCAPE:
+ return new NormalBatteryMeterDrawable(res, true);
+
+ default:
+ return new NormalBatteryMeterDrawable(res, false);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ if (mMeterMode == BatteryMeterMode.BATTERY_METER_CIRCLE) {
+ height += (CircleBatteryMeterDrawable.STROKE_WITH / 3);
+ width = height;
+ } else if (mMeterMode == BatteryMeterMode.BATTERY_METER_TEXT) {
+ width = (int)((TextBatteryMeterDrawable) mBatteryMeterDrawable).calculateMeasureWidth();
+ onSizeChanged(width, height, 0, 0); // Force a size changed event
+ } else if (mMeterMode.compareTo(BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE) == 0) {
+ width = (int)(height * 1.2f);
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mHeight = h;
+ mWidth = w;
+ synchronized (mLock) {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onSizeChanged(w, h, oldw, oldh);
+ }
+ }
+ }
+
+ protected void invalidateIfVisible() {
+ if (getVisibility() == View.VISIBLE && mAttached) {
+ if (mAttached) {
+ postInvalidate();
+ } else {
+ invalidate();
+ }
+ }
+ }
+
+ public int getColorForLevel(int percent) {
+ int thresh, color = 0;
+ for (int i=0; i<mColors.length; i+=2) {
+ thresh = mColors[i];
+ color = mColors[i+1];
+ if (percent <= thresh) return color;
+ }
+ return color;
+ }
+
+ public void setShowPercent(boolean show) {
+ if (ENABLE_PERCENT) {
+ mShowPercent = show;
+ invalidateIfVisible();
+ }
+ }
+
+ public void setMode(BatteryMeterMode mode) {
+ if (mMeterMode == mode) {
+ return;
+ }
+
+ mMeterMode = mode;
+ if (mode == BatteryMeterMode.BATTERY_METER_GONE) {
+ setVisibility(View.GONE);
+ synchronized (mLock) {
+ mBatteryMeterDrawable = null;
+ }
+ } else {
+ synchronized (mLock) {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onDispose();
+ }
+ mBatteryMeterDrawable = createBatteryMeterDrawable(mode);
+ }
+ if (mMeterMode == BatteryMeterMode.BATTERY_METER_ICON_PORTRAIT ||
+ mMeterMode == BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE) {
+ ((NormalBatteryMeterDrawable)mBatteryMeterDrawable).loadBoltPoints(
+ mContext.getResources());
+ }
+ setVisibility(View.VISIBLE);
+ postInvalidate();
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void draw(Canvas c) {
+ synchronized (mLock) {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onDraw(c);
+ }
+ }
+ }
+
+ protected interface BatteryMeterDrawable {
+ void onDraw(Canvas c);
+ void onSizeChanged(int w, int h, int oldw, int oldh);
+ void onDispose();
+ }
+
+ protected class NormalBatteryMeterDrawable implements BatteryMeterDrawable {
+
+ public static final boolean SINGLE_DIGIT_PERCENT = false;
+
+ public static final int FULL = 96;
+ public static final int EMPTY = 4;
+
+ public static final float SUBPIXEL = 0.4f; // inset rects for softer edges
+
+ private boolean mDisposed;
+
+ protected final boolean mHorizontal;
+
+ private Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
+ private int mButtonHeight;
+ private float mTextHeight;
+
+ private final float[] mBoltPoints;
+ private final Path mBoltPath = new Path();
+
+ private final RectF mFrame = new RectF();
+ private final RectF mButtonFrame = new RectF();
+ private final RectF mClipFrame = new RectF();
+ private final RectF mBoltFrame = new RectF();
+
+ public NormalBatteryMeterDrawable(Resources res, boolean horizontal) {
+ super();
+ mHorizontal = horizontal;
+ mDisposed = false;
+
+ mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mFramePaint.setColor(mFrameColor);
+ mFramePaint.setDither(true);
+ mFramePaint.setStrokeWidth(0);
+ mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ mFramePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
+
+ mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBatteryPaint.setDither(true);
+ mBatteryPaint.setStrokeWidth(0);
+ mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setColor(mBoltColor);
+ Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD);
+ mTextPaint.setTypeface(font);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+
+ mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mWarningTextPaint.setColor(mColors[1]);
+ font = Typeface.create("sans-serif", Typeface.BOLD);
+ mWarningTextPaint.setTypeface(font);
+ mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
+
+ mBoltPaint = new Paint();
+ mBoltPaint.setAntiAlias(true);
+ mBoltPaint.setColor(mFrameColor);
+ mBoltPoints = loadBoltPoints(res);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mDisposed) return;
+
+ final int level = 50;
+
+ float drawFrac = (float) level / 100f;
+ final int pt = getPaddingTop() + (mHorizontal ? (int)(mHeight * 0.20f) : 0);
+ final int pl = getPaddingLeft();
+ final int pr = getPaddingRight();
+ final int pb = getPaddingBottom();
+ int height = mHeight - pt - pb;
+ int width = mWidth - pl - pr;
+
+ mButtonHeight = (int) ((mHorizontal ? width : height) * 0.12f);
+
+ mFrame.set(0, 0, width, height);
+ mFrame.offset(pl, pt);
+
+ if (mHorizontal) {
+ mButtonFrame.set(
+ /*cover frame border of intersecting area*/
+ width - (mButtonHeight + 5) - mFrame.left,
+ mFrame.top + height * 0.25f,
+ mFrame.right,
+ mFrame.bottom - height * 0.25f);
+
+ mButtonFrame.top += SUBPIXEL;
+ mButtonFrame.bottom -= SUBPIXEL;
+ mButtonFrame.right -= SUBPIXEL;
+ } else {
+ mButtonFrame.set(
+ mFrame.left + width * 0.25f,
+ mFrame.top,
+ mFrame.right - width * 0.25f,
+ mFrame.top + mButtonHeight + 5 /*cover frame border of intersecting area*/);
+
+ mButtonFrame.top += SUBPIXEL;
+ mButtonFrame.left += SUBPIXEL;
+ mButtonFrame.right -= SUBPIXEL;
+ }
+
+ if (mHorizontal) {
+ mFrame.right -= mButtonHeight;
+ } else {
+ mFrame.top += mButtonHeight;
+ }
+ mFrame.left += SUBPIXEL;
+ mFrame.top += SUBPIXEL;
+ mFrame.right -= SUBPIXEL;
+ mFrame.bottom -= SUBPIXEL;
+
+ // first, draw the battery shape
+ c.drawRect(mFrame, mFramePaint);
+
+ // fill 'er up
+ final int color = getColorForLevel(level);
+ mBatteryPaint.setColor(color);
+
+ if (level >= FULL) {
+ drawFrac = 1f;
+ } else if (level <= EMPTY) {
+ drawFrac = 0f;
+ }
+
+ c.drawRect(mButtonFrame, drawFrac == 1f ? mBatteryPaint : mFramePaint);
+
+ mClipFrame.set(mFrame);
+ if (mHorizontal) {
+ mClipFrame.right -= (mFrame.width() * (1f - drawFrac));
+ } else {
+ mClipFrame.top += (mFrame.height() * (1f - drawFrac));
+ }
+
+ c.save(Canvas.CLIP_SAVE_FLAG);
+ c.clipRect(mClipFrame);
+ c.drawRect(mFrame, mBatteryPaint);
+ c.restore();
+
+ // draw the bolt
+ final float bl = (int)(mFrame.left + mFrame.width() / (mHorizontal ? 9f : 4.5f));
+ final float bt = (int)(mFrame.top + mFrame.height() / (mHorizontal ? 4.5f : 6f));
+ final float br = (int)(mFrame.right - mFrame.width() / (mHorizontal ? 6f : 7f));
+ final float bb = (int)(mFrame.bottom - mFrame.height() / (mHorizontal ? 7f : 10f));
+ if (mBoltFrame.left != bl || mBoltFrame.top != bt
+ || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
+ mBoltFrame.set(bl, bt, br, bb);
+ mBoltPath.reset();
+ mBoltPath.moveTo(
+ mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+ for (int i = 2; i < mBoltPoints.length; i += 2) {
+ mBoltPath.lineTo(
+ mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height());
+ }
+ mBoltPath.lineTo(
+ mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+ }
+ c.drawPath(mBoltPath, mBoltPaint);
+ if (mShowPercent) {
+ final float nofull = mHorizontal ? 0.75f : 0.6f;
+ final float single = mHorizontal ? 0.86f : 0.75f;
+ mTextPaint.setTextSize(height *
+ (SINGLE_DIGIT_PERCENT ? single
+ : nofull));
+ mTextHeight = -mTextPaint.getFontMetrics().ascent;
+
+ final String str = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level);
+ final float x = mWidth * 0.5f;
+ final float y = pt + (height + mTextHeight) * 0.47f;
+
+ c.drawText(str, x, y, mTextPaint);
+ }
+ }
+
+ @Override
+ public void onDispose() {
+ mHandler.removeCallbacks(mInvalidate);
+ mDisposed = true;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWarningTextPaint.setTextSize(h * 0.75f);
+ }
+
+ private float[] loadBoltPoints(Resources res) {
+ final int[] pts = BatteryMeterView.this.mBoltPoints;
+ int maxX = 0, maxY = 0;
+ for (int i = 0; i < pts.length; i += 2) {
+ maxX = Math.max(maxX, pts[i]);
+ maxY = Math.max(maxY, pts[i + 1]);
+ }
+ final float[] ptsF = new float[pts.length];
+ for (int i = 0; i < pts.length; i += 2) {
+ ptsF[i] = (float)pts[i] / maxX;
+ ptsF[i + 1] = (float)pts[i + 1] / maxY;
+ }
+ return ptsF;
+ }
+ }
+
+ protected class CircleBatteryMeterDrawable implements BatteryMeterDrawable {
+
+ public static final float STROKE_WITH = 6.5f;
+
+ private boolean mDisposed;
+
+ private int mAnimOffset;
+ private boolean mIsAnimating; // stores charge-animation status to reliably
+ //remove callbacks
+
+ private int mCircleSize; // draw size of circle
+ private RectF mRectLeft; // contains the precalculated rect used in drawArc(),
+ // derived from mCircleSize
+ private float mTextX, mTextY; // precalculated position for drawText() to appear centered
+
+ private Paint mTextPaint;
+ private Paint mFrontPaint;
+ private Paint mBackPaint;
+ private Paint mBoltPaint;
+
+ private final RectF mBoltFrame = new RectF();
+ private final float[] mBoltPoints;
+ private final Path mBoltPath = new Path();
+
+ public CircleBatteryMeterDrawable(Resources res) {
+ super();
+ mDisposed = false;
+
+ mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setColor(mTextColor);
+ Typeface font = Typeface.create("sans-serif-condensed", Typeface.NORMAL);
+ mTextPaint.setTypeface(font);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+
+ mFrontPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mFrontPaint.setStrokeCap(Paint.Cap.BUTT);
+ mFrontPaint.setDither(true);
+ mFrontPaint.setStrokeWidth(0);
+ mFrontPaint.setStyle(Paint.Style.STROKE);
+
+ mBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBackPaint.setColor(mFrameColor);
+ mBackPaint.setStrokeCap(Paint.Cap.BUTT);
+ mBackPaint.setDither(true);
+ mBackPaint.setStrokeWidth(0);
+ mBackPaint.setStyle(Paint.Style.STROKE);
+ mBackPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
+
+ mBoltPaint = new Paint();
+ mBoltPaint.setAntiAlias(true);
+ mBoltPaint.setColor(getColorForLevel(50));
+ mBoltPoints = loadBoltPoints(res);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mDisposed) return;
+
+ if (mRectLeft == null) {
+ initSizeBasedStuff();
+ }
+
+ drawCircle(c, mTextX, mRectLeft);
+ }
+
+ @Override
+ public void onDispose() {
+ mHandler.removeCallbacks(mInvalidate);
+ mDisposed = true;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ initSizeBasedStuff();
+ }
+
+ private float[] loadBoltPoints(Resources res) {
+ final int[] pts = BatteryMeterView.this.mBoltPoints;
+ int maxX = 0, maxY = 0;
+ for (int i = 0; i < pts.length; i += 2) {
+ maxX = Math.max(maxX, pts[i]);
+ maxY = Math.max(maxY, pts[i + 1]);
+ }
+ final float[] ptsF = new float[pts.length];
+ for (int i = 0; i < pts.length; i += 2) {
+ ptsF[i] = (float)pts[i] / maxX;
+ ptsF[i + 1] = (float)pts[i + 1] / maxY;
+ }
+ return ptsF;
+ }
+
+ private void drawCircle(Canvas canvas, float textX, RectF drawRect) {
+ int level = 50;
+ Paint paint;
+
+ paint = mFrontPaint;
+ paint.setColor(getColorForLevel(level));
+
+ // draw thin gray ring first
+ canvas.drawArc(drawRect, 270, 360, false, mBackPaint);
+ // draw colored arc representing charge level
+ canvas.drawArc(drawRect, 270 + mAnimOffset, 3.6f * level, false, paint);
+ // if chosen by options, draw percentage text in the middle
+ // always skip percentage when 100, so layout doesnt break
+
+ // draw the bolt
+ final float bl = (int)(drawRect.left + drawRect.width() / 3.2f);
+ final float bt = (int)(drawRect.top + drawRect.height() / 4f);
+ final float br = (int)(drawRect.right - drawRect.width() / 5.2f);
+ final float bb = (int)(drawRect.bottom - drawRect.height() / 8f);
+ if (mBoltFrame.left != bl || mBoltFrame.top != bt
+ || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
+ mBoltFrame.set(bl, bt, br, bb);
+ mBoltPath.reset();
+ mBoltPath.moveTo(
+ mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+ for (int i = 2; i < mBoltPoints.length; i += 2) {
+ mBoltPath.lineTo(
+ mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height());
+ }
+ mBoltPath.lineTo(
+ mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+ mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+ }
+ canvas.drawPath(mBoltPath, mBoltPaint);
+ }
+
+ /**
+ * initializes all size dependent variables
+ * sets stroke width and text size of all involved paints
+ * YES! i think the method name is appropriate
+ */
+ private void initSizeBasedStuff() {
+ mCircleSize = Math.min(getMeasuredWidth(), getMeasuredHeight());
+ mTextPaint.setTextSize(mCircleSize / 2f);
+
+ float strokeWidth = mCircleSize / STROKE_WITH;
+ mFrontPaint.setStrokeWidth(strokeWidth);
+ mBackPaint.setStrokeWidth(strokeWidth);
+
+ // calculate rectangle for drawArc calls
+ int pLeft = getPaddingLeft();
+ mRectLeft = new RectF(pLeft + strokeWidth / 2.0f, 0 + strokeWidth / 2.0f, mCircleSize
+ - strokeWidth / 2.0f + pLeft, mCircleSize - strokeWidth / 2.0f);
+
+ // calculate Y position for text
+ Rect bounds = new Rect();
+ mTextPaint.getTextBounds("99", 0, "99".length(), bounds);
+ mTextX = mCircleSize / 2.0f + getPaddingLeft();
+ // the +1dp at end of formula balances out rounding issues.works out on all resolutions
+ mTextY = mCircleSize / 2.0f + (bounds.bottom - bounds.top) / 2.0f
+ - strokeWidth / 2.0f + getResources().getDisplayMetrics().density;
+ }
+ }
+
+ protected class TextBatteryMeterDrawable implements BatteryMeterDrawable {
+
+ private static final boolean DRAW_LEVEL = false;
+
+ public static final int FULL = 96;
+ public static final int EMPTY = 4;
+
+ private boolean mDisposed;
+
+ private float mTextX;
+ private float mTextY;
+
+ private boolean mOldPlugged = false;
+ private int mOldLevel = -1;
+
+ private boolean mIsAnimating;
+ private int mAnimOffset;
+
+ private Paint mBackPaint;
+ private Paint mFrontPaint;
+
+ public TextBatteryMeterDrawable(Resources res) {
+ super();
+ mDisposed = false;
+ mIsAnimating = false;
+
+ DisplayMetrics dm = res.getDisplayMetrics();
+
+ mBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBackPaint.setTextAlign(Paint.Align.RIGHT);
+ mBackPaint.setColor(mFrameColor);
+ mBackPaint.setTextSize(16.0f * dm.density);
+
+ mFrontPaint = new Paint(mBackPaint);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mDisposed) return;
+
+ int level = 50;
+ boolean plugged = true;
+
+ if (mOldLevel != level || mOldPlugged != plugged) {
+ mOldLevel = level;
+ mOldPlugged = plugged;
+
+ postInvalidate();
+ requestLayout();
+ return;
+ }
+
+ mFrontPaint.setColor(getColorForLevel(level));
+
+ // Is plugged? Then use the animation status
+ drawWithLevel(c, mAnimOffset, getLevel(level));
+ }
+
+ private void drawWithLevel(Canvas c, int level, String levelTxt) {
+ Rect bounds = getBounds(level);
+
+ // Draw the background
+ c.drawText(levelTxt, mTextX, mTextY, mBackPaint);
+
+ // Draw the foreground
+ c.save();
+ c.clipRect(0.0f, mTextY - ((level * bounds.height()) / 100.0f), mTextX, mTextY);
+ c.drawText(levelTxt, mTextX, mTextY, mFrontPaint);
+ c.restore();
+ }
+
+ private void drawWithoutLevel(Canvas c, String levelTxt) {
+ // We need to draw the overlay back paint to get the proper color
+ c.drawText(levelTxt, mTextX, mTextY, mBackPaint);
+ c.drawText(levelTxt, mTextX, mTextY, mFrontPaint);
+ }
+
+ @Override
+ public void onDispose() {
+ mHandler.removeCallbacks(mInvalidate);
+ mDisposed = true;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ Rect bounds = getBounds(50);
+ float onedp = mContext.getResources().getDisplayMetrics().density * 0.5f;
+ float height = h - getPaddingBottom() - getPaddingTop();
+
+ mTextX = w;
+ mTextY = h - getPaddingBottom() - (height / 2 - bounds.height() /2) + onedp;
+ }
+
+ protected float calculateMeasureWidth() {
+ Rect bounds = getBounds(50);
+ float onedp = mContext.getResources().getDisplayMetrics().density;
+ return bounds.width() + getPaddingStart() + getPaddingEnd() + onedp;
+ }
+
+ private Rect getBounds(int level) {
+ Rect bounds = new Rect();
+ String levelTxt = getLevel(level);
+ mBackPaint.getTextBounds(levelTxt, 0, levelTxt.length(), bounds);
+ return bounds;
+ }
+
+ private String getLevel(int level) {
+ if (level == -1) {
+ return String.format("?", level);
+ }
+ return String.format("%s%%", level);
+ }
+
+ private void resetChargeAnimation() {
+ if (mIsAnimating) {
+ mIsAnimating = false;
+ mAnimOffset = 0;
+ mHandler.removeCallbacks(mInvalidate);
+ }
+ }
+ }
+}