This article shares the specific code of Android to implement a smooth curve chart that can be used for your reference. The specific content is as follows
Directly upload the code, with detailed annotations
1 attr attribute writing
<!-- xyAxis color --> <attr name="xy_line_color" format="color" /> <!-- xyCoordinate axis width --> <attr name="xy_line_width" format="dimension" /> <!-- xyAxis text color --> <attr name="xy_text_color" format="color" /> <!-- xyAxis text size --> <attr name="xy_text_size" format="dimension" /> <!-- Color of the line in the line chart --> <attr name="line_color" format="color" /> <!-- xHorizontal spacing between each coordinate point of the axis --> <attr name="interval" format="dimension" /> <!-- Background color --> <attr name="bg_color" format="color" /> <!-- Select the external color of the curve --> <attr name="select_circle_color" format="color" /> <!-- Select the internal color of the curve --> <attr name="select_reminder_color" format="color" /> <!--Do you raise your hand and scroll--> <attr name="isScroll" format="boolean" /> <declare-styleable name="ChartView"> <attr name="xy_line_color" /> <attr name="xy_line_width" /> <attr name="xy_text_color" /> <attr name="xy_text_size" /> <attr name="line_color" /> <attr name="interval" /> <attr name="bg_color" /> <attr name="select_circle_color" /> <attr name="select_reminder_color" /> <attr name="isScroll" /> <!--The prompt box and the sliding display position--> <attr name="show_position"> <enum name="first" value="1" /> <enum name="middle" value="2" /> <enum name="end" value="3" /> </attr> </declare-styleable>
2 ChartView
package ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; /** * Custom line chart */ public class ChartView extends View { private static final int FIRST = 1; private static final int MIDDLE = 2; private static final int END = 3; //xy axis color private int xyLineColor = 0xffCFE2CF; //The round color selected by the polyline private int selectCircleColor = 0xff00A8FF; //Select the color of the data prompt box private int selectReminderColor = 0xff00A8FF; //The color of the inner circle in the fold line private int xyTextColor = 0xff0014FF; //The color of the line in the line chart private int lineColor = 0xffFD00FF; //xy coordinate axis width private int xyLineWidth = dpToPx(1); //xy axis text size private int xyTextSize = spToPx(12); //Horizontal spacing of each coordinate point of the x-axis private int interval = dpToPx(40); //Background color private int bgColor = 0xffffffff; //Is there any sliding feeling when starting private boolean isScroll = false; //The prompt box display position private int mShowPositionType = 3; //Draw the brush corresponding to the XY axis coordinates private Paint mXYPaint; //Draw the brush corresponding to the text of the XY axis private Paint mXYTextPaint; //Draw the corresponding brushes of the folded lines private Paint mSpinnerLinePaint; private int width; private int height; //The origin coordinates of the x-axis private int mXOri; //The origin coordinate of the y-axis private int mYOri; //The coordinates of the first point X private float mXInit; //The maximum X coordinate corresponding to the first point private float maxXInit; //The minimum X coordinate corresponding to the first point private float minXInit; //The data corresponding to the x-axis coordinates private List<String> mXData = new ArrayList<>(); //The data corresponding to the y-axis coordinates private List<Integer> mYData = new ArrayList<>(); //The data corresponding to the polyline private Map<String, Integer> mSpinnerValue = new HashMap<>(); //How many points in the X-axis corresponding to the clicked point are 1 by default private int selectIndex = 1; //The maximum rectangle corresponding to the X-axis scale text. In order to select, the frame sizes drawn in the x-axis text are the same, and the x-axis data obtained from the data are obtained to obtain the longest data private Rect xValueRect; //Speed Detector private VelocityTracker mTracker; //Is it a short distance sliding private boolean isShortSlide = false; //Get the middle of the size private int mSelectMiddle = 0; //Curve cut ratio private float mLineSmoothness = 0.18f; public ChartView(Context context) { this(context, null); } public ChartView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ChartView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr); initPaint(); } //Set the cut rate public void setLineSmoothness(float lineSmoothness) { if (lineSmoothness != ) { = lineSmoothness; } } /** * Initialization */ private void initPaint() { mXYPaint = new Paint(); (true); (xyLineWidth); (); (xyLineColor); mXYTextPaint = new Paint(); (true); (xyTextSize); (); (xyTextColor); (); mSpinnerLinePaint = new Paint(); (true); (xyLineWidth); (lineColor); (); (); } /** * Initialization * * @param context * @param attrs * @param defStyleAttr */ private void init(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray array = (attrs, , defStyleAttr, 0); int count = (); for (int i = 0; i < count; i++) { int attr = (i); if (attr == .ChartView_xy_line_color) { xyLineColor = (attr, xyLineColor); } else if (attr == .ChartView_xy_line_width) { xyLineWidth = (int) (attr, (TypedValue.COMPLEX_UNIT_PX, xyLineWidth, getResources().getDisplayMetrics())); } else if (attr == .ChartView_xy_text_color) { xyTextColor = (attr, xyTextColor); } else if (attr == .ChartView_xy_text_size) { xyTextSize = (int) (attr, (TypedValue.COMPLEX_UNIT_PX, xyTextSize, getResources().getDisplayMetrics())); } else if (attr == .ChartView_line_color) { lineColor = (attr, lineColor); } else if (attr == .ChartView_interval) { interval = (int) (attr, (TypedValue.COMPLEX_UNIT_PX, interval, getResources().getDisplayMetrics())); } else if (attr == .ChartView_bg_color) { bgColor = (attr, bgColor); } else if (attr == .ChartView_select_circle_color) { selectCircleColor = (attr, selectCircleColor); } else if (attr == .ChartView_select_reminder_color) { selectReminderColor = (attr, selectReminderColor); } else if (attr == .ChartView_isScroll) { isScroll = (attr, isScroll); } else if (attr == .ChartView_show_position) { mShowPositionType = (attr, mShowPositionType); } } (); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (changed) { width = getWidth(); height = getHeight(); //The maximum width of Y-axis text float textYWdith = getTextBounds((getListItemMaxIndex(mYData)) + "", mXYTextPaint).width(); for (int i = 0; i < (); i++) {//Finding the maximum width of the y-axis text float temp = getTextBounds((i) + "", mXYTextPaint).width(); if (temp > textYWdith) textYWdith = temp; } int dp2 = dpToPx(2); int dp3 = dpToPx(3); mXOri = (int) (dp2 + textYWdith + dp2 + xyLineWidth); //Get the rectangle occupied by the longest text width of the x-axis xValueRect = getTextBounds((getListItemMaxIndex(mXData)), mXYTextPaint); //X-axis text height float textXHeight = (); for (int i = 0; i < (); i++) { Rect rect = getTextBounds((i) + "", mXYTextPaint); if (() > textXHeight) textXHeight = (); if (() > ()) xValueRect = rect; } mYOri = (int) (height - dp2 - textXHeight - dp3 - xyLineWidth); mXInit = mXOri + () / 2 + dpToPx(5); minXInit = width - (width - mXOri) * 0.1f - interval * (() - 1); maxXInit = mXInit; } selectIndex = getSelectIndexFromShowType(mShowPositionType); (changed, left, top, right, bottom); } @Override protected void onDraw(Canvas canvas) { (bgColor); drawXY(canvas); drawBrokenLineAndPoint(canvas); } /** * Draw the corresponding point at the intersection */ private void drawBrokenLineAndPoint(Canvas canvas) { if (() <= 0) return; int layerId = (0, 0, width, height, null, Canvas.ALL_SAVE_FLAG); drawBrokenLine(canvas); drawBrokenPoint(canvas); // Cut off the part beyond the x-axis coordinates (); (bgColor); (new PorterDuffXfermode()); RectF rectF = new RectF(0, 0, mXOri, height); (rectF, mSpinnerLinePaint); (null); (layerId); } /** * Draw the corresponding points of the curve */ private void drawBrokenPoint(Canvas canvas) { float dp2 = dpToPx(2); float dp4 = dpToPx(4); float dp7 = dpToPx(7); ("selectIndex", "index:" + selectIndex); //Draw node for (int i = 0; i < (); i++) { float x = mXInit + interval * i; float y = mYOri - mYOri * (1 - 0.1f) * ((i)) / (() - 1); //Draw the selected point if (i == selectIndex - 1) { (); //Set the selected color (selectCircleColor); (x, y, dp7, mSpinnerLinePaint); (selectReminderColor); (x, y, dp4, mSpinnerLinePaint); drawFloatTextBox(canvas, x, y - dp7, ((i))); } //Draw ordinary nodes (); (); (x, y, dp2, mSpinnerLinePaint); (); (lineColor); (x, y, dp2, mSpinnerLinePaint); } } /** * Draw floating boxes * */ private void drawFloatTextBox(Canvas canvas, float x, float y, int text) { int dp6 = dpToPx(6); int dp18 = dpToPx(18); //p1 Path path = new Path(); (x, y); //p2 (x - dp6, y - dp6); //p3 (x - dp18, y - dp6); //p4 (x - dp18, y - dp6 - dp18); //p5 (x + dp18, y - dp6 - dp18); //p6 (x + dp18, y - dp6); //p7 (x + dp6, y - dp6); //p1 (x, y); (path, mSpinnerLinePaint); (); (spToPx(14)); Rect rect = getTextBounds(text + "", mSpinnerLinePaint); (text + "", x - () / 2, y - dp6 - (dp18 - ()) / 2, mSpinnerLinePaint); } /** * Draw smooth curves */ private void drawBrokenLine(Canvas canvas) { (); (lineColor); //Draw the folded lines Path path = new Path(); float prePreviousPointX = ; float prePreviousPointY = ; float previousPointX = ; float previousPointY = ; float currentPointX = ; float currentPointY = ; float nextPointX; float nextPointY; int lineSize = (); for (int i = 0; i < lineSize; i++) { float x; float y; if ((currentPointX)) { currentPointX = getSpinnerPoint(i).x; currentPointY = getSpinnerPoint(i).y; } if ((previousPointX)) { //Is it the first point? if (i > 0) { previousPointX = getSpinnerPoint(i - 1).x; previousPointY = getSpinnerPoint(i - 1).y; } else { //Denote the previous point with the current point previousPointX = currentPointX; previousPointY = currentPointY; } } if ((prePreviousPointX)) { //The first two points? if (i > 1) { prePreviousPointX = getSpinnerPoint(i - 2).x; prePreviousPointY = getSpinnerPoint(i - 2).y; } else { //The current point indicates the previous point prePreviousPointX = previousPointX; prePreviousPointY = previousPointY; } } // Determine if it is the last point if (i < lineSize - 1) { nextPointX = getSpinnerPoint(i + 1).x; nextPointY = getSpinnerPoint(i + 1).y; } else { //Denote the next point with the current point nextPointX = currentPointX; nextPointY = currentPointY; } if (i == 0) { // Move the Path to the start point (currentPointX, currentPointY); } else { // Find the coordinates of the control point final float firstDiffX = (currentPointX - prePreviousPointX); final float firstDiffY = (currentPointY - prePreviousPointY); final float secondDiffX = (nextPointX - previousPointX); final float secondDiffY = (nextPointY - previousPointY); final float firstControlPointX = previousPointX + (mLineSmoothness * firstDiffX); final float firstControlPointY = previousPointY + (mLineSmoothness * firstDiffY); final float secondControlPointX = currentPointX - (mLineSmoothness * secondDiffX); final float secondControlPointY = currentPointY - (mLineSmoothness * secondDiffY); //Draw the curve (firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY, currentPointX, currentPointY); } // renew prePreviousPointX = previousPointX; prePreviousPointY = previousPointY; previousPointX = currentPointX; previousPointY = currentPointY; currentPointX = nextPointX; currentPointY = nextPointY; } (path, mSpinnerLinePaint); } /** * Draw XY coordinates */ private void drawXY(Canvas canvas) { int length = dpToPx(5);//The length of the scale //Draw Y coordinates (mXOri - xyLineWidth / 2, 0, mXOri - xyLineWidth / 2, mYOri, mXYPaint); //Draw arrows (); Path path = new Path(); (mXOri - xyLineWidth / 2 - dpToPx(5), dpToPx(12)); (mXOri - xyLineWidth / 2, xyLineWidth / 2); (mXOri - xyLineWidth / 2 + dpToPx(5), dpToPx(12)); (path, mXYPaint); //Draw the scale int yLength = (int) (mYOri * (1 - 0.1f) / (() - 1));//10% empty on the y-axis, calculate the y-axis scale spacing for (int i = 0; i < (); i++) { //Draw the scale (mXOri, mYOri - yLength * i + xyLineWidth / 2, mXOri + length, mYOri - yLength * i + xyLineWidth / 2, mXYPaint); (xyTextColor); //Draw text String text = (i) + ""; Rect rect = getTextBounds(text, mXYTextPaint); (text, 0, (), mXOri - xyLineWidth - dpToPx(2) - (), mYOri - yLength * i + () / 2, mXYTextPaint); } //Draw coordinates (mXOri, mYOri + xyLineWidth / 2, width, mYOri + xyLineWidth / 2, mXYPaint); //Draw arrows (); path = new Path(); //The entire length float xLength = mXInit + interval * (() - 1) + (width - mXOri) * 0.1f; if (xLength < width) xLength = width; (xLength - dpToPx(12), mYOri + xyLineWidth / 2 - dpToPx(5)); (xLength - xyLineWidth / 2, mYOri + xyLineWidth / 2); (xLength - dpToPx(12), mYOri + xyLineWidth / 2 + dpToPx(5)); (path, mXYPaint); //Draw the x-axis scale for (int i = 0; i < (); i++) { float x = mXInit + interval * i; if (x >= mXOri) {//Plot only the area starting from the origin (xyTextColor); (x, mYOri, x, mYOri - length, mXYPaint); //Draw X-axis text String text = (i); Rect rect = getTextBounds(text, mXYTextPaint); if (i == selectIndex - 1) { (lineColor); (text, 0, (), x - () / 2, mYOri + xyLineWidth + dpToPx(2) + (), mXYTextPaint); (x - () / 2 - dpToPx(3), mYOri + xyLineWidth + dpToPx(1), x + () / 2 + dpToPx(3), mYOri + xyLineWidth + dpToPx(2) + () + dpToPx(2), dpToPx(2), dpToPx(2), mXYTextPaint); } else { (text, 0, (), x - () / 2, mYOri + xyLineWidth + dpToPx(2) + (), mXYTextPaint); } } } } private float startX; private float startx; @Override public boolean onTouchEvent(MotionEvent event) { if (isScrolling) return (event); //When the view gets a click event, the parent control requests not to intercept the event ().requestDisallowInterceptTouchEvent(true); obtainVelocityTracker(event); switch (()) { case MotionEvent.ACTION_DOWN: startX = (); startx = (); ("XXXX", "down:" + startX + ""); break; case MotionEvent.ACTION_MOVE: // When the sliding distance is less than or equal to 8, the task is short-distance sliding //The product of the current x-axis size and the set x-axis distance is greater than the difference between the width of the display layout on the screen and the seven points in the x-axis, start moving. if (interval * () > width - mXOri) { //Get the sliding distance float dis = () - startX; //Reassign value to startX startX = (); //The sum of the current x origin distance and the distance sliding left and right is not large, then the current x distance is assigned to the minimum, and the following is similar if (mXInit + dis < minXInit) { mXInit = minXInit; } else if (mXInit + dis > maxXInit) { mXInit = maxXInit; } else { mXInit = mXInit + dis; } invalidate(); } break; case MotionEvent.ACTION_UP: isShortSlide = (() - startx) <= dpToPx(8); clickAction(event); scrollAfterActionUp(); ().requestDisallowInterceptTouchEvent(false); recycleVelocityTracker(); break; case MotionEvent.ACTION_CANCEL: //Add this line of code to prevent conflicts with the sliding event of the parent class ().requestDisallowInterceptTouchEvent(false); recycleVelocityTracker(); break; } return true; } //Is it sliding private boolean isScrolling = false; /** * Sliding treatment after finger lifting */ private void scrollAfterActionUp() { if (!isScroll) return; final float velocity = getVelocity(); float scrollLength = maxXInit - minXInit; if ((velocity) < 10000) scrollLength = (maxXInit - minXInit) * (velocity) / 10000; ValueAnimator animator = (0, scrollLength); ((long) (scrollLength / (maxXInit - minXInit) * 1000));//The maximum time is 1000 milliseconds, and the ratio is used for conversion here (new DecelerateInterpolator()); (new () { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { float value = (float) (); if (velocity < 0 && mXInit > minXInit) {//Swipe left if (mXInit - value <= minXInit) mXInit = minXInit; else mXInit = mXInit - value; } else if (velocity > 0 && mXInit < maxXInit) {//Swipe right if (mXInit + value >= maxXInit) mXInit = maxXInit; else mXInit = mXInit + value; } invalidate(); } }); (new () { @Override public void onAnimationStart(Animator animator) { isScrolling = true; } @Override public void onAnimationEnd(Animator animator) { isScrolling = false; } @Override public void onAnimationCancel(Animator animator) { isScrolling = false; } @Override public void onAnimationRepeat(Animator animator) { } }); (); } /** * Get speed * * @return */ private float getVelocity() { if (mTracker != null) { (1000); return (); } return 0; } /** * Click on the X-axis coordinate or polyline node * */ // 44 142 139 private void clickAction(MotionEvent event) { int dp8 = dpToPx(8); float eventX = (); float eventY = (); if (!isShortSlide) { for (int i = 0; i < (); i++) { float x = mXInit + interval * i; float start = mXOri; if (x >= start + (mSelectMiddle - 1) * interval && x < start + mSelectMiddle * interval) { selectIndex = i + 1; invalidate(); } } return; } for (int i = 0; i < (); i++) { //node float x = mXInit + interval * i; float y = mYOri - mYOri * (1 - 0.1f) * ((i)) / (() - 1); if (eventX >= x - dp8 && eventX <= x + dp8 && eventY >= y - dp8 && eventY <= y + dp8 && selectIndex != i + 1) {//There is a clickable area around each node selectIndex = i + 1; invalidate(); return; } //X-axis scale String text = (i); Rect rect = getTextBounds(text, mXYTextPaint); x = mXInit + interval * i; y = mYOri + xyLineWidth + dpToPx(2); if (eventX >= x - () / 2 - dp8 && eventX <= x + () + dp8 / 2 && eventY >= y - dp8 && eventY <= y + () + dp8 && selectIndex != i + 1) { selectIndex = i + 1; invalidate(); return; } } } /** * Get the speed tracker * * @param event */ private void obtainVelocityTracker(MotionEvent event) { if (!isScroll) return; if (mTracker == null) { mTracker = (); } (event); } /** * Recycling speed tracker */ private void recycleVelocityTracker() { if (mTracker != null) { (); mTracker = null; } } /** * According to the display type of user input, the prompt box is displayed at different positions when sliding */ private int getSelectIndexFromShowType(int showPositionType) { int visibleScale = (width - mXOri) / interval; switch (showPositionType) { case FIRST: mSelectMiddle = 1; return mSelectMiddle; case MIDDLE: if (() <= visibleScale) { mSelectMiddle = middleIndex(()); } else { mSelectMiddle = middleIndex(visibleScale); } return mSelectMiddle; //The scale that can be displayed on the screen case END: if (() <= visibleScale) { mSelectMiddle = (); } else { mSelectMiddle = visibleScale; } return visibleScale; default: mSelectMiddle = 0; return mSelectMiddle; } } public void setValue(Map<String, Integer> value) { = value; invalidate(); } public void setValue(Map<String, Integer> value, List<String> xValue, List<Integer> yValue) { = value; = xValue; = yValue; invalidate(); } public Map<String, Integer> getValue() { return mSpinnerValue; } /** * Get the rectangle that measures the text * * @param text * @param paint * @return */ private Rect getTextBounds(String text, Paint paint) { Rect rect = new Rect(); (text, 0, (), rect); return rect; } /** * dp converts to px * * @param dp * @return */ private int dpToPx(int dp) { float density = getContext().getResources().getDisplayMetrics().density; return (int) (dp * density + 0.5f * (dp >= 0 ? 1 : -1)); } /** * sp to px * * @param sp * @return */ private int spToPx(int sp) { float scaledDensity = getContext().getResources().getDisplayMetrics().scaledDensity; return (int) (scaledDensity * sp + 0.5f * (sp >= 0 ? 1 : -1)); } /** * Get the longest index in the collection */ private static final int NULL_INDEX = -1; public int getListItemMaxIndex(List<?> data) { if (data == null || () < 1) { return NULL_INDEX; } int max = ((0) + "").length(); for (int i = 0; i < (); i++) { String s = (i) + ""; if (() > max) { return i; } } return NULL_INDEX; } //Get the point in the screen at the end of the slide private int middleIndex(int size) { if (size % 2 == 0) { return size / 2; } else { return size / 2 + 1; } } /** * Get a point in the middle based on the coordinates of two points * * @param from coordinate 1 * @param to Coordinate 2 */ //Get the slope of the known point y = kx+b private float getSlope(Point from, Point to) { float k = ( - ) / ( - ); ("Point", "Parameter b:" + k); return k; } //Get parameter b private float getParams(Point from, Point to) { float b = - (getSlope(from, to) * ); ("Point", "Parameter b:" + b); return b; } //Get any coordinate x value of the x axis based on the coordinates between two points, private float getArbitrarilyX(Point from, Point to, int grade, int needGrade) { //Get the new coordinates of the input float x = (( - ) * needGrade) / grade + ; ("Point", "x coordinate value:" + x); return x; } //Get coordinate value private Point getPoint(Point from, Point to, int grade, int needGrade) { Point point = new Point(); (getArbitrarilyX(from, to, grade, needGrade)); float slope = getSlope(from, to); (slope * + getParams(from, to)); return point; } //Get the point where the polyline is drawn private Point getSpinnerPoint(int valueIndex) { float x = mXInit + interval * (valueIndex); float y = mYOri - mYOri * (1 - 0.1f) * ((valueIndex)) / (() - 1); return new Point(x, y); } private class Point { float x; float y; public Point() { } public float getX() { return x; } public void setX(float x) { = x; } public float getY() { return y; } public void setY(float y) { = y; } public Point(float x, float y) { = x; = y; } @Override public String toString() { return "Point{" + "x=" + x + ", y=" + y + '}'; } } }
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.