本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布
糖護(hù)士IOS版效果
自定義效果
適配效果
實(shí)現(xiàn)思路
將view拆分為4個(gè)部分繪制
- 繪制橫向線段+文字,即2.5-33.0以及對(duì)應(yīng)的線段橫向無(wú)變化蹲嚣。
- 繪制縱向線段+文字递瑰,即日期以及對(duì)應(yīng)的縱向線段祟牲,在滑動(dòng)時(shí)候要一直變換
- 繪制中間的陰影塊以及文字
- 繪制曲線
實(shí)現(xiàn)步驟
1 重寫onMeasure()
,當(dāng)高度的測(cè)量模式為EXACTLY時(shí)抖部,方格的高度為说贝,view高-1個(gè)半字體的高度/(縱列字?jǐn)?shù)-1)即segmentLone = (height - textRect.height() / 2 * 3) / (vPoints - 1);
。高度測(cè)量模式為AT_MOST時(shí)慎颗,通過(guò)固定的方格高度計(jì)算view高度 即 height = segmentLone * (vPoints + 1);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
width = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = MeasureSpec.getSize(heightMeasureSpec);
textPaint.getTextBounds(textV[0], 0, textV[0].length(), textRect);
segmentLone = (height - textRect.height() / 2 * 3) / (vPoints - 1);
setMeasuredDimension(width, height);
break;
case MeasureSpec.AT_MOST:
height = segmentLone * (vPoints + 1);
textPaint.getTextBounds(textV[0], 0, textV[0].length(), textRect);
setMeasuredDimension(width, segmentLone * (vPoints - 1) + textRect.height() / 2 * 3);
break;
}
}
2 重寫onDraw()
將view拆分為4個(gè)部分繪制,畫橫線乡恕,縱線,陰影以及曲線
@Override
protected void onDraw(Canvas canvas) {
drawHLinesAndText(canvas);
drawVLinesAndText(canvas);
drawTransRectAntText(canvas);
drawLinesAndPoint(canvas);
}
3 drawHLinesAndText()
先裁剪畫布為屏幕寬度及高度俯萎,第一條線的高度為text的高度的一半傲宜,之后的線段遞增固定高度
private void drawHLinesAndText(Canvas canvas) {
canvas.save();
int count = 0;
canvas.clipRect(new RectF(0, 0, width, height));
for (int i = 0; i < vPoints; i++) {
if (i == 0) {
textPaint.getTextBounds(textV[0], 0, textV[0].length(), textRect);
canvas.translate(textRect.width() + 10, 0);
count = textRect.height() / 2;
} else if (i == 1) {
count = segmentLone;
}
textPaint.getTextBounds(textV[i], 0, textV[i].length(), textRect);
canvas.translate(0, count);
canvas.drawText(textV[i], 0 - textRect.width() - 8, textRect.height() - textRect.height() / 2, textPaint);
canvas.drawLine(0, 0, width, 0, HLinePaint);
}
canvas.restore();
}
4 drawVLinesAndText()
畫縱向線段,先需要工具類獲取今天以及今天前N天的日期放入list中
夫啊,根據(jù)module設(shè)置不同取不同的日期函卒,在繪制后,將畫布平移到最新的日期撇眯。
public enum MODULE {
ONEDAY,
FIVEDAY,
HOUR,
}
private void drawVLinesAndText(Canvas canvas) {
canvas.save();
hPoints = textH.length;
int count = 0;
for (int i = 0; i < hPoints; i++) {
if (i == 0) {
textPaint.getTextBounds(textV[0], 0, textV[0].length(), textRect);
canvas.translate(0, textRect.height() / 2);
count = textRect.width() + 10;
canvas.clipRect(new RectF(textRect.width() + 10, 0, width, height));
} else if (i == 1) {
if (isFirstShow) {
//期初的偏移量=線段數(shù)(底部文字?jǐn)?shù)-1)* segmentLone;
offsetX = getDayOffset(currentDay);
lastMove = offsetX;
isFirstShow = false;
}
canvas.translate(offsetX, 0);
count = segmentLone;
}
textPaint.getTextBounds(textH[i], 0, textH[i].length(), textRect);
canvas.translate(count, 0);
canvas.drawText(textH[i], -textRect.width() / 2, segmentLone * (vPoints - 1) + textRect.height() + 8, textPaint);
canvas.drawLine(0, 0, 0, segmentLone * (vPoints - 1), HLinePaint);
// drawVDshLine(canvas);
}
canvas.restore();
}
5 drawTransRectAntText()
繪制陰影,這部分比較簡(jiǎn)單
private void drawTransRectAntText(Canvas canvas) {
textPaint.getTextBounds(textV[0], 0, textV[0].length(), textRect);
transRect.set(textRect.width() + 10, textRect.height() / 2 + segmentLone * 2 + segmentLone / 2, width
, textRect.height() / 2 + segmentLone * 7 + segmentLone / 10 * 6);
canvas.drawRect(transRect, rectTransPaint);
canvas.drawText("4.4", width - textRect.width() - 5, segmentLone * 7 + segmentLone / 10 * 6 + textRect.height() + 10, textPaint);
canvas.drawText("10.0", width - textRect.width() - 5, segmentLone * 2 + segmentLone / 2, textPaint);
}
6 drawLinesAndPoint()
繪制曲線报嵌,根據(jù)預(yù)先封裝好的用來(lái)獲取坐標(biāo)原點(diǎn)的方法,并且根據(jù)module設(shè)置的不同熊榛,來(lái)獲取某一點(diǎn)在表格中實(shí)際的位置
private void drawLinesAndPoint(Canvas canvas) {
canvas.clipRect(new RectF(textRect.width() + 10, 0, width, height));
float[] points = new float[]{getCurrentDayX(1), getY(0), getCurrentDayX(1.5f), getY(4)};
canvas.drawCircle(points[0], points[1], 10, circlePaint);
canvas.drawCircle(points[2], points[3], 10, circlePaint);
canvas.drawLine(getCurrentDayX(1), getY(0), getCurrentDayX(1.5f), getY(4), linePaint);
}
7 處理onTouchEvent()
锚国,滑動(dòng)時(shí),記錄橫向滑動(dòng)產(chǎn)生的offset不斷重新繪制玄坦,使表格可以移動(dòng)血筑,并且通過(guò)速度監(jiān)聽器以及屬性動(dòng)畫實(shí)現(xiàn)慣性滑動(dòng)
public boolean onTouchEvent(MotionEvent event) {
velocityTracker.computeCurrentVelocity(1000);
velocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (valueAnimator != null && valueAnimator.isRunning()) {
valueAnimator.cancel();
}
downX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
moveX = (int) event.getX();
offsetX = moveX - downX + lastMove;
if (offsetX < getDayOffset(1)) {
offsetX = getDayOffset(1);
} else if (offsetX > 0) {
offsetX = 0;
}
invalidate();
break;
case MotionEvent.ACTION_UP:
lastMove = offsetX;
xVelocity = (int) velocityTracker.getXVelocity();
autoVelocityScroll(xVelocity);
break;
}
return true;
}
8 供外部刷新view的方法
- 更新模式 5天 1天 1天+小時(shí)段
public void setModule(MODULE module) {
this.module = module;
init();
invalidate();
}
- 定位到某一天,適配以上3種模式
public void setCurrentDay(float currentDay) {
switch (module) {
case FIVEDAY:
this.currentDay = currentDay / 5.0f + 1;
break;
case ONEDAY:
this.currentDay = currentDay;
break;
case HOUR:
this.currentDay = currentDay * 6;
break;
}
init();
invalidate();
}
更多的優(yōu)化
動(dòng)態(tài)設(shè)置陰影的位置营搅,以及要顯示的文字
public void setTransTopAndBottom(int top, int bottom,String topStr,String bottomStr) {
this.transRectTop = top;
this.transRectBottom = bottom;
this.transRectTopStr = topStr;
this.transRectBottomStr = bottomStr;
init();
invalidate();
}
動(dòng)態(tài)更新點(diǎn)
public void setPointList(List<PointF> pointList) {
this.pointList = pointList;
init();
invalidate();
}