| /* |
| * Copyright (C) 2016 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 com.android.settingslib.graph; |
| |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.CornerPathEffect; |
| import android.graphics.DashPathEffect; |
| import android.graphics.LinearGradient; |
| import android.graphics.Paint; |
| import android.graphics.Paint.Cap; |
| import android.graphics.Paint.Join; |
| import android.graphics.Paint.Style; |
| import android.graphics.Path; |
| import android.graphics.Shader.TileMode; |
| import android.graphics.drawable.Drawable; |
| import android.util.AttributeSet; |
| import android.util.SparseIntArray; |
| import android.util.TypedValue; |
| import android.view.View; |
| import com.android.settingslib.R; |
| |
| public class UsageGraph extends View { |
| |
| private static final int PATH_DELIM = -1; |
| |
| private final Paint mLinePaint; |
| private final Paint mFillPaint; |
| private final Paint mDottedPaint; |
| |
| private final Drawable mDivider; |
| private final Drawable mTintedDivider; |
| private final int mDividerSize; |
| |
| private final Path mPath = new Path(); |
| |
| // Paths in coordinates they are passed in. |
| private final SparseIntArray mPaths = new SparseIntArray(); |
| // Paths in local coordinates for drawing. |
| private final SparseIntArray mLocalPaths = new SparseIntArray(); |
| private final int mCornerRadius; |
| |
| private int mAccentColor; |
| private boolean mShowProjection; |
| private boolean mProjectUp; |
| |
| private float mMaxX = 100; |
| private float mMaxY = 100; |
| |
| private float mMiddleDividerLoc = .5f; |
| private int mMiddleDividerTint = -1; |
| private int mTopDividerTint = -1; |
| |
| public UsageGraph(Context context, @Nullable AttributeSet attrs) { |
| super(context, attrs); |
| final Resources resources = context.getResources(); |
| |
| mLinePaint = new Paint(); |
| mLinePaint.setStyle(Style.STROKE); |
| mLinePaint.setStrokeCap(Cap.ROUND); |
| mLinePaint.setStrokeJoin(Join.ROUND); |
| mLinePaint.setAntiAlias(true); |
| mCornerRadius = resources.getDimensionPixelSize(R.dimen.usage_graph_line_corner_radius); |
| mLinePaint.setPathEffect(new CornerPathEffect(mCornerRadius)); |
| mLinePaint.setStrokeWidth(resources.getDimensionPixelSize(R.dimen.usage_graph_line_width)); |
| |
| mFillPaint = new Paint(mLinePaint); |
| mFillPaint.setStyle(Style.FILL); |
| |
| mDottedPaint = new Paint(mLinePaint); |
| mDottedPaint.setStyle(Style.STROKE); |
| float dots = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_size); |
| float interval = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_interval); |
| mDottedPaint.setStrokeWidth(dots * 3); |
| mDottedPaint.setPathEffect(new DashPathEffect(new float[] {dots, interval}, 0)); |
| mDottedPaint.setColor(context.getColor(R.color.usage_graph_dots)); |
| |
| TypedValue v = new TypedValue(); |
| context.getTheme().resolveAttribute(com.android.internal.R.attr.listDivider, v, true); |
| mDivider = context.getDrawable(v.resourceId); |
| mTintedDivider = context.getDrawable(v.resourceId); |
| mDividerSize = resources.getDimensionPixelSize(R.dimen.usage_graph_divider_size); |
| } |
| |
| void clearPaths() { |
| mPaths.clear(); |
| } |
| |
| void setMax(int maxX, int maxY) { |
| mMaxX = maxX; |
| mMaxY = maxY; |
| } |
| |
| void setDividerLoc(int height) { |
| mMiddleDividerLoc = 1 - height / mMaxY; |
| } |
| |
| void setDividerColors(int middleColor, int topColor) { |
| mMiddleDividerTint = middleColor; |
| mTopDividerTint = topColor; |
| } |
| |
| public void addPath(SparseIntArray points) { |
| for (int i = 0; i < points.size(); i++) { |
| mPaths.put(points.keyAt(i), points.valueAt(i)); |
| } |
| mPaths.put(points.keyAt(points.size() - 1) + 1, PATH_DELIM); |
| calculateLocalPaths(); |
| postInvalidate(); |
| } |
| |
| void setAccentColor(int color) { |
| mAccentColor = color; |
| mLinePaint.setColor(mAccentColor); |
| updateGradient(); |
| postInvalidate(); |
| } |
| |
| void setShowProjection(boolean showProjection, boolean projectUp) { |
| mShowProjection = showProjection; |
| mProjectUp = projectUp; |
| postInvalidate(); |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| super.onSizeChanged(w, h, oldw, oldh); |
| updateGradient(); |
| calculateLocalPaths(); |
| } |
| |
| private void calculateLocalPaths() { |
| if (getWidth() == 0) return; |
| mLocalPaths.clear(); |
| int pendingXLoc = 0; |
| int pendingYLoc = PATH_DELIM; |
| for (int i = 0; i < mPaths.size(); i++) { |
| int x = mPaths.keyAt(i); |
| int y = mPaths.valueAt(i); |
| if (y == PATH_DELIM) { |
| if (i == mPaths.size() - 1 && pendingYLoc != PATH_DELIM) { |
| // Connect to the end of the graph. |
| mLocalPaths.put(pendingXLoc, pendingYLoc); |
| } |
| // Clear out any pending points. |
| pendingYLoc = PATH_DELIM; |
| mLocalPaths.put(pendingXLoc + 1, PATH_DELIM); |
| } else { |
| final int lx = getX(x); |
| final int ly = getY(y); |
| pendingXLoc = lx; |
| if (mLocalPaths.size() > 0) { |
| int lastX = mLocalPaths.keyAt(mLocalPaths.size() - 1); |
| int lastY = mLocalPaths.valueAt(mLocalPaths.size() - 1); |
| if (lastY != PATH_DELIM && !hasDiff(lastX, lx) && !hasDiff(lastY, ly)) { |
| pendingYLoc = ly; |
| continue; |
| } |
| } |
| mLocalPaths.put(lx, ly); |
| } |
| } |
| } |
| |
| private boolean hasDiff(int x1, int x2) { |
| return Math.abs(x2 - x1) >= mCornerRadius; |
| } |
| |
| private int getX(float x) { |
| return (int) (x / mMaxX * getWidth()); |
| } |
| |
| private int getY(float y) { |
| return (int) (getHeight() * (1 - (y / mMaxY))); |
| } |
| |
| private void updateGradient() { |
| mFillPaint.setShader(new LinearGradient(0, 0, 0, getHeight(), |
| getColor(mAccentColor, .2f), 0, TileMode.CLAMP)); |
| } |
| |
| private int getColor(int color, float alphaScale) { |
| return (color & (((int) (0xff * alphaScale) << 24) | 0xffffff)); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| // Draw lines across the top, middle, and bottom. |
| if (mMiddleDividerLoc != 0) { |
| drawDivider(0, canvas, mTopDividerTint); |
| } |
| drawDivider((int) ((canvas.getHeight() - mDividerSize) * mMiddleDividerLoc), canvas, |
| mMiddleDividerTint); |
| drawDivider(canvas.getHeight() - mDividerSize, canvas, -1); |
| |
| if (mLocalPaths.size() == 0) { |
| return; |
| } |
| if (mShowProjection) { |
| drawProjection(canvas); |
| } |
| drawFilledPath(canvas); |
| drawLinePath(canvas); |
| } |
| |
| private void drawProjection(Canvas canvas) { |
| mPath.reset(); |
| int x = mLocalPaths.keyAt(mLocalPaths.size() - 2); |
| int y = mLocalPaths.valueAt(mLocalPaths.size() - 2); |
| mPath.moveTo(x, y); |
| mPath.lineTo(canvas.getWidth(), mProjectUp ? 0 : canvas.getHeight()); |
| canvas.drawPath(mPath, mDottedPaint); |
| } |
| |
| private void drawLinePath(Canvas canvas) { |
| mPath.reset(); |
| mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0)); |
| for (int i = 1; i < mLocalPaths.size(); i++) { |
| int x = mLocalPaths.keyAt(i); |
| int y = mLocalPaths.valueAt(i); |
| if (y == PATH_DELIM) { |
| if (++i < mLocalPaths.size()) { |
| mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i)); |
| } |
| } else { |
| mPath.lineTo(x, y); |
| } |
| } |
| canvas.drawPath(mPath, mLinePaint); |
| } |
| |
| private void drawFilledPath(Canvas canvas) { |
| mPath.reset(); |
| float lastStartX = mLocalPaths.keyAt(0); |
| mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0)); |
| for (int i = 1; i < mLocalPaths.size(); i++) { |
| int x = mLocalPaths.keyAt(i); |
| int y = mLocalPaths.valueAt(i); |
| if (y == PATH_DELIM) { |
| mPath.lineTo(mLocalPaths.keyAt(i - 1), getHeight()); |
| mPath.lineTo(lastStartX, getHeight()); |
| mPath.close(); |
| if (++i < mLocalPaths.size()) { |
| lastStartX = mLocalPaths.keyAt(i); |
| mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i)); |
| } |
| } else { |
| mPath.lineTo(x, y); |
| } |
| } |
| canvas.drawPath(mPath, mFillPaint); |
| } |
| |
| private void drawDivider(int y, Canvas canvas, int tintColor) { |
| Drawable d = mDivider; |
| if (tintColor != -1) { |
| mTintedDivider.setTint(tintColor); |
| d = mTintedDivider; |
| } |
| d.setBounds(0, y, canvas.getWidth(), y + mDividerSize); |
| d.draw(canvas); |
| } |
| } |