話不多說,先上效果圖:
要點
- 支持滾動查看
- 支持兩種顯示模式切換
思路
根據效果圖可整理思路
1.因爲要實現左側x軸固定,右側可滑動,所以可將整個View看成左右兩部分,分別爲左側自定義LeftView,及右側的水平滾動視圖ScrollView.然後再ScrollView中加入自定義RightView.這樣即可實現左側固定,右側水平滑動的效果.
2.所以我們的View應該繼承LinearLayout,用於放置左側和右側兩個視圖
3.由效果圖可知,切換顯示模式只需改變bar的寬度,及繪製的起始座標.
代碼實現
1.繼承自LinearLayout並設置爲水平方向排列.
public class PlanCompleteBarView extends LinearLayout {
public PlanCompleteBarView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
setOrientation(HORIZONTAL);
}
2.添加基本結構
private void show() {
removeAllViews();
init();
LayoutParams layoutParams = new LayoutParams(dp2px(leftWidth), ViewGroup.LayoutParams.MATCH_PARENT);
addView(new LeftView(mContext), layoutParams);
HorizontalScrollView scrollView = new HorizontalScrollView(mContext);
scrollView.setHorizontalScrollBarEnabled(false);
scrollView.addView(new RightView(mContext), new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
addView(scrollView, new LayoutParams(getMeasuredWidth() - dp2px(leftWidth), ViewGroup.LayoutParams.MATCH_PARENT));
}
3.初始化必要參數
private void init() {
yPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
yPaint.setTextSize(sp2px(yTextSize));
yPaint.setColor(yTextColor);
xPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
xPaint.setTextSize(sp2px(xTextSize));
xPaint.setColor(xTextColor);
topPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
topPaint.setTextSize(sp2px(topTextSize));
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setStrokeWidth(sp2px(lineWidth));
linePaint.setColor(lineColor);
planPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
planPaint.setStyle(Paint.Style.FILL);
planPaint.setStrokeWidth(sp2px(barWidth));
planPaint.setColor(planColor);
completePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
completePaint.setStyle(Paint.Style.FILL);
completePaint.setStrokeWidth(sp2px(barWidth));
completePaint.setColor(completeColor);
Paint.FontMetricsInt xFontMetricsInt = xPaint.getFontMetricsInt();
marginBottom = xFontMetricsInt.bottom - xFontMetricsInt.top;
Paint.FontMetricsInt topFontMetricsInt = topPaint.getFontMetricsInt();
marginTop = topFontMetricsInt.bottom - topFontMetricsInt.top + topMarginBottom;
//因爲頂部有兩行文字
marginTop *= 2;
drawHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop() - marginBottom - marginTop;
percentHeight = drawHeight / maxValue;
yLabels = new ArrayList<>();
int i = maxValue / step;
for (int j = 0; j <= step; j++) {
yLabels.add(String.valueOf(j * i));
}
}
4.自定義左側LeftView
@Override
protected void onDraw(Canvas canvas) {
//將座標系移動至右下角
canvas.translate(mWidth, mHeight - marginBottom);
//計算每一段的長度
float i = maxValue / step;
for (int j = 0; j <= step; j++) {
float i1 = j * i * percentHeight;
// canvas.drawLine(0,-i1,-mWidth,-i1,linePaint);
String s = yLabels.get(j);
float v1 = yPaint.measureText(s);
//繪製y軸文字
canvas.drawText(s, -v1 - dp2px(yPaddingRight), -i1, yPaint);
}
}
5.自定義右側RightView
測量自身所需寬度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//計算所需寬度
int i = barWidth * mDataWrappers.size() + spaceWidth * mDataWrappers.size();
setMeasuredDimension(dp2px(i), MeasureSpec.getSize(heightMeasureSpec));
}
開始繪製
@Override
protected void onDraw(Canvas canvas) {
//將座標移動至左下角
canvas.translate(0, mHeight);
float i = maxValue / step;
//繪製網格線
for (int j = 0; j <= step; j++) {
float i1 = j * i * percentHeight;
canvas.drawLine(0, -i1 - marginBottom, mWidth, -i1 - marginBottom, linePaint);
}
for (int j = 0; j < mDataWrappers.size(); j++) {
DataWrapper dataWrapper = mDataWrappers.get(j);
String label = dataWrapper.label;
if (j == 0) {
canvas.translate(0, -marginBottom);
}
//計算繪製的x中心
float x = j * (dp2px(spaceWidth)
+ dp2px(barWidth))
+ dp2px(barWidth / 2)
+ dp2px(spaceWidth / 2);
//繪製x軸文字
float textWidth = xPaint.measureText(label);
Paint.FontMetrics fontMetrics = xPaint.getFontMetrics();
canvas.drawText(label, x - textWidth / 2, marginBottom - fontMetrics.bottom, xPaint);
//繪製bar
if (BAR_STYLE_OVERLAY == barstyle) {
//堆疊樣式,先繪製大的數值再繪製小的數值,形成覆蓋的效果
if (dataWrapper.complete > dataWrapper.plan) {
canvas.drawLine(x, 0, x, -dataWrapper.complete * percentHeight, completePaint);
canvas.drawLine(x, 0, x, -dataWrapper.plan * percentHeight, planPaint);
} else {
canvas.drawLine(x, 0, x, -dataWrapper.plan * percentHeight, planPaint);
canvas.drawLine(x, 0, x, -dataWrapper.complete * percentHeight, completePaint);
}
} else if (BAR_STYLE_TIE == barstyle) {
//排列樣式
planPaint.setStrokeWidth(dp2px(barWidth / 2));
completePaint.setStrokeWidth(dp2px(barWidth / 2));
canvas.drawLine(x - dp2px(barWidth / 4), 0, x - dp2px(barWidth / 4),
-dataWrapper.plan * percentHeight, planPaint);
canvas.drawLine(x + dp2px(barWidth / 4), 0,
x + dp2px(barWidth / 4), -dataWrapper.complete * percentHeight, completePaint);
}
//繪製頂部文字 200%
// 200/100
//應爲需要中心對齊,所以要分別繪製左邊的數值和右邊的數值
String str = "/";
float separatorWidth = topPaint.measureText(str);
topPaint.setColor(percentColor);
canvas.drawText(str, x - separatorWidth / 2,
-(Math.max(dataWrapper.complete, dataWrapper.plan) * percentHeight)
- dp2px(topMarginBottom), topPaint);
//繪製完成數值200
topPaint.setColor(completeColor);
textWidth = topPaint.measureText(String.valueOf(dataWrapper.complete));
canvas.drawText(String.valueOf(dataWrapper.complete), x - separatorWidth / 2 - textWidth,
-(Math.max(dataWrapper.complete, dataWrapper.plan) * percentHeight)
- dp2px(topMarginBottom), topPaint);
//繪製計劃數值100
topPaint.setColor(planColor);
canvas.drawText(String.valueOf(dataWrapper.plan), x + separatorWidth / 2,
-(Math.max(dataWrapper.complete, dataWrapper.plan) * percentHeight)
- dp2px(topMarginBottom), topPaint);
//繪製百分比 200%
topPaint.setColor(percentColor);
float percent = (float) dataWrapper.complete / dataWrapper.plan * 100;
str = (int) percent + "%";
textWidth = topPaint.measureText(str);
Paint.FontMetricsInt fontMetricsInt = topPaint.getFontMetricsInt();
int textHeight = fontMetricsInt.bottom - fontMetricsInt.top;
canvas.drawText(str, x - textWidth / 2, -(Math.max(dataWrapper.complete, dataWrapper.plan) * percentHeight)
- dp2px(topMarginBottom) - textHeight, topPaint);
}
}
完成.查看完整代碼請跳轉https://github.com/TanZhiL/PlanCompleteBarView