您好,登錄后才能下訂單哦!
繼續練習自定義View,這次帶來的是簡易折線圖,支持坐標點點擊監聽,效果如下:
畫坐標軸、畫刻度、畫點、連線。。x、y軸的數據范圍是寫死的 1 <= x <= 7 ,1 <= y <= 70 。。寫活的話涉及到坐標軸刻度的動態計算、坐標點的坐標修改,想想就頭大,這里只練習自定義View。
1、在res/values文件夾下新建attrs.xml文件,編寫自定義屬性:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LineChartView"> <attr name="textColor" format="color" /> <attr name="lineColor" format="color" /> <attr name="pointColor" format="color" /> </declare-styleable> </resources>
2、新建LineChartView繼承View,重寫構造方法:
public LineChartView(Context context) { this(context, null); } public LineChartView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LineChartView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }
3、在第三個構造方法中獲取自定義屬性的值:
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LineChartView, defStyleAttr, 0); mTextColor = ta.getColor(R.styleable.LineChartView_textColor, 0xff381a59); mLineColor = ta.getColor(R.styleable.LineChartView_lineColor, 0xff8e29fa); mPointColor = ta.getColor(R.styleable.LineChartView_pointColor, 0xffff5100); mPointRadius = DensityUtils.dp2px(context, 3); ta.recycle();
4、創建畫圖所使用的對象,如Paint、Path:
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setStyle(Paint.Style.FILL); mTextPaint.setColor(mTextColor); mTextPaint.setTextSize(40); mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mLinePaint.setStyle(Paint.Style.STROKE); mLinePaint.setColor(mLineColor); mLinePaint.setStrokeWidth(DensityUtils.dp2px(context, 2)); mLinePaint.setStrokeCap(Paint.Cap.ROUND); mXyPath = new Path(); mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPointPaint.setStyle(Paint.Style.FILL); mPointPaint.setColor(mPointColor); mPointCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPointCirclePaint.setStyle(Paint.Style.STROKE); mPointCirclePaint.setStrokeWidth(DensityUtils.dp2px(context, 2)); mPointCirclePaint.setColor(mLineColor);
5、重寫onMeasure()方法,計算自定義View的寬高:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measuredDimension(widthMeasureSpec), measuredDimension(heightMeasureSpec)); } private int measuredDimension(int measureSpec) { int result; int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); if (mode == MeasureSpec.EXACTLY) { result = size; } else { result = 500; if (mode == MeasureSpec.AT_MOST) { result = Math.min(result, size); } } return result; }
6、暴露一個設置x、y數據集合的方法:
/** * 設置數據 * * @param xList x軸數據集合 * @param yList y軸數據集合 */ public void setDataList(List<Integer> xList, List<Integer> yList) { if (xList == null || yList == null || xList.size() == 0 || yList.size() == 0) { throw new IllegalArgumentException("沒有數據"); } if (xList.size() != yList.size()) { throw new IllegalArgumentException("x、y軸數據長度不一致"); } setPointData(xList, yList); setPointAnimator(); } /** * 設置坐標點的數據、坐標 * * @param xList x軸數據集合 * @param yList y軸數據集合 */ private void setPointData(List<Integer> xList, List<Integer> yList) { mPointList = new ArrayList<>(); for (int i = 0; i < xList.size(); i++) { ChartPoint point = new ChartPoint(); //設置坐標點的xy數據 point.setxData(xList.get(i)); point.setyData(yList.get(i)); //計算坐標點的橫縱坐標 point.setX(xyMargin + xList.get(i) * (getWidth() - 2 * xyMargin) / maxX); point.setY(getHeight() - xyMargin - (getHeight() - 2 * xyMargin) * yList.get(i) / maxY); mPointList.add(point); } } /** * 設置坐標點移動的動畫 */ private void setPointAnimator() { for (int i = 0; i < mPointList.size(); i++) { final ChartPoint point = mPointList.get(i); ValueAnimator anim; if (mLastPointList != null && mLastPointList.size() > 0) { anim = ValueAnimator.ofInt(mLastPointList.get(i).getY(), point.getY()); } else { anim = ValueAnimator.ofInt(getHeight() - xyMargin, point.getY()); } anim.setDuration(500); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); point.setY(value); invalidate(); } }); anim.start(); } //儲存坐標點集合 mLastPointList = mPointList; }
7、重寫onDraw()方法,繪制坐標軸、刻度,畫點連線,注意坐標的計算:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mPointList == null || mPointList.size() == 0) { return; } mXyPath.reset(); mXyPath.moveTo(xyMargin, 0); mXyPath.lineTo(xyMargin, getHeight() - xyMargin); mXyPath.lineTo(getWidth(), getHeight() - xyMargin); canvas.drawPath(mXyPath, mLinePaint);//畫x、y坐標軸 for (int i = 0; i < mPointList.size(); i++) { //畫x軸刻度線 int x = xyMargin + (i + 1) * (getWidth() - 2 * xyMargin) / mPointList.size(); canvas.drawLine(x, getHeight() - xyMargin - graduatedLineLength, x, getHeight() - xyMargin, mLinePaint); //畫y軸刻度線 int y = getHeight() - xyMargin - (i + 1) * (getHeight() - 2 * xyMargin) / mPointList.size(); canvas.drawLine(xyMargin, y, xyMargin + graduatedLineLength, y, mLinePaint); //畫坐標軸刻度文本 canvas.drawText(String.valueOf(mPointList.get(i).getxData()), x, getHeight() - mTextPaint.getTextSize() / 4, mTextPaint); canvas.drawText(String.valueOf((i + 1) * 10), 0, y + mTextPaint.getTextSize() / 2, mTextPaint); } //畫連接線 for (int i = 0; i < mPointList.size(); i++) { if (i != mPointList.size() - 1) { ChartPoint lastP = mPointList.get(i); ChartPoint nextP = mPointList.get(i + 1); canvas.drawLine(lastP.getX(), lastP.getY(), nextP.getX(), nextP.getY(), mLinePaint); } } //畫坐標點 for (int i = 0; i < mPointList.size(); i++) { ChartPoint point = mPointList.get(i); canvas.drawCircle(point.getX(), point.getY(), mPointRadius, mPointPaint); canvas.drawCircle(point.getX(), point.getY(), mPointRadius, mPointCirclePaint); } }
8、設置坐標點點擊事件:
private OnPointClickListener mOnPointClickListener; /** * 坐標點點擊監聽 */ public interface OnPointClickListener { /** * @param index 當前坐標點在數據集中的下標 * @param point 當前坐標點對象 */ void onPointClick(int index, ChartPoint point); } public void setOnPointClickListener(OnPointClickListener onPointClickListener) { mOnPointClickListener = onPointClickListener; }
9、重寫onTouchEvent()方法,判斷當前點擊的點是不是在坐標點范圍內:
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //判斷當前點擊的點是否在坐標點范圍內 int curX = (int) event.getX(); int curY = (int) event.getY(); for (int i = 0; i < mPointList.size(); i++) { ChartPoint point = mPointList.get(i); double d1 = Math.pow(curX - point.getX(), 2); double d2 = Math.pow(curY - point.getY(), 2); //√ ̄(curX - cx)² + (curY - cy)² < R if (Math.sqrt(d1 + d2) < mPointRadius + 10) {//為了方便點擊,把坐標點范圍增大了10像素 if (mOnPointClickListener != null) { mOnPointClickListener.onPointClick(i, point); } } } break; } return super.onTouchEvent(event); }
10、在activity_main.xml布局文件中使用該View:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:lcv="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" tools:context=".MainActivity"> <com.monkey.linechartview.LineChartView android:id="@+id/chartView" android:layout_width="250dp" android:layout_height="250dp" android:layout_marginTop="@dimen/activity_vertical_margin" lcv:lineColor="#8e29fa" lcv:pointColor="#ff5100" lcv:textColor="#000000" /> <Button android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/activity_vertical_margin" android:text="set data" android:textAllCaps="false" /> </LinearLayout>
11、在MainActivity.java中傳入數據集合,并設置坐標點點擊監聽:
btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { List<Integer> xList = new ArrayList<>(); List<Integer> yList = new ArrayList<>(); for (int i = 0; i < 7; i++) { xList.add(i + 1); int y = (int) (Math.random() * 70 + 1); yList.add(y); } chartView.setDataList(xList, yList); } }); chartView.setOnPointClickListener(new LineChartView.OnPointClickListener() { @Override public void onPointClick(int position, ChartPoint point) { tv.setText("position:" + position + "\nx:" + point.getxData() + "\ny:" + point.getyData()); } });
致此大致步驟完成了,發現和上一篇步驟差不多。。代碼已上傳github:
https://github.com/MonkeyMushroom/LineChartView/tree/master
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。