android:自定義view--區線圖
阿新 • • 發佈:2019-01-02
老規矩,先上圖,整個自定義view分為標題模組,xy軸標註資料模組和資料模組,
其實資料模組是有一個淺灰色的背景,但是動態圖裡面看不出來,真機上可以看出;
和前面幾個圖表相比增加了貝塞爾區線,虛線,手勢點選顯示標註線和當前日資料;
後面還有左右按鈕切換上週下週資料,這個不是主要功能,就不加到部落格中了,真實專案使用直接將資料準備好,
左右切換的時候切換資料來源重新整理view即可;
老樣子,直接上程式碼
/** * Created by zheng on 2018/3/28 0027. * VFX我的模組 貝塞爾區線資料圖 * 前六個繪製方法沒寫多少註釋,都是前面用到過的方法 * 繪製主要資料 和 繪製點選事件的顯示資料 註釋都寫的很詳細 */ public class VFXBesselChart extends View { //主畫筆 private Paint mPaint; //標題矩形 private Rect titleRect; //資料區域矩形 private Rect dataRect; String titleName = "浮動收益"; String[] yStrs = {"$10000.00", "$7500.00", "$5000.00", "$2500.00", "$0.00"}; String[] xStrs = {"3/20", "3/21", "3/22", "3/23", "3/24", "3/25", "3/26"}; Integer[] dataArray = {500, 5000, 1399, 9999, 7499, 999, 10}; //定時器 private CountDownTimer timer; //手勢點選記錄週期 private int weekPosition = -1; /** * 曲線上資料點 */ private List<Point> pointList = new ArrayList<>(); // 左上角標題文字顏色 x軸底部文字顏色 x軸刻度顏色 private int textTitleColor, textXColor, scaleXColor; // y軸左邊文字顏色 貝塞爾資料線顏色 手勢點選後資料標註線顏色 private int textYColor, lineBesselColor, lineClickColor; // 虛線顏色 y軸左邊文字距離 x軸下邊文字距離底部的高度 private int lineImaginaryColor, yScaleDataWidth, xScaleDataHeight; // x軸刻度線寬度 貝塞爾資料線寬度 private int scaleXWidth, lineBesselWidth; // 虛線寬度 手勢點選後資料標註線寬度 資料區域距離頂部距離 private float lineImaginaryWidth, lineClickWidth, dataMarginTop; //資料區域距離右邊的距離 private int dataMarginRightWidth; //資料區域內部padding距離 private int dataPaddingLeft; private int dataPaddingRight; private int dataPaddingTop; private int dataPaddingBottom; public VFXBesselChart(Context context) { this(context, null); } public VFXBesselChart(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public VFXBesselChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initXmlAttrs(context, defStyleAttr); initPaint(); } private void initPaint() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStrokeWidth(2); } private void initXmlAttrs(Context context, int attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VFXBesselChart); if (typedArray == null) return; textTitleColor = typedArray.getColor(R.styleable.VFXBesselChart_text_title_color, getResources().getColor(R.color.black)); textXColor = typedArray.getColor(R.styleable.VFXBesselChart_text_x_color, getResources().getColor(R.color.black)); scaleXColor = typedArray.getColor(R.styleable.VFXBesselChart_scale_x_color, getResources().getColor(R.color.gray_d5)); textYColor = typedArray.getColor(R.styleable.VFXBesselChart_text_y_color, getResources().getColor(R.color.black)); lineBesselColor = typedArray.getColor(R.styleable.VFXBesselChart_line_bessel_color, getResources().getColor(R.color.gold)); lineClickColor = typedArray.getColor(R.styleable.VFXBesselChart_line_click_color, getResources().getColor(R.color.black_an)); lineImaginaryColor = typedArray.getColor(R.styleable.VFXBesselChart_line_imaginary_color, getResources().getColor(R.color.gray_d5)); scaleXWidth = typedArray.getInteger(R.styleable.VFXBesselChart_scale_x_width, 2); lineImaginaryWidth = typedArray.getDimension(R.styleable.VFXBesselChart_line_imaginary_width, 2); lineBesselWidth = typedArray.getInteger(R.styleable.VFXBesselChart_line_bessel_widht, 3); lineClickWidth = typedArray.getDimension(R.styleable.VFXBesselChart_line_click_widht, 2); dataMarginTop = typedArray.getDimension(R.styleable.VFXBesselChart_data_margin_top, 120); yScaleDataWidth = typedArray.getInteger(R.styleable.VFXBesselChart_width_y_scale_data, 150); xScaleDataHeight = typedArray.getInteger(R.styleable.VFXBesselChart_height_x_scale_data, 60); dataMarginRightWidth = typedArray.getInteger(R.styleable.VFXBesselChart_width_data_margin_right, 50); dataPaddingLeft = typedArray.getInteger(R.styleable.VFXBesselChart_padding_data_left, 10); dataPaddingRight = typedArray.getInteger(R.styleable.VFXBesselChart_padding_data_right, 10); dataPaddingTop = typedArray.getInteger(R.styleable.VFXBesselChart_padding_data_top, 10); dataPaddingBottom = typedArray.getInteger(R.styleable.VFXBesselChart_padding_data_bottom, 20); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //繪製左上角標題 浮動收益 drawTitle(canvas); //資料區域 (灰色矩形) drawDataRect(canvas); //y軸左邊資料 ($10000.00) drawYStrs(canvas); //x軸下面的資料 (3.20) drawXStrs(canvas); //繪製五條虛線 drawImaginary(canvas); //繪製x軸的刻度 drawXScales(canvas); //繪製主要資料 drawMainDatas(canvas); //繪製點選事件的顯示資料 drawClickData(canvas); } //繪製左上角標題 浮動收益 private void drawTitle(Canvas canvas) { mPaint.setTextAlign(Paint.Align.LEFT); mPaint.setColor(textTitleColor); mPaint.setTextSize(38); titleRect = new Rect(); mPaint.getTextBounds(titleName, 0, titleName.length(), titleRect); canvas.drawText( titleName, 50, titleRect.height() + 40, mPaint ); } //資料區域 (灰色矩形) private void drawDataRect(Canvas canvas) { mPaint.setColor(Color.parseColor("#f8f8f8")); dataRect = new Rect( yScaleDataWidth, (int) dataMarginTop, getWidth() - dataMarginRightWidth, getHeight() - xScaleDataHeight ); canvas.drawRect( dataRect, mPaint ); } //y軸左邊資料 ($10000.00) private void drawYStrs(Canvas canvas) { mPaint.setTextSize(25); mPaint.setColor(textYColor); mPaint.setTextAlign(Paint.Align.RIGHT); for (int i = 0; i < yStrs.length; i++) { canvas.drawText( yStrs[i], yScaleDataWidth - 5, (dataRect.height() - 30) / 4 * i + 20 + dataMarginTop, mPaint ); } } //x軸下面的資料 (3.20) private void drawXStrs(Canvas canvas) { mPaint.setTextSize(25); mPaint.setColor(textXColor); for (int i = 0; i < 7; i++) { if (i == 0) { mPaint.setTextAlign(Paint.Align.LEFT); } else if (i == 6) { mPaint.setTextAlign(Paint.Align.RIGHT); } else { mPaint.setTextAlign(Paint.Align.CENTER); } Rect xDataRect = new Rect(); mPaint.getTextBounds(xStrs[i], 0, xStrs[i].length(), xDataRect); canvas.drawText( xStrs[i], yScaleDataWidth + 10 + ((dataRect.width() - 20) / 6) * i, dataMarginTop + dataRect.height() + (int) (xDataRect.height() * 1.5), mPaint ); } } //繪製五條虛線 private void drawImaginary(Canvas canvas) { Paint imaginaryPaint = new Paint(); imaginaryPaint.setStyle(Paint.Style.FILL); imaginaryPaint.setColor(lineImaginaryColor); imaginaryPaint.setStrokeWidth(lineImaginaryWidth); //繪製長度為4的實線後再繪製長度為4的空白區域,0位間隔 DashPathEffect effect = new DashPathEffect(new float[]{4, 4}, 0); imaginaryPaint.setPathEffect(effect); imaginaryPaint.setAntiAlias(true); int allImaginaryHeight = dataRect.height() - dataPaddingTop - dataPaddingBottom; for (int i = 0; i < 5; i++) { //兩點一線 canvas.drawLine( yScaleDataWidth + dataPaddingLeft, dataMarginTop + dataPaddingTop + allImaginaryHeight / 4 * i, getWidth() - dataMarginRightWidth - dataPaddingRight, dataMarginTop + dataPaddingTop + allImaginaryHeight / 4 * i, imaginaryPaint ); } } //繪製x軸的刻度 private void drawXScales(Canvas canvas) { Paint xScalesPaint = new Paint(); xScalesPaint.setAntiAlias(true); xScalesPaint.setStyle(Paint.Style.STROKE); xScalesPaint.setColor(scaleXColor); xScalesPaint.setStrokeWidth(scaleXWidth); int allXScalesWidth = getWidth() - yScaleDataWidth - dataPaddingLeft - dataPaddingRight - dataMarginRightWidth; for (int i = 0; i < 7; i++) { canvas.drawLine( yScaleDataWidth + dataPaddingLeft + allXScalesWidth / 6 * i, getHeight() - xScaleDataHeight, yScaleDataWidth + dataPaddingLeft + allXScalesWidth / 6 * i, getHeight() - xScaleDataHeight - dataPaddingBottom + 10, xScalesPaint ); } } //繪製主要資料 private void drawMainDatas(Canvas canvas) { Paint dataPaint = new Paint(); dataPaint.setAntiAlias(true); dataPaint.setStyle(Paint.Style.STROKE); dataPaint.setColor(lineBesselColor); dataPaint.setStrokeWidth(lineBesselWidth); Path path = new Path(); path.reset(); //計算整個資料區域的寬高 int allDataHeight = dataRect.height() - dataPaddingTop - dataPaddingBottom; int allDataWidth = dataRect.width() - dataPaddingLeft - dataPaddingRight; /** * 計算資料的XY座標 */ for (int i = 0; i < dataArray.length; i++) { Point point = new Point(); point.set( yScaleDataWidth + dataPaddingLeft + allDataWidth / 6 * i, (int) (getHeight() - xScaleDataHeight - dataPaddingBottom - (allDataHeight / 10000.0 * dataArray[i])) ); pointList.add(point); } //資料資料點繪製貝塞爾區線 drawScrollLine(canvas, dataPaint); } //繪製點選事件的顯示資料 private void drawClickData(Canvas canvas) { //如果為-1代表沒有點選 if (weekPosition == -1) return; //這裡重新重建畫筆 Paint weekPaint = new Paint(); weekPaint.setAntiAlias(true); weekPaint.setStrokeWidth(lineClickWidth); weekPaint.setTextSize(20); weekPaint.setColor(lineClickColor); //繪製豎線(兩點一線) canvas.drawLine( //y軸標註資料寬度 + 資料模組左padding值 + 六天總寬度 / 6 * 第幾天 yScaleDataWidth + dataPaddingLeft + (dataRect.width() - dataPaddingLeft - dataPaddingRight) / 6 * weekPosition, //資料模組上padding值 + 資料模組距離view頂部距離 dataPaddingTop + dataMarginTop, yScaleDataWidth + dataPaddingLeft + (dataRect.width() - dataPaddingLeft - dataPaddingRight) / 6 * weekPosition, //整個view高度 - x軸下方標註資料高度 - 資料模組下padding值 getHeight() - xScaleDataHeight - dataPaddingBottom, weekPaint ); //繪製矩形資料 Point weekPoint = pointList.get(weekPosition); //獲取資料點 String weekDataStr = "$" + dataArray[weekPosition]; ToastUtils.showToast(getContext(), weekDataStr); Rect weekRect = new Rect(); weekPaint.getTextBounds(weekDataStr, 0, weekDataStr.length(), weekRect); weekPaint.setColor(Color.parseColor("#aac69d5e")); weekPaint.setTextAlign(Paint.Align.CENTER); //如果是前五天(週一到週五)顯示在資料線右邊 否則 顯示在資料線左邊 if (weekPosition < 5) { /** * 精細計算了一下寬度,高度同理,寬度根據文字寬度+預留padding值 * 寬度:當前資料點x座標 + 文字寬度 + 預留padding值(30) */ Rect weekBgRect = new Rect( weekPoint.x + 5, weekPoint.y - 35, weekPoint.x + weekRect.width() + 30, weekPoint.y - 5 ); canvas.drawRect(weekBgRect, weekPaint); weekPaint.setColor(Color.WHITE); canvas.drawText( weekDataStr, weekPoint.x + 5 + weekBgRect.width() / 2, weekPoint.y - 12, weekPaint ); } else { Rect weekBgRect = new Rect( weekPoint.x - weekRect.width() - 30, weekPoint.y - 35, weekPoint.x - 5, weekPoint.y - 5 ); canvas.drawRect(weekBgRect, weekPaint); weekPaint.setColor(Color.WHITE); canvas.drawText( weekDataStr, weekPoint.x - 5 - weekBgRect.width() / 2, weekPoint.y - 12, weekPaint ); } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: //兩天之間的寬度 int weekScale = pointList.get(1).x - pointList.get(0).x; for (int i = 0; i < pointList.size(); i++) { //for迴圈遍歷計算手勢點選座標離哪天最近 if (Math.abs(event.getX() - pointList.get(i).x) < weekScale / 2) { //標註點選 weekPosition = i; //倒計時4秒隱藏(倒計時之前先取消上次倒計時) if (timer != null) timer.cancel(); //開啟倒計時 startCountDownTime(4); invalidate(); break; } } break; } return true; } //多個數據點繪製貝塞爾區線 private void drawScrollLine(Canvas canvas, Paint dataPaint) { Point startp = new Point(); Point endp = new Point(); for (int i = 0; i < pointList.size() - 1; i++) { startp = pointList.get(i); endp = pointList.get(i + 1); int wt = (startp.x + endp.x) / 2; Point p3 = new Point(); Point p4 = new Point(); p3.y = startp.y; p3.x = wt; p4.y = endp.y; p4.x = wt; Path path = new Path(); path.moveTo(startp.x, startp.y); path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y); canvas.drawPath(path, dataPaint); } } private void startCountDownTime(long time) { timer = new CountDownTimer(time * 1000, 1000) { @Override public void onTick(long millisUntilFinished) { //每隔countDownInterval秒會回撥一次onTick()方法 } @Override public void onFinish() { //倒計時結束 weekPosition = -1; invalidate(); } }; timer.start();// 開始計時 //timer.cancel(); // 取消 } }
設定自定義屬性需要在styles.xml中宣告:
<!--我的模組貝塞爾區線資料圖--> <declare-styleable name="VFXBesselChart"> <!--左上角標題文字顏色--> <attr name="text_title_color" format="color" /> <!--x軸底部文字顏色--> <attr name="text_x_color" format="color" /> <!--x軸刻度顏色--> <attr name="scale_x_color" format="color" /> <!--y軸左邊文字顏色--> <attr name="text_y_color" format="color" /> <!--貝塞爾資料線顏色--> <attr name="line_bessel_color" format="color" /> <!--手勢點選後資料標註線顏色--> <attr name="line_click_color" format="color" /> <!--虛線顏色--> <attr name="line_imaginary_color" format="color" /> <!--x軸刻度線寬度--> <attr name="scale_x_width" format="integer" /> <!--虛線寬度--> <attr name="line_imaginary_width" format="dimension" /> <!--貝塞爾資料線寬度--> <attr name="line_bessel_widht" format="integer" /> <!--手勢點選後資料標註線寬度--> <attr name="line_click_widht" format="dimension" /> <!--資料區域距離頂部距離--> <attr name="data_margin_top" format="dimension" /> <!--y軸左邊文字距離--> <attr name="width_y_scale_data" format="integer"/> <!--x軸下邊文字距離底部的高度--> <attr name="height_x_scale_data" format="integer"/> <!--資料區域距離右邊的距離--> <attr name="width_data_margin_right" format="integer"/> <!--資料區域內部padding距離--> <attr name="padding_data_top" format="integer"/> <attr name="padding_data_bottom" format="integer"/> <attr name="padding_data_left" format="integer"/> <attr name="padding_data_right" format="integer"/> </declare-styleable>
<!--android:layerType="software" 繪製虛線需要設定-->
<com.example.administrator.vfxbesselchart.view.VFXBesselChart
android:layout_width="match_parent"
android:layout_height="240dp"
android:layerType="software"
app:text_title_color="#000000"/>
點選開啟連結免費下載原始碼