1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 package com.android.settings.widget; 17 18 import android.content.Context; 19 import android.content.res.Resources; 20 import android.graphics.Canvas; 21 import android.graphics.ColorFilter; 22 import android.graphics.Paint; 23 import android.graphics.PorterDuff; 24 import android.graphics.PorterDuffColorFilter; 25 import android.text.TextPaint; 26 import android.util.AttributeSet; 27 import android.view.View; 28 29 import com.android.settings.R; 30 import com.android.settings.Utils; 31 32 /** 33 * DonutView represents a donut graph. It visualizes a certain percentage of fullness with a 34 * corresponding label with the fullness on the inside (i.e. "50%" inside of the donut). 35 */ 36 public class DonutView extends View { 37 private static final int TOP = -90; 38 // From manual testing, this is the longest we can go without visual errors. 39 private static final int LINE_CHARACTER_LIMIT = 10; 40 private float mStrokeWidth; 41 private float mDeviceDensity; 42 private int mPercent; 43 private Paint mBackgroundCircle; 44 private Paint mFilledArc; 45 private TextPaint mTextPaint; 46 private TextPaint mBigNumberPaint; 47 private String mPercentString; 48 private String mFullString; 49 50 public DonutView(Context context) { 51 super(context); 52 } 53 54 public DonutView(Context context, AttributeSet attrs) { 55 super(context, attrs); 56 mDeviceDensity = getResources().getDisplayMetrics().density; 57 mStrokeWidth = 6f * mDeviceDensity; 58 final ColorFilter mAccentColorFilter = 59 new PorterDuffColorFilter( 60 Utils.getColorAttr(context, android.R.attr.colorAccent), 61 PorterDuff.Mode.SRC_IN); 62 63 mBackgroundCircle = new Paint(); 64 mBackgroundCircle.setAntiAlias(true); 65 mBackgroundCircle.setStrokeCap(Paint.Cap.BUTT); 66 mBackgroundCircle.setStyle(Paint.Style.STROKE); 67 mBackgroundCircle.setStrokeWidth(mStrokeWidth); 68 mBackgroundCircle.setColorFilter(mAccentColorFilter); 69 mBackgroundCircle.setColor(context.getColor(R.color.meter_background_color)); 70 71 mFilledArc = new Paint(); 72 mFilledArc.setAntiAlias(true); 73 mFilledArc.setStrokeCap(Paint.Cap.BUTT); 74 mFilledArc.setStyle(Paint.Style.STROKE); 75 mFilledArc.setStrokeWidth(mStrokeWidth); 76 mFilledArc.setColor(Utils.getDefaultColor(mContext, R.color.meter_consumed_color)); 77 mFilledArc.setColorFilter(mAccentColorFilter); 78 79 Resources resources = context.getResources(); 80 mTextPaint = new TextPaint(); 81 mTextPaint.setColor(Utils.getColorAccent(getContext())); 82 mTextPaint.setAntiAlias(true); 83 mTextPaint.setTextSize( 84 resources.getDimension(R.dimen.storage_donut_view_label_text_size)); 85 mTextPaint.setTextAlign(Paint.Align.CENTER); 86 87 mBigNumberPaint = new TextPaint(); 88 mBigNumberPaint.setColor(Utils.getColorAccent(getContext())); 89 mBigNumberPaint.setAntiAlias(true); 90 mBigNumberPaint.setTextSize( 91 resources.getDimension(R.dimen.storage_donut_view_percent_text_size)); 92 mBigNumberPaint.setTextAlign(Paint.Align.CENTER); 93 } 94 95 @Override 96 protected void onDraw(Canvas canvas) { 97 super.onDraw(canvas); 98 drawDonut(canvas); 99 drawInnerText(canvas); 100 } 101 102 private void drawDonut(Canvas canvas) { 103 canvas.drawArc( 104 0 + mStrokeWidth, 105 0 + mStrokeWidth, 106 getWidth() - mStrokeWidth, 107 getHeight() - mStrokeWidth, 108 TOP, 109 360, 110 false, 111 mBackgroundCircle); 112 113 canvas.drawArc( 114 0 + mStrokeWidth, 115 0 + mStrokeWidth, 116 getWidth() - mStrokeWidth, 117 getHeight() - mStrokeWidth, 118 TOP, 119 (360 * mPercent / 100), 120 false, 121 mFilledArc); 122 } 123 124 private void drawInnerText(Canvas canvas) { 125 final float centerX = getWidth() / 2; 126 final float centerY = getHeight() / 2; 127 final float totalHeight = getTextHeight(mTextPaint) + getTextHeight(mBigNumberPaint); 128 final float startY = centerY + totalHeight / 2; 129 130 // The first line is the height of the bottom text + its descender above the bottom line. 131 canvas.drawText(mPercentString, centerX, 132 startY - getTextHeight(mTextPaint) - mBigNumberPaint.descent(), 133 mBigNumberPaint); 134 // The second line starts at the bottom + room for the descender. 135 canvas.drawText(mFullString, centerX, startY - mTextPaint.descent(), mTextPaint); 136 } 137 138 /** 139 * Set a percentage full to have the donut graph. 140 */ 141 public void setPercentage(int percent) { 142 mPercent = percent; 143 mPercentString = Utils.formatPercentage(mPercent); 144 mFullString = getContext().getString(R.string.storage_percent_full); 145 if (mFullString.length() > LINE_CHARACTER_LIMIT) { 146 mTextPaint.setTextSize( 147 getContext() 148 .getResources() 149 .getDimension( 150 R.dimen.storage_donut_view_shrunken_label_text_size)); 151 } 152 invalidate(); 153 } 154 155 private float getTextHeight(TextPaint paint) { 156 // Technically, this should be the cap height, but I can live with the descent - ascent. 157 return paint.descent() - paint.ascent(); 158 } 159 } 160