Android 自定義控制元件 (一) ,柱狀圖 ,Canvas 繪製 柱狀圖 ,支援觸控操作
專案中,經常會用到統計圖表,個性化展示資料,增加趣味性,之前也用過百度Echarts來展示,效果很不錯,包括一些互動操作,不得不說,echarts幫我我們實現了絕大多數的需求,體積小不說,實現方式也很簡單,後來想了想,為什麼不用安卓Canvas繪製呢,畢竟是安卓開發攻城獅,下面就用Canvas繪製一個自帶動畫和觸控事件的柱狀圖
先來張效果圖:
效果還是不錯的 , 這裡我主要是添加了動畫,以便於更加直觀的瞭解柱狀圖的整個繪製過程
自定義View當然少不了測量寬高,確定繪製區域了,老樣子,還是在onLayout ( ) 中獲取控制元件的尺寸,確定下邊界 , 我這裡是預留了些距離
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { startX = getPaddingLeft() + basePadding; endX = getMeasuredWidth() - getPaddingRight() - basePadding; startY = getMeasuredHeight() - getPaddingBottom() - basePadding * 3; endY = getPaddingTop() + basePadding * 4; } }
繪製區域確定以後 ,不著急繪製 , 先來分析下好了 ,Canvas繪製矩形應該是輕車熟路,主要靠的還是動畫 , 這樣在繪製圖表的時候,整個繪製過程才會更加細膩,平滑 ; 這裡我用的是 ValueAnimator , ValueAnimator.ofFloat(0, 1.0f) 意思是從0到1漸變 ,想要獲取實時的動畫值,可以通過設定動畫的更新監聽ValueAnimator.AnimatorUpdateListener , 拿到當前的mAnimatedValue , 每個矩形的最大值 * mAnimatedValue 就是當前矩形的高度了
private void darwRect(Canvas canvas) { float dx = getDx(); float dy = (startY - endY - basePadding) / 100; for (int i = 0; i < datas.size(); i++) { RectF rectf = new RectF(startX + dx * i, startY - datas.get(i).y * dy * (isUserAnimator ? mAnimatedValue : 1), startX + dx * (i + 1), startY); rectPaint = new Paint(basePaint); if (datas.get(i).y > 90 || datas.get(i).y <= 10) { rectPaint.setColor(ContextCompat.getColor(getContext(), labelColors[2])); touchColors.put(i, labelColors[2]); } else if (datas.get(i).y >= 60) { rectPaint.setColor(ContextCompat.getColor(getContext(), labelColors[1])); touchColors.put(i, labelColors[1]); } else { rectPaint.setColor(ContextCompat.getColor(getContext(), labelColors[0])); touchColors.put(i, labelColors[0]); } canvas.drawRect(rectf, rectPaint); } }
柱狀圖繪製還是挺簡單的 , 但是現在問題來了 , 如果使用者在手指觸控的過程中, 想得到些UI上的反饋 , 一個柱狀圖肯定有若干個矩形組成的 , 比如說觸控時我想知道自己點了哪一個矩形 ,那應該怎麼做呢 ?
監聽觸控操作,當然是重寫View的onTouchEvent方法了 , 但是現在還有有一個問題 , 就是如何確定手指處於哪一個矩形上面?
首先,我們得確定下每個矩形的寬度了, 這裡我是繪製了9個矩形,所以單個矩形的寬度就是 getDx() 即 (endX - startX) / 9 ; onTouchEvent中可以拿到當前觸控的xy座標 , 每次ACTION_MOVE事件中 , 可以計算moveX的值 (moveX - startX) / getDx() 就是當前處於哪一個矩形上面 , 這裡我們需要取 int 型別的數值 , 剛好與資料來源的index吻合 ,如果資料長度過短,可能會索引越界,可以對index進行判斷 if (index >= datas.size()) index = datas.size() - 1;
private void drawOnTouch(Canvas canvas) {
//這裡獲取int整型數值 ,剛好與資料來源的索引吻合 ,如果資料長度過短,可能會索引越界,可以對index進行判斷
int index = (int) ((moveX - startX) / getDx());
if (index >= datas.size()) index = datas.size() - 1;
float y = datas.get(index).y;
float dx = getDx();
float dy = (startY - endY - basePadding) / 100;
float x0 = startX + index * dx;
float x1 = x0 + 0.5f * dx;
float y1 = startY - y * dy;
//畫矩形
canvas.drawRect(x0, y1, x0 + dx, startY, touchPaint);
Paint p = new Paint(touchPaint);
p.setTextSize(dip2px(15));
p.setColor(ContextCompat.getColor(getContext(), touchColors.get(index)));
canvas.drawText(String.valueOf(y), x1, y1 - basePadding / 2, p);
// canvas.drawLine(x1, startY, x1, endY, touchPaint);//輔助線 Y
// canvas.drawLine(startX + 2 * basePadding, y1, endX, y1, touchPaint);//輔助線 X
//
// //畫指示點
// Paint paint = new Paint(touchPaint);
// paint.setColor(Color.WHITE);
// paint.setStrokeWidth(dip2px(9.5f));
// canvas.drawPoint(x1, y1, paint);//畫圓點
//
// paint.setColor(Color.BLACK);
// paint.setStrokeWidth(dip2px(6.5f));
// canvas.drawPoint(x1, y1, paint);//畫圓點
}
到這裡柱狀圖的繪製就完成了 , 主要還是確定觸控的索引和平滑繪製 ,