之前寫過一個簡單的圖表繪制demo:Android圖表繪制产舞,但是實(shí)際應(yīng)用中只是簡單的繪制數(shù)據(jù)并不能達(dá)到最好的效果易猫【咦常考慮到實(shí)際體驗(yàn)嘴办,在之前的demo基礎(chǔ)上增加動態(tài)繪制動畫以及圖表各項(xiàng)屬性設(shè)置。
下面看下的靜態(tài)效果圖:
整個布局分為三個部分贯被,即上方的四個按鈕區(qū)域彤灶,下方的線條說明區(qū)域幌陕,以及我們本次需要開發(fā)的圖表區(qū)域搏熄。上部分的是四個自定義按鈕心例,代碼比較簡單鞋囊,此處不多做說明。
我們總體需要繪制2條折線和若干數(shù)量的柱狀圖瓜喇,首先是繪制折線歉糜,參考一篇博文的方法:http://blog.csdn.net/tianjian4592/article/details/47067161现恼,這篇博文通過PathMeasure和屬性動畫結(jié)合的方式繪制一個動態(tài)路徑特效叉袍,由于我們這里并非是要動態(tài)繪制圖表而非根據(jù)路徑動態(tài)繪制一個圓刽酱,所以針對原博文做了一些修改棵里。
首先是獲取整個折線的path,然后將這個path設(shè)置給一個PathMeasure對象典蝌。
// 獲取room temp繪線Path數(shù)據(jù)
private void initRoomTempPath(float[] data) {
mRoomTempPath.reset();
Path path = new Path();
float pointX;
float pointY;
mRoomTempPath.moveTo(Xpoint + xFirstPointOffset,
getDataY(data[0], Ylabel));
path.moveTo(Xpoint + xFirstPointOffset,
getDataY(data[0], Ylabel));
for (int i = 0; i < Xlabel.length; i++) {
float startX = Xpoint + i * Xscale + xFirstPointOffset;
if (i != 0) {
pointX = Xpoint + (i - 1) * Xscale + xFirstPointOffset;
pointY = getDataY(data[i - 1], Ylabel);
path.lineTo(pointX, pointY);
}
if (i == Xlabel.length - 1) {
pointX = startX;
pointY = getDataY(data[i], Ylabel);
path.lineTo(pointX, pointY);
}
}
mRoomTempPathMeasure = new PathMeasure(path, false);
}
然后創(chuàng)建一個ValueAnimator骏掀,在這個ValueAnimator中我們根據(jù)ValueAnimator計(jì)算出的當(dāng)前point值來動態(tài)的更新一個新的path截驮。
private void startRoomTempAnimation() {
if (roomTempValueAnimator != null) {
roomTempValueAnimator.cancel();
}
roomTempValueAnimator = ValueAnimator.ofFloat(0,
mRoomTempPathMeasure.getLength());
roomTempValueAnimator.setDuration(duration);
// 減速插值器
roomTempValueAnimator.setInterpolator(new DecelerateInterpolator());
roomTempValueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
// 獲取當(dāng)前點(diǎn)坐標(biāo)封裝到mCurrentPosition
mRoomTempPathMeasure.getPosTan(value, mCurrentPosition, null);
mRoomTempPath.lineTo(mCurrentPosition[0], mCurrentPosition[1]);
postInvalidate();
}
});
roomTempValueAnimator.start();
}
最后再onDraw里面繪制path葵袭。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
drawGreyXLine(canvas, linePaint);
drawXLine(canvas, linePaint);
drawUnit(canvas);
canvas.drawPath(mRoomTempPath, roomTempPaint);
if (roomTempDataArray.length > 1) {
drawData(canvas, roomTempDataArray, roomTempLineColor);
}
}
然后我們看到了如下的效果:
看起來好像一切正常,似乎達(dá)到了我們需要的效果窒所,但是將duration的值設(shè)置小一些之后墩新,整個路徑繪制都出錯了。
具體原因是什么呢绵疲,其實(shí)也很簡單盔憨,當(dāng)duration值設(shè)置的比較大的時候,ValueAnimator中取到的點(diǎn)坐標(biāo)自然比較密集婿奔,這樣mRoomTempPath的路徑值也比較平均萍摊,繪制起來自然看起來比較完善如叼。然而當(dāng)duration值設(shè)置的比較小的時候笼恰,ValueAnimator取到點(diǎn)坐標(biāo)跨度比較大,如果是一條直線還沒什么問題逼龟,但是一旦路徑是折線腺律,自然就出現(xiàn)問題了辽俗,因?yàn)槲覀兊穆窂蕉际莾牲c(diǎn)兩點(diǎn)之間lineTo連接的崖飘。
由于上面的這種方法存在很大的問題,不得不想一個新的方法吊圾,一番查閱之后项乒,發(fā)現(xiàn)了一種新的思路梁沧,通過DashPathEffect來實(shí)現(xiàn),具體的思路也比較簡單栓辜,可參考原博主:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0907/3429.html垛孔,稍微做了一些修整之后周荐,我們的代碼修改如下。
// 獲取room temp繪線Path數(shù)據(jù)
private void initRoomTempPath(float[] data) {
mRoomTempPath.reset();
//Path path = new Path();
float pointX;
float pointY;
// 橫向
mRoomTempPath.moveTo(Xpoint + xFirstPointOffset,
getDataY(data[0], Ylabel));
mRoomTempPath.moveTo(Xpoint + xFirstPointOffset, getDataY(data[0], Ylabel));
for (int i = 0; i < Xlabel.length; i++) {
float startX = Xpoint + i * Xscale + xFirstPointOffset;
if (i != 0) {
pointX = Xpoint + (i - 1) * Xscale + xFirstPointOffset;
pointY = getDataY(data[i - 1], Ylabel);
mRoomTempPath.lineTo(pointX, pointY);
}
if (i == Xlabel.length - 1) {
pointX = startX;
pointY = getDataY(data[i], Ylabel);
mRoomTempPath.lineTo(pointX, pointY);
}
}
mRoomTempPathMeasure = new PathMeasure(mRoomTempPath, false);
}
private void startAnimation() {
if (mValueAnimator != null) {
mValueAnimator.cancel();
}
final float targetTempLength = mTargetTempPathMeasure.getLength();
final float roomTempLength = mRoomTempPathMeasure.getLength();
mValueAnimator = ValueAnimator.ofFloat(1, 0);
mValueAnimator.setDuration(duration);
// 減速插值器
mValueAnimator.setInterpolator(new DecelerateInterpolator());
mValueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = (Float) animation.getAnimatedValue();
//更新mtargetTempEffect
mtargetTempEffect = new DashPathEffect(new float[]{targetTempLength,
targetTempLength}, fraction * targetTempLength);
targetTempPaint.setPathEffect(mtargetTempEffect);
//更新mRoomTempEffect
mRoomTempEffect = new DashPathEffect(new float[]{roomTempLength, roomTempLength},
fraction * roomTempLength);
roomTempPaint.setPathEffect(mRoomTempEffect);
//更新rect繪制fraction進(jìn)度
mRectFration = 1 - fraction;//fraction是1->0 我們需要的柱形圖繪制比例是0->1
postInvalidate();
}
});
mValueAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
drawGreyXLine(canvas, linePaint);
drawUnit(canvas);
if (powerOnTimeDataArray.length > 1) {
drawRect(canvas, powerOnTimeDataArray, powerTimeLineColor);
}
canvas.drawPath(mRoomTempPath, roomTempPaint);
if (roomTempDataArray.length > 1) {
drawData(canvas, roomTempDataArray, roomTempLineColor);
}
canvas.drawPath(mTargetTempPath, targetTempPaint);
if (targetTempDataArray.length > 1) {
drawData(canvas, targetTempDataArray, targetTempLineColor);
}
drawXLine(canvas, linePaint);
}
最后很完美的實(shí)現(xiàn)了我們想要的折線動態(tài)繪制效果仆嗦。
接下來是柱形圖的動態(tài)繪制瘩扼,一樣根據(jù)valueAnimator中onAnimationUpdate的fraction來動態(tài)的更新每個rect的top值并繪制就可以了:
// 繪制矩形圖
private void drawRect(Canvas canvas, float[] data, int dataColor) {
Paint p = new Paint();
float left;
float top;
float right;
float bottom;
float stopY = getDataY(Float.parseFloat(Ylabel[Ylabel.length - 1]),
Ylabel);// 灰色線Y軸位置
float rectYScale = (Ypoint - stopY) / 100;
p.setAntiAlias(true);
p.setStrokeWidth(dataStrokeWidth);
p.setColor(dataColor);
// 橫向
for (int i = 0; i < Xlabel.length; i++) {
// 繪制柱形圖
if (i != 0) {
left = Xpoint + (i - 1) * Xscale + xFirstPointOffset + Xscale
/ 6;
top = Ypoint - data[i - 1] * rectYScale + lineStrokeWidth;//要繪制的rect最終top值
//起點(diǎn)top + (起點(diǎn)top - 終點(diǎn)top) * mRectFration
rectCurrentTops[i] = Ypoint - (Ypoint - top) * mRectFration;//根據(jù)fraction動態(tài)更新top值
right = left + Xscale * 4 / 6;
bottom = Ypoint;
canvas.drawRect(left, rectCurrentTops[i], right, bottom, p);//每次valueAnimator更新時重繪最新top值
}
}
}
值得注意的一點(diǎn)是,由于本次demo是根據(jù)數(shù)據(jù)長度動態(tài)繪制谆棺,所以需要在view onLayout之后才能設(shè)置數(shù)據(jù)給view并正確的繪制,因此在view暴露一個接口給Activity碍岔,Activity實(shí)現(xiàn)這個接口蔼啦,在View onLayout之后設(shè)置數(shù)據(jù)仰猖。
public interface OnViewLayoutListener {
public void onLayoutSuccess();
}
public void setOnViewLayoutListener(
OnViewLayoutListener onViewLayoutListener) {
this.onViewLayoutListener = onViewLayoutListener;
}
private OnViewLayoutListener onViewLayoutListener;
根據(jù)上面的方法將整個demo完成后饥侵,效果如下。