Merge "New way to draw battery chart axis labels"
This commit is contained in:
committed by
Android (Google) Code Review
commit
6b806e31f9
@@ -46,12 +46,10 @@ import com.android.settings.R;
|
|||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
import com.android.settingslib.Utils;
|
import com.android.settingslib.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/** A widget component to draw chart graph. */
|
/** A widget component to draw chart graph. */
|
||||||
public class BatteryChartViewV2 extends AppCompatImageView implements View.OnClickListener,
|
public class BatteryChartViewV2 extends AppCompatImageView implements View.OnClickListener,
|
||||||
@@ -62,8 +60,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
|
|
||||||
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
|
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
|
||||||
private static final long UPDATE_STATE_DELAYED_TIME = 500L;
|
private static final long UPDATE_STATE_DELAYED_TIME = 500L;
|
||||||
private static final Map<Integer, Integer[]> MODEL_SIZE_TO_LABEL_INDEXES_MAP =
|
|
||||||
buildModelSizeToLabelIndexesMap();
|
|
||||||
|
|
||||||
/** A callback listener for selected group index is updated. */
|
/** A callback listener for selected group index is updated. */
|
||||||
public interface OnSelectListener {
|
public interface OnSelectListener {
|
||||||
@@ -79,7 +75,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
private float mTrapezoidHOffset;
|
private float mTrapezoidHOffset;
|
||||||
private boolean mIsSlotsClickabled;
|
private boolean mIsSlotsClickabled;
|
||||||
private String[] mPercentages = getPercentages();
|
private String[] mPercentages = getPercentages();
|
||||||
private Integer[] mLabelsIndexes;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
||||||
@@ -94,7 +89,8 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
private final Rect[] mPercentageBounds =
|
private final Rect[] mPercentageBounds =
|
||||||
new Rect[]{new Rect(), new Rect(), new Rect()};
|
new Rect[]{new Rect(), new Rect(), new Rect()};
|
||||||
// For drawing the axis label information.
|
// For drawing the axis label information.
|
||||||
private final Rect[] mAxisLabelsBounds = initializeAxisLabelsBounds();
|
private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
Handler mHandler = new Handler();
|
Handler mHandler = new Handler();
|
||||||
@@ -138,7 +134,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.",
|
Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.",
|
||||||
viewModel.size(), viewModel.selectedIndex()));
|
viewModel.size(), viewModel.selectedIndex()));
|
||||||
mViewModel = viewModel;
|
mViewModel = viewModel;
|
||||||
mLabelsIndexes = MODEL_SIZE_TO_LABEL_INDEXES_MAP.get(mViewModel.size());
|
initializeAxisLabelsBounds();
|
||||||
initializeTrapezoidSlots(viewModel.size() - 1);
|
initializeTrapezoidSlots(viewModel.size() - 1);
|
||||||
setClickable(hasAnyValidTrapezoid(viewModel));
|
setClickable(hasAnyValidTrapezoid(viewModel));
|
||||||
requestLayout();
|
requestLayout();
|
||||||
@@ -166,6 +162,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
// Measures text bounds and updates indent configuration.
|
// Measures text bounds and updates indent configuration.
|
||||||
if (mTextPaint != null) {
|
if (mTextPaint != null) {
|
||||||
|
mTextPaint.setTextAlign(Paint.Align.LEFT);
|
||||||
for (int index = 0; index < mPercentages.length; index++) {
|
for (int index = 0; index < mPercentages.length; index++) {
|
||||||
mTextPaint.getTextBounds(
|
mTextPaint.getTextBounds(
|
||||||
mPercentages[index], 0, mPercentages[index].length(),
|
mPercentages[index], 0, mPercentages[index].length(),
|
||||||
@@ -176,13 +173,13 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
mIndent.right = mPercentageBounds[0].width() + mTextPadding;
|
mIndent.right = mPercentageBounds[0].width() + mTextPadding;
|
||||||
|
|
||||||
if (mViewModel != null) {
|
if (mViewModel != null) {
|
||||||
int maxHeight = 0;
|
int maxTop = 0;
|
||||||
for (int index = 0; index < mLabelsIndexes.length; index++) {
|
for (int index = 0; index < mViewModel.size(); index++) {
|
||||||
final String text = getAxisLabelText(index);
|
final String text = mViewModel.texts().get(index);
|
||||||
mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds[index]);
|
mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds.get(index));
|
||||||
maxHeight = Math.max(maxHeight, mAxisLabelsBounds[index].height());
|
maxTop = Math.max(maxTop, -mAxisLabelsBounds.get(index).top);
|
||||||
}
|
}
|
||||||
mIndent.bottom = maxHeight + round(mTextPadding * 1.5f);
|
mIndent.bottom = maxTop + round(mTextPadding * 2f);
|
||||||
}
|
}
|
||||||
Log.d(TAG, "setIndent:" + mPercentageBounds[0]);
|
Log.d(TAG, "setIndent:" + mPercentageBounds[0]);
|
||||||
} else {
|
} else {
|
||||||
@@ -385,10 +382,10 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
|
|
||||||
private void drawPercentage(Canvas canvas, int index, float offsetY) {
|
private void drawPercentage(Canvas canvas, int index, float offsetY) {
|
||||||
if (mTextPaint != null) {
|
if (mTextPaint != null) {
|
||||||
|
mTextPaint.setTextAlign(Paint.Align.RIGHT);
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
mPercentages[index],
|
mPercentages[index],
|
||||||
getWidth() - mPercentageBounds[index].width()
|
getWidth(),
|
||||||
- mPercentageBounds[index].left,
|
|
||||||
offsetY + mPercentageBounds[index].height() * .5f,
|
offsetY + mPercentageBounds[index].height() * .5f,
|
||||||
mTextPaint);
|
mTextPaint);
|
||||||
}
|
}
|
||||||
@@ -416,68 +413,113 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
}
|
}
|
||||||
// Draws the axis label slot information.
|
// Draws the axis label slot information.
|
||||||
if (mViewModel != null) {
|
if (mViewModel != null) {
|
||||||
final float[] xOffsets = new float[mLabelsIndexes.length];
|
final float baselineY = getHeight() - mTextPadding * 1.5f;
|
||||||
final float baselineX = mDividerWidth * .5f;
|
Rect[] axisLabelDisplayAreas;
|
||||||
final float offsetX = mDividerWidth + unitWidth;
|
|
||||||
for (int index = 0; index < mLabelsIndexes.length; index++) {
|
|
||||||
xOffsets[index] = baselineX + mLabelsIndexes[index] * offsetX;
|
|
||||||
}
|
|
||||||
switch (mViewModel.axisLabelPosition()) {
|
switch (mViewModel.axisLabelPosition()) {
|
||||||
case CENTER_OF_TRAPEZOIDS:
|
case CENTER_OF_TRAPEZOIDS:
|
||||||
drawAxisLabelsCenterOfTrapezoids(canvas, xOffsets, unitWidth);
|
axisLabelDisplayAreas = getAxisLabelDisplayAreas(
|
||||||
|
/* size= */ mViewModel.size() - 1,
|
||||||
|
/* baselineX= */ mDividerWidth + unitWidth * .5f,
|
||||||
|
/* offsetX= */ mDividerWidth + unitWidth,
|
||||||
|
baselineY,
|
||||||
|
/* shiftFirstAndLast= */ false);
|
||||||
break;
|
break;
|
||||||
case BETWEEN_TRAPEZOIDS:
|
case BETWEEN_TRAPEZOIDS:
|
||||||
default:
|
default:
|
||||||
drawAxisLabelsBetweenTrapezoids(canvas, xOffsets);
|
axisLabelDisplayAreas = getAxisLabelDisplayAreas(
|
||||||
|
/* size= */ mViewModel.size(),
|
||||||
|
/* baselineX= */ mDividerWidth * .5f,
|
||||||
|
/* offsetX= */ mDividerWidth + unitWidth,
|
||||||
|
baselineY,
|
||||||
|
/* shiftFirstAndLast= */ true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
drawAxisLabels(canvas, axisLabelDisplayAreas, baselineY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawAxisLabelsBetweenTrapezoids(Canvas canvas, float[] xOffsets) {
|
/** Gets all the axis label texts displaying area positions if they are shown. */
|
||||||
// Draws the 1st axis label info.
|
private Rect[] getAxisLabelDisplayAreas(final int size, final float baselineX,
|
||||||
canvas.drawText(
|
final float offsetX, final float baselineY, final boolean shiftFirstAndLast) {
|
||||||
getAxisLabelText(0), xOffsets[0] - mAxisLabelsBounds[0].left, getAxisLabelY(0),
|
final Rect[] result = new Rect[size];
|
||||||
mTextPaint);
|
for (int index = 0; index < result.length; index++) {
|
||||||
final int latestIndex = mLabelsIndexes.length - 1;
|
final float width = mAxisLabelsBounds.get(index).width();
|
||||||
// Draws the last axis label info.
|
float middle = baselineX + index * offsetX;
|
||||||
canvas.drawText(
|
if (shiftFirstAndLast) {
|
||||||
getAxisLabelText(latestIndex),
|
if (index == 0) {
|
||||||
xOffsets[latestIndex]
|
middle += width * .5f;
|
||||||
- mAxisLabelsBounds[latestIndex].width()
|
}
|
||||||
- mAxisLabelsBounds[latestIndex].left,
|
if (index == size - 1) {
|
||||||
getAxisLabelY(latestIndex),
|
middle -= width * .5f;
|
||||||
mTextPaint);
|
}
|
||||||
// Draws the rest of axis label info since it is located in the center.
|
}
|
||||||
for (int index = 1; index <= mLabelsIndexes.length - 2; index++) {
|
final float left = middle - width * .5f;
|
||||||
canvas.drawText(
|
final float right = left + width;
|
||||||
getAxisLabelText(index),
|
final float top = baselineY + mAxisLabelsBounds.get(index).top;
|
||||||
xOffsets[index]
|
final float bottom = top + mAxisLabelsBounds.get(index).height();
|
||||||
- (mAxisLabelsBounds[index].width() - mAxisLabelsBounds[index].left)
|
result[index] = new Rect(round(left), round(top), round(right), round(bottom));
|
||||||
* .5f,
|
}
|
||||||
getAxisLabelY(index),
|
return result;
|
||||||
mTextPaint);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pairly draws axis labels from left and right side to middle. If the pair of labels have
|
||||||
|
* any overlap, skips that pair of labels.
|
||||||
|
*/
|
||||||
|
private void drawAxisLabels(Canvas canvas, final Rect[] displayAreas, final float baselineY) {
|
||||||
|
int forwardCheckLine = Integer.MIN_VALUE;
|
||||||
|
int backwardCheckLine = Integer.MAX_VALUE;
|
||||||
|
Rect middleDisplayArea = null;
|
||||||
|
for (int forwardIndex = 0, backwordIndex = displayAreas.length - 1;
|
||||||
|
forwardIndex <= backwordIndex; forwardIndex++, backwordIndex--) {
|
||||||
|
final Rect forwardDisplayArea = displayAreas[forwardIndex];
|
||||||
|
final Rect backwardDisplayArea = displayAreas[backwordIndex];
|
||||||
|
if (forwardDisplayArea.left < forwardCheckLine
|
||||||
|
|| backwardDisplayArea.right > backwardCheckLine) {
|
||||||
|
// Overlapped at left or right, skip the pair of labels
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (middleDisplayArea != null && (
|
||||||
|
forwardDisplayArea.right + mTextPadding > middleDisplayArea.left
|
||||||
|
|| backwardDisplayArea.left - mTextPadding < middleDisplayArea.right)) {
|
||||||
|
// Overlapped with the middle label.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (forwardIndex != backwordIndex
|
||||||
|
&& forwardDisplayArea.right + mTextPadding > backwardDisplayArea.left) {
|
||||||
|
// Overlapped in the middle, skip the pair of labels
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawAxisLabelText(canvas, forwardIndex, forwardDisplayArea, baselineY);
|
||||||
|
drawAxisLabelText(canvas, backwordIndex, backwardDisplayArea, baselineY);
|
||||||
|
|
||||||
|
forwardCheckLine = forwardDisplayArea.right + mTextPadding;
|
||||||
|
backwardCheckLine = backwardDisplayArea.left - mTextPadding;
|
||||||
|
|
||||||
|
// If the number of labels is odd, draw the middle label first
|
||||||
|
if (forwardIndex == 0 && backwordIndex % 2 == 0) {
|
||||||
|
final int middleIndex = backwordIndex / 2;
|
||||||
|
middleDisplayArea = displayAreas[middleIndex];
|
||||||
|
if (middleDisplayArea.left < forwardCheckLine
|
||||||
|
|| middleDisplayArea.right > backwardCheckLine) {
|
||||||
|
// Overlapped at left or right, skip the pair of labels
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
drawAxisLabelText(canvas, middleIndex, middleDisplayArea, baselineY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawAxisLabelsCenterOfTrapezoids(
|
private void drawAxisLabelText(
|
||||||
Canvas canvas, float[] xOffsets, float unitWidth) {
|
Canvas canvas, final int index, final Rect displayArea, final float baselineY) {
|
||||||
for (int index = 0; index < mLabelsIndexes.length - 1; index++) {
|
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
getAxisLabelText(index),
|
mViewModel.texts().get(index),
|
||||||
xOffsets[index] + (unitWidth - (mAxisLabelsBounds[index].width()
|
displayArea.centerX(),
|
||||||
- mAxisLabelsBounds[index].left)) * .5f,
|
baselineY,
|
||||||
getAxisLabelY(index),
|
|
||||||
mTextPaint);
|
mTextPaint);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private int getAxisLabelY(int index) {
|
|
||||||
return getHeight()
|
|
||||||
- mAxisLabelsBounds[index].height()
|
|
||||||
+ (mAxisLabelsBounds[index].height() + mAxisLabelsBounds[index].top)
|
|
||||||
+ round(mTextPadding * 1.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawTrapezoids(Canvas canvas) {
|
private void drawTrapezoids(Canvas canvas) {
|
||||||
// Ignores invalid trapezoid data.
|
// Ignores invalid trapezoid data.
|
||||||
@@ -555,8 +597,11 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
return BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
return BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAxisLabelText(int labelIndex) {
|
private void initializeAxisLabelsBounds() {
|
||||||
return mViewModel.texts().get(mLabelsIndexes[labelIndex]);
|
mAxisLabelsBounds.clear();
|
||||||
|
for (int i = 0; i < mViewModel.size(); i++) {
|
||||||
|
mAxisLabelsBounds.add(new Rect());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isTrapezoidValid(
|
private static boolean isTrapezoidValid(
|
||||||
@@ -612,33 +657,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<Integer, Integer[]> buildModelSizeToLabelIndexesMap() {
|
|
||||||
final Map<Integer, Integer[]> result = new HashMap<>();
|
|
||||||
result.put(2, new Integer[]{0, 1});
|
|
||||||
result.put(3, new Integer[]{0, 1, 2});
|
|
||||||
result.put(4, new Integer[]{0, 1, 2, 3});
|
|
||||||
result.put(5, new Integer[]{0, 1, 2, 3, 4});
|
|
||||||
result.put(6, new Integer[]{0, 1, 2, 3, 4, 5});
|
|
||||||
result.put(7, new Integer[]{0, 1, 2, 3, 4, 5, 6});
|
|
||||||
result.put(8, new Integer[]{0, 1, 2, 3, 4, 5, 6, 7});
|
|
||||||
result.put(9, new Integer[]{0, 2, 4, 6, 8});
|
|
||||||
result.put(10, new Integer[]{0, 3, 6, 9});
|
|
||||||
result.put(11, new Integer[]{0, 5, 10});
|
|
||||||
result.put(12, new Integer[]{0, 4, 7, 11});
|
|
||||||
result.put(13, new Integer[]{0, 4, 8, 12});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Rect[] initializeAxisLabelsBounds() {
|
|
||||||
final int maxLabelsLength = MODEL_SIZE_TO_LABEL_INDEXES_MAP.values().stream().max(
|
|
||||||
Comparator.comparingInt(indexes -> indexes.length)).get().length;
|
|
||||||
final Rect[] bounds = new Rect[maxLabelsLength];
|
|
||||||
for (int i = 0; i < maxLabelsLength; i++) {
|
|
||||||
bounds[i] = new Rect();
|
|
||||||
}
|
|
||||||
return bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A container class for each trapezoid left and right location.
|
// A container class for each trapezoid left and right location.
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final class TrapezoidSlot {
|
static final class TrapezoidSlot {
|
||||||
|
Reference in New Issue
Block a user