New way to draw battery chart axis labels
Pairly draws axis labels from left and right side to middle. If the pair of labels have any overlap, skips that pair of labels. https://drive.google.com/drive/folders/1tR4xfJsJGakuH5JRdn74kPD5GBH6u6CL?resourcekey=0-Ikp5CV0DpxllWv7n5-UHnw&usp=sharing Test: manual Bug: 236101166 Change-Id: Ib13d4c73b31ad86ac9e318d4315b5a1a0bb25814
This commit is contained in:
@@ -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();
|
||||||
@@ -167,6 +163,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(),
|
||||||
@@ -177,13 +174,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 {
|
||||||
@@ -386,10 +383,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);
|
||||||
}
|
}
|
||||||
@@ -417,68 +414,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.
|
||||||
@@ -556,8 +598,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(
|
||||||
@@ -613,33 +658,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