折れ線グラフコントロールを自分で作成するChartView
13232 ワード
折れ線グラフは多くのAppで役立ち、GitHubにはhellecharts-android、achartengineなどの機能的な折れ線グラフのフレームワークがあります.しかし、多くの場合、デザイナーが与えたスタイルは、これらのフレームワークによって完全に効果が得られるとは限らない.だから、ビューをカスタマイズして自分で折れ線図を描くことを考えています.
最終効果図
展示効果によって、座標軸、目盛り、目盛り値、データ点線、タイトルはすべて自画で実現されます.
ドラフト関連パラメータの初期化
コンストラクション関数でブラシやデータの目盛り値などのパラメータを初期化します.
x軸の目盛値、データ点、yにおける目盛値設定初期値は、初期値に基づいて先に描画される.後でデータを再設定してから再描画します.所与のデータ点からyの座標スケーリング値を生成する際には、2点:1を考慮する必要がある.データ点とその連線は座標領域の中間位置に描画する必要があり、データ点の臨界値(最大または最小値)はy座標目盛りの臨界値を超えてはならない.2.y目盛り値は均等に分け、異なるデータ値に基づいて適切なy目盛り値を示す必要がある.従って、
データ値を並べ替えて、中間値
描画
描画方法の具体的な実装:
private void showDetails(int index) { if (mPopWin != null) mPopWin.dismiss(); TextView tv = new TextView(getContext()); tv.setTextColor(Color.WHITE); tv.setBackgroundResource(R.drawable.shape_pop_bg); GradientDrawable myGrad = (GradientDrawable) tv.getBackground(); myGrad.setColor(Color.parseColor(mDataLineColors[index])); tv.setPadding(20, 0, 20, 0); tv.setGravity(Gravity.CENTER); tv.setText(data[index] + "%"); mPopWin = new PopupWindow(tv, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mPopWin.setBackgroundDrawable(new ColorDrawable(0)); mPopWin.setFocusable(false);//座標点の位置からポップアップ窓の展示位置int xoff=(int)(mDataCoords[index][0]-0.5 f*xScale)を計算する.int yoff = -(int) (getHeight() - mDataCoords[index][1] + 0.75f * yScale); mPopWin.showAsDropDown(this, xoff, yoff); mPopWin.update(); }
private void hideDetails() { if (mPopWin != null) mPopWin.dismiss(); }
/** x軸目盛り値 を設定. @param xLabel x目盛り*/public void setxLabel(String[]xLabel){this.xLabel=xLabel;}
/**設定データ @param dataデータ値*/public void setData(String[]data){this.data=data;}
/**設定タイトル @param titleタイトル*/public void settitle(String title){this.title=title;}
/** x軸目盛り、データ、タイトルを再設定した後、再描画*/public void fresh(){init();postInvalidate();
private void setData(){String title="7日年化収益率(%)";String[]xLabel 1={"12-11","12-12","12-13","12-14","12-15","12-16","12-17"};String[] xLabel2 = {"2-13", "2-14", "2-15", "2-16", "2-17", "2-18", "2-19"}; String[] data1 = {"2.92", "2.99", "3.20", "2.98", "2.92", "2.94", "2.90"}; String[] data2 = {"2.50", "2.50", "2.50", "2.50", "2.50", "2.50", "2.50"}; mChartView1.setTitle(title); mChartView1.setxLabel(xLabel1); mChartView1.setData(data1); mChartView1.fresh(); mChartView2.setTitle(title); mChartView2.setxLabel(xLabel2); mChartView2.setData(data2); mChartView2.fresh(); }
最終効果図
展示効果によって、座標軸、目盛り、目盛り値、データ点線、タイトルはすべて自画で実現されます.
ドラフト関連パラメータの初期化
コンストラクション関数でブラシやデータの目盛り値などのパラメータを初期化します.
private void init() {
this.setBackgroundColor(Color.WHITE);
// x
if (xLabel == null) {
xLabel = new String[]{"12-11", "12-12", "12-13", "12-14", "12-15", "12-16", "12-17"};
}
//
if (data == null) {
data = new String[]{"2.98", "2.99", "2.99", "2.98", "2.92", "2.94", "2.95"};
}
//
if (title == null) {
title = " (%)";
}
// Y
yLabel = createYLabel();
//
mDataLineColors = new String[]{"#fbbc14", "#fbaa0c", "#fbaa0c", "#fb8505", "#ff6b02", "#ff5400", "#ff5400"};
//
mDataLinePaint = new Paint(); // ( )
mScaleLinePaint = new Paint(); // ( )
mScaleValuePaint = new Paint(); // ( )
mBackColorPaint = new Paint(); // ( )
//
mDataLinePaint.setAntiAlias(true);
mScaleLinePaint.setAntiAlias(true);
mScaleValuePaint.setAntiAlias(true);
mBackColorPaint.setAntiAlias(true);
}
x軸の目盛値、データ点、yにおける目盛値設定初期値は、初期値に基づいて先に描画される.後でデータを再設定してから再描画します.所与のデータ点からyの座標スケーリング値を生成する際には、2点:1を考慮する必要がある.データ点とその連線は座標領域の中間位置に描画する必要があり、データ点の臨界値(最大または最小値)はy座標目盛りの臨界値を超えてはならない.2.y目盛り値は均等に分け、異なるデータ値に基づいて適切なy目盛り値を示す必要がある.従って、
createYLabel()
法は、所与のデータ点の値に基づいて対応するyスケール値を算出するアルゴリズムを実現する./**
* data Y
*
* @return y
*/
private String[] createYLabel() {
float[] dataFloats = new float[7];
for (int i = 0; i < data.length; i++) {
dataFloats[i] = Float.parseFloat(data[i]);
}
//
Arrays.sort(dataFloats);
//
float middle = format3Bit((dataFloats[0] + dataFloats[6]) / 2f);
// y ,+0.01f y 0.
float scale = format3Bit((dataFloats[6] - dataFloats[0]) / 4 + 0.01f);
String[] yText = new String[5];
yText[0] = (middle - 2 * scale) + "";
yText[1] = (middle - scale) + "";
yText[2] = middle + "";
yText[3] = (middle + scale) + "";
yText[4] = (middle + 2 * scale) + "";
for (int i = 0; i < yText.length; i++) {
yText[i] = format3Bit(yText[i]);
}
return yText;
}
データ値を並べ替えて、中間値
middle
、y軸目盛り値scale
を算出する.format3Bit(float number)
は計算結果をフォーマットし、目盛り値の小数位数が一致することを保証する./**
* ###.000
*
* @return ###.000
*/
private float format3Bit(float number) {
DecimalFormat decimalFormat = new DecimalFormat("###.000");
String target = decimalFormat.format(number);
if (target.startsWith(".")) {
target = "0" + target;
}
return Float.parseFloat(target);
}
/**
* ###.000
*
* @param numberStr
* @return ###.000
*/
private String format3Bit(String numberStr) {
if (TextUtils.isEmpty(numberStr)) {
return "0.000";
}
float numberFloat = Float.parseFloat(numberStr);
DecimalFormat decimalFormat = new DecimalFormat("###.000");
String target = decimalFormat.format(numberFloat);
if (target.startsWith(".")) {
target = "0" + target;
}
return target;
}
onMeasure
でペイントサイズやブラシアトリビュートなどのパラメータを初期化します.@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
initParams();
}
private void initParams() {
int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
yScale = height / 7.5f; // y
xScale = width / 7.5f; // x
startPointX = xScale / 2; // x
startPointY = yScale / 2; // UI y
xLength = 6.5f * xScale; // x
yLength = 5.5f * yScale; // y
xTextPlaceHeight = yScale / 2; // x
yTextPlaceWidth = xScale / 2; // y
titleHeight = yScale;
chartLineStrokeWidth = xScale / 50; //
coordTextSize = xScale / 5; //
dataLineStrodeWidth = xScale / 15; //
//
mBackColorPaint.setColor(0x11DEDE68);
mScaleLinePaint.setStrokeWidth(chartLineStrokeWidth);
mScaleLinePaint.setColor(0xFFDEDCD8);
mScaleValuePaint.setColor(0xFF999999);
mScaleValuePaint.setTextSize(coordTextSize);
mDataLinePaint.setStrokeWidth(dataLineStrodeWidth);
mDataLinePaint.setStrokeCap(Paint.Cap.ROUND);
mDataLinePaint.setTextSize(1.5f * coordTextSize);
}
onMeasure
では測定モードは判断されず、レイアウトではmatch_parent
または具体的なdp
の値が直接使用される.描画
onDraw
メソッドで描画します.@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackColor(canvas); //
drawYAxisAndXScaleValue(canvas); // y x
drawXAxisAndYScaleValue(canvas); // x y
drawDataLines(canvas); //
drawDataPoints(canvas); //
drawTitle(canvas); //
}
描画方法の具体的な実装:
/**
*
* @param canvas
*/
private void drawBackColor(Canvas canvas) {
for (int i = 0; i < 7; i++) {
if (i == 2 || i == 4 || i == 6) {
canvas.drawRect(startPointX + (i - 1) * xScale,
startPointY,
startPointX + i * xScale,
yLength + startPointY,
mBackColorPaint);
}
}
}
private void drawYAxisAndXScaleValue(Canvas canvas) {
for (int i = 0; i < 7; i++) {
canvas.drawLine(startPointX + i * xScale,
startPointY,
startPointX + i * xScale,
startPointY + yLength,
mScaleLinePaint);
mScaleValuePaint.getTextBounds(xLabel[i], 0, xLabel[i].length(), bounds);
if (i == 0) {
canvas.drawText(xLabel[i],
startPointX,
startPointY + yLength + bounds.height() + yScale / 15,
mScaleValuePaint);
} else {
canvas.drawText(xLabel[i],
startPointX + i * xScale - bounds.width() / 2,
startPointY + yLength + bounds.height() + yScale / 15,
mScaleValuePaint);
}
}
}
/**
* x y
* @param canvas
*/
private void drawXAxisAndYScaleValue(Canvas canvas) {
for (int i = 0; i < 6; i++) {
if (i < 5) {
mScaleValuePaint.getTextBounds(yLabel[4 - i], 0, yLabel[4 - i].length(), bounds);
canvas.drawText(yLabel[4 - i],
startPointX + xScale / 15,
startPointY + yScale * (i + 0.5f) + bounds.height() / 2,
mScaleValuePaint);
canvas.drawLine(startPointX + bounds.width() + 2 * xScale / 15,
startPointY + (i + 0.5f) * yScale,
startPointX + xLength,
startPointY + (i + 0.5f) * yScale,
mScaleLinePaint);
} else {
canvas.drawLine(startPointX,
startPointY + (i + 0.5f) * yScale,
startPointX + xLength,
startPointY + (i + 0.5f) * yScale,
mScaleLinePaint);
}
}
}
/**
*
* @param canvas
*/
private void drawDataLines(Canvas canvas) {
getDataRoords();
for (int i = 0; i < 6; i++) {
mDataLinePaint.setColor(Color.parseColor(mDataLineColors[i]));
canvas.drawLine(mDataCoords[i][0], mDataCoords[i][1], mDataCoords[i + 1][0], mDataCoords[i + 1][1], mDataLinePaint);
}
}
/**
*
* @param canvas
*/
private void drawDataPoints(Canvas canvas) {
// ,
if (isClick && clickIndex > -1) {
mDataLinePaint.setColor(Color.parseColor(mDataLineColors[clickIndex]));
canvas.drawCircle(mDataCoords[clickIndex][0], mDataCoords[clickIndex][1], xScale / 10, mDataLinePaint);
mDataLinePaint.setColor(Color.WHITE);
canvas.drawCircle(mDataCoords[clickIndex][0], mDataCoords[clickIndex][1], xScale / 20, mDataLinePaint);
mDataLinePaint.setColor(Color.parseColor(mDataLineColors[clickIndex]));
}
}
/**
*
* @param canvas
*/
private void drawTitle(Canvas canvas) {
//
mDataLinePaint.getTextBounds(title, 0, title.length(), bounds);
canvas.drawText(title, (getWidth() - bounds.width()) / 2, startPointY + yLength + yScale, mDataLinePaint);
canvas.drawLine((getWidth() - bounds.width()) / 2 - xScale / 15,
startPointY + yLength + yScale - bounds.height() / 2 + coordTextSize / 4,
(getWidth() - bounds.width()) / 2 - xScale / 2,
startPointY + yLength + yScale - bounds.height() / 2 + coordTextSize / 4,
mDataLinePaint);
}
/**
*
*
* @return
*/
private void getDataRoords() {
float originalPointX = startPointX;
float originalPointY = startPointY + yLength - yScale;
for (int i = 0; i < data.length; i++) {
mDataCoords[i][0] = originalPointX + i * xScale;
float dataY = Float.parseFloat(data[i]);
float oriY = Float.parseFloat(yLabel[0]);
mDataCoords[i][1] = originalPointY - (yScale * (dataY - oriY) / (Float.parseFloat(yLabel[1]) - Float.parseFloat(yLabel[0])));
}
}
getDataRoords()
は、データ点の値および座標目盛りの比例関係からデータ点の座標を算出するためであり、データ点(小円)はクリックして再描画して表示される.データポイントをクリックすると、詳細なデータ情報がPopupWindow
で表示されます.クリックイベントはonTouchEnvent
で直接処理されます.@Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
for (int i = 0; i < 7; i++) {
float dataX = mDataCoords[i][0];
float dataY = mDataCoords[i][1];
// / ,
if (Math.abs(touchX - dataX) < xScale / 2 && Math.abs(touchY - dataY) < yScale / 2) {
isClick = true;
clickIndex = i;
invalidate(); //
showDetails(i); // PopupWindow
return true;
} else {
hideDetails();
}
clickIndex = -1;
invalidate();
}
return super.onTouchEvent(event);
}```
, , `showDetails(i)` , 。
private void showDetails(int index) { if (mPopWin != null) mPopWin.dismiss(); TextView tv = new TextView(getContext()); tv.setTextColor(Color.WHITE); tv.setBackgroundResource(R.drawable.shape_pop_bg); GradientDrawable myGrad = (GradientDrawable) tv.getBackground(); myGrad.setColor(Color.parseColor(mDataLineColors[index])); tv.setPadding(20, 0, 20, 0); tv.setGravity(Gravity.CENTER); tv.setText(data[index] + "%"); mPopWin = new PopupWindow(tv, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mPopWin.setBackgroundDrawable(new ColorDrawable(0)); mPopWin.setFocusable(false);//座標点の位置からポップアップ窓の展示位置int xoff=(int)(mDataCoords[index][0]-0.5 f*xScale)を計算する.int yoff = -(int) (getHeight() - mDataCoords[index][1] + 0.75f * yScale); mPopWin.showAsDropDown(this, xoff, yoff); mPopWin.update(); }
private void hideDetails() { if (mPopWin != null) mPopWin.dismiss(); }
: `index` , `xoff ` `xoff `, 。
##### ,
/**
/**
/**
/**
, , 、 , 。
#####
private void setData(){String title="7日年化収益率(%)";String[]xLabel 1={"12-11","12-12","12-13","12-14","12-15","12-16","12-17"};String[] xLabel2 = {"2-13", "2-14", "2-15", "2-16", "2-17", "2-18", "2-19"}; String[] data1 = {"2.92", "2.99", "3.20", "2.98", "2.92", "2.94", "2.90"}; String[] data2 = {"2.50", "2.50", "2.50", "2.50", "2.50", "2.50", "2.50"}; mChartView1.setTitle(title); mChartView1.setxLabel(xLabel1); mChartView1.setData(data1); mChartView1.fresh(); mChartView2.setTitle(title); mChartView2.setxLabel(xLabel2); mChartView2.setData(data2); mChartView2.fresh(); }
![ 1](http://upload-images.jianshu.io/upload_images/1801191-c593c7a590797c62.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![ 2](http://upload-images.jianshu.io/upload_images/1801191-7ae480b871c84795.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
***
:https://github.com/xiaoyanger0825/ChartView