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:
Zaiyue Xue
2022-08-05 13:18:28 +08:00
parent 248895db72
commit 72b64f2515

View File

@@ -46,12 +46,10 @@ import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/** A widget component to draw chart graph. */
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 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. */
public interface OnSelectListener {
@@ -79,7 +75,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
private float mTrapezoidHOffset;
private boolean mIsSlotsClickabled;
private String[] mPercentages = getPercentages();
private Integer[] mLabelsIndexes;
@VisibleForTesting
int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
@@ -94,7 +89,8 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
private final Rect[] mPercentageBounds =
new Rect[]{new Rect(), new Rect(), new Rect()};
// For drawing the axis label information.
private final Rect[] mAxisLabelsBounds = initializeAxisLabelsBounds();
private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
@VisibleForTesting
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.",
viewModel.size(), viewModel.selectedIndex()));
mViewModel = viewModel;
mLabelsIndexes = MODEL_SIZE_TO_LABEL_INDEXES_MAP.get(mViewModel.size());
initializeAxisLabelsBounds();
initializeTrapezoidSlots(viewModel.size() - 1);
setClickable(hasAnyValidTrapezoid(viewModel));
requestLayout();
@@ -167,6 +163,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Measures text bounds and updates indent configuration.
if (mTextPaint != null) {
mTextPaint.setTextAlign(Paint.Align.LEFT);
for (int index = 0; index < mPercentages.length; index++) {
mTextPaint.getTextBounds(
mPercentages[index], 0, mPercentages[index].length(),
@@ -177,13 +174,13 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
mIndent.right = mPercentageBounds[0].width() + mTextPadding;
if (mViewModel != null) {
int maxHeight = 0;
for (int index = 0; index < mLabelsIndexes.length; index++) {
final String text = getAxisLabelText(index);
mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds[index]);
maxHeight = Math.max(maxHeight, mAxisLabelsBounds[index].height());
int maxTop = 0;
for (int index = 0; index < mViewModel.size(); index++) {
final String text = mViewModel.texts().get(index);
mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds.get(index));
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]);
} else {
@@ -386,10 +383,10 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
private void drawPercentage(Canvas canvas, int index, float offsetY) {
if (mTextPaint != null) {
mTextPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(
mPercentages[index],
getWidth() - mPercentageBounds[index].width()
- mPercentageBounds[index].left,
getWidth(),
offsetY + mPercentageBounds[index].height() * .5f,
mTextPaint);
}
@@ -417,67 +414,112 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
}
// Draws the axis label slot information.
if (mViewModel != null) {
final float[] xOffsets = new float[mLabelsIndexes.length];
final float baselineX = mDividerWidth * .5f;
final float offsetX = mDividerWidth + unitWidth;
for (int index = 0; index < mLabelsIndexes.length; index++) {
xOffsets[index] = baselineX + mLabelsIndexes[index] * offsetX;
}
final float baselineY = getHeight() - mTextPadding * 1.5f;
Rect[] axisLabelDisplayAreas;
switch (mViewModel.axisLabelPosition()) {
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;
case BETWEEN_TRAPEZOIDS:
default:
drawAxisLabelsBetweenTrapezoids(canvas, xOffsets);
axisLabelDisplayAreas = getAxisLabelDisplayAreas(
/* size= */ mViewModel.size(),
/* baselineX= */ mDividerWidth * .5f,
/* offsetX= */ mDividerWidth + unitWidth,
baselineY,
/* shiftFirstAndLast= */ true);
break;
}
drawAxisLabels(canvas, axisLabelDisplayAreas, baselineY);
}
}
/** Gets all the axis label texts displaying area positions if they are shown. */
private Rect[] getAxisLabelDisplayAreas(final int size, final float baselineX,
final float offsetX, final float baselineY, final boolean shiftFirstAndLast) {
final Rect[] result = new Rect[size];
for (int index = 0; index < result.length; index++) {
final float width = mAxisLabelsBounds.get(index).width();
float middle = baselineX + index * offsetX;
if (shiftFirstAndLast) {
if (index == 0) {
middle += width * .5f;
}
if (index == size - 1) {
middle -= width * .5f;
}
}
final float left = middle - width * .5f;
final float right = left + width;
final float top = baselineY + mAxisLabelsBounds.get(index).top;
final float bottom = top + mAxisLabelsBounds.get(index).height();
result[index] = new Rect(round(left), round(top), round(right), round(bottom));
}
return result;
}
/**
* 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 drawAxisLabelsBetweenTrapezoids(Canvas canvas, float[] xOffsets) {
// Draws the 1st axis label info.
private void drawAxisLabelText(
Canvas canvas, final int index, final Rect displayArea, final float baselineY) {
mTextPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(
getAxisLabelText(0), xOffsets[0] - mAxisLabelsBounds[0].left, getAxisLabelY(0),
mViewModel.texts().get(index),
displayArea.centerX(),
baselineY,
mTextPaint);
final int latestIndex = mLabelsIndexes.length - 1;
// Draws the last axis label info.
canvas.drawText(
getAxisLabelText(latestIndex),
xOffsets[latestIndex]
- mAxisLabelsBounds[latestIndex].width()
- mAxisLabelsBounds[latestIndex].left,
getAxisLabelY(latestIndex),
mTextPaint);
// Draws the rest of axis label info since it is located in the center.
for (int index = 1; index <= mLabelsIndexes.length - 2; index++) {
canvas.drawText(
getAxisLabelText(index),
xOffsets[index]
- (mAxisLabelsBounds[index].width() - mAxisLabelsBounds[index].left)
* .5f,
getAxisLabelY(index),
mTextPaint);
}
}
private void drawAxisLabelsCenterOfTrapezoids(
Canvas canvas, float[] xOffsets, float unitWidth) {
for (int index = 0; index < mLabelsIndexes.length - 1; index++) {
canvas.drawText(
getAxisLabelText(index),
xOffsets[index] + (unitWidth - (mAxisLabelsBounds[index].width()
- mAxisLabelsBounds[index].left)) * .5f,
getAxisLabelY(index),
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) {
@@ -556,8 +598,11 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
return BatteryChartViewModel.SELECTED_INDEX_INVALID;
}
private String getAxisLabelText(int labelIndex) {
return mViewModel.texts().get(mLabelsIndexes[labelIndex]);
private void initializeAxisLabelsBounds() {
mAxisLabelsBounds.clear();
for (int i = 0; i < mViewModel.size(); i++) {
mAxisLabelsBounds.add(new Rect());
}
}
private static boolean isTrapezoidValid(
@@ -613,33 +658,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
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.
@VisibleForTesting
static final class TrapezoidSlot {