First, the reason for writing this article

On the lot at present there are multiple framework about the chart, such as MPAndroidChart is very good, but is very big, there is no need for a small icon to expand many of the project, the other lightweight framework, but the individual feels it is difficult to meet the needs of themselves, and even a good framework, it is others, only to write myself, In addition, in the process of writing, we can find out more details, such as the problem of memory allocation during drawing, Canvas drawing directly or through Bitmap drawing, etc., so the purpose of this article is as follows:

  • 1 is to provide you with the idea of custom view drawing
  • 2. Slide custom view part of the area how to achieve
  • 3. Realization of path animation drawing
  • 4. Familiar with Canvas API, in short, can directly start, then custom view clearance, so I write this article mainly to encourage everyone to implement more.

Two. The realization of the effect diagram

Linear icon

Three. Linear chart implementation ideas:

As the width of the screen is limited, it is better to display 7 points on one screen after calculation. Therefore, we need to calculate the width of our view first, get the width of the screen first, then perform /7 to get the width of each interval, and then multiply by the number of coordinate points of our x. The onMeasure method is as follows:

int widthParentMeasureMode = MeasureSpec.getMode(widthMeasureSpec); int widthParentMeasureSize = MeasureSpec.getSize(widthMeasureSpec); int heightParentMeasureMode = MeasureSpec.getMode(heightMeasureSpec); int heightParentMeasureSize = MeasureSpec.getSize(heightMeasureSpec); int resultWidthSize = 0; int resultHeightSize = 0; int resultWidthMode = MeasureSpec.EXACTLY; // Int resultHeightMode = MeasureSpec.EXACTLY; int paddingWidth = getPaddingLeft() + getPaddingRight(); int paddingHeight = getPaddingTop() + getPaddingBottom(); ViewGroup.LayoutParams thisLp = getLayoutParams(); Switch (widthParentMeasureMode) {// The parent class is not restricted to the child classcaseMeasureSpec.UNSPECIFIED: // This represents the width of the layout written outif (thisLp.width > 0) {
                    resultWidthSize = thisLp.width;
                    resultWidthMode = MeasureSpec.EXACTLY;
                } else {
                    resultWidthSize = (int) (getYMaxTextWidth() + mXinterval * mXdots.length);
                    resultWidthMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            caseMeasureSpec.AT_MOST: // This represents the width written in the layoutif (thisLp.width > 0) {
                    resultWidthSize = thisLp.width;
                    resultWidthMode = MeasureSpec.EXACTLY;
                } else if (thisLp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
                    resultWidthSize = Math.max(0, widthParentMeasureSize - paddingWidth);
                    resultWidthMode = MeasureSpec.AT_MOST;
                } else if (thisLp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    resultWidthSize = (int) (getYMaxTextWidth() + mXinterval * mXdots.length);
                    resultWidthMode = MeasureSpec.AT_MOST;
                }
                break;
            caseMeasureSpec.EXACTLY: // This represents the width in the layoutif (thisLp.width > 0) {
                    resultWidthSize = Math.min(widthParentMeasureSize, thisLp.width);
                    resultWidthMode = MeasureSpec.EXACTLY;
                } else if (thisLp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
                    resultWidthSize = widthParentMeasureSize;
                    resultWidthMode = MeasureSpec.EXACTLY;
                } else if (thisLp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    resultWidthSize = (int) (getYMaxTextWidth() + mXinterval * mXdots.length);
                    resultWidthMode = MeasureSpec.AT_MOST;
                }
                break; } Switch (heightParentMeasureMode) {// The parent view is unlimitedcaseMeasureSpec.UNSPECIFIED: // This represents the width of the layout written outif (thisLp.height > 0) {
                    resultHeightSize = thisLp.height;
                    resultHeightMode = MeasureSpec.EXACTLY;
                } else {
                    resultHeightSize = (int) (getYMaxTextHeight() + mYvisibleNum * mYinterval + getXMaxTextHeight());
                    resultHeightMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            case MeasureSpec.AT_MOST:
                if (thisLp.height > 0) {
                    resultHeightSize = heightParentMeasureSize;
                    resultHeightMode = MeasureSpec.EXACTLY;
                } else if (thisLp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
                    resultHeightSize = Math.max(0, heightParentMeasureSize - paddingHeight);
                    resultHeightMode = MeasureSpec.AT_MOST;
                } else if (thisLp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    resultHeightSize = (int) (getYMaxTextHeight() + mYvisibleNum * mYinterval + getXMaxTextHeight());
                    resultHeightMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            caseMeasureSpec.EXACTLY: // This represents the width in the layoutif (thisLp.height > 0) {
                    resultHeightSize = Math.min(heightParentMeasureSize, getMeasuredWidth());
                    resultHeightMode = MeasureSpec.EXACTLY;
                } else if (thisLp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
                    resultHeightSize = heightParentMeasureSize;
                    resultHeightMode = MeasureSpec.EXACTLY;
                } else if (thisLp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    resultHeightSize = (int) (getYMaxTextHeight() + mYvisibleNum * mYinterval + getXMaxTextHeight());
                    resultHeightMode = MeasureSpec.AT_MOST;
                }
                break;
        }

        setMeasuredDimension(MeasureSpec.makeMeasureSpec(resultWidthSize, resultWidthMode),
                MeasureSpec.makeMeasureSpec(resultHeightSize, resultHeightMode));Copy the code

Set size, we can draw the interface, here we ontouch, have, in turn, draw the horizontal and vertical lines, at the time of draw horizontal lines, the Y coordinate of digital drawing up together, in the same way to draw a vertical bar, the x coordinate of the digital map, line drawings according to the number to calculate the coordinates of points, and then create a path, First moveTo(firstX,firstY), then the point below the lineTo is ok, and finally draw the path, however, when we swipe, we will find that the view will scroll with us, so how can we achieve the view part pinned? In this case, we need to create a bitmap and draw the sliding part onto this bitmap. Then, when the bitmap is drawn on the canvas, it will keep its fixed position.

        float tempTableLeftPadding = getYMaxTextWidth();
        if(mBitmap == null || mYNumCanvas == null) { mBitmap = Bitmap.createBitmap((int) (getMeasuredWidth() - getYMaxTextWidth()), getMeasuredHeight(), Bitmap.Config.ARGB_8888); mYNumCanvas = new Canvas(mBitmap); } mYNumCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); mYNumCanvas.translate(mScrollPosX,0); // This code is to achieve the sliding operation // draw a linefor (int y = 0, size = mYdots.length; y < size; y++) {
            String tempText = String.valueOf(mYdots[mYdots.length - 1 - y]);
            mYNumCanvas.drawLine(0, (float) (mYinterval * y), (float) (mXdots.length * mXinterval), (float) (mYinterval * y), mXlinePaint);
            canvas.drawText(tempText, getYMaxTextWidth() - mYNumPaint.measureText(tempText), getYMaxTextHeight() + (float) (mYinterval * y), mYNumPaint); } // Draw vertical linesfor (int x = 0, size = mXdots.length; x <= size; x++) {
            mYNumCanvas.drawLine((float) (mXinterval * x), 0, (float) (mXinterval * x), (float) (mYinterval * mYvisibleNum), mXlinePaint);
            if (x >= 1) {
                String tempText = mXdots[x - 1];
                mYNumCanvas.drawText(tempText, (float) (mXinterval * x) - mYNumPaint.measureText(tempText) / 2, (float) (mYvisibleNum * mYinterval + getYMaxTextHeight()), mYNumPaint); }}ifDrawPath (mLineDrawPath, mLinePaint); drawPath(mLineDrawPath, mLinePaint);else
            mYNumCanvas.drawPath(mLinePath, mLinePaint);
        canvas.drawBitmap(mBitmap, tempTableLeftPadding, getYMaxTextHeight() / 2, null);Copy the code

The above mScrollPosX is obtained from the GestureDetector class:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(! isAnimationOpen || isDrawOver)return mGestureDetector.onTouchEvent(event);
        return super.onTouchEvent(event);
    }Copy the code

The animation effect is right. Here we use the method of animation through path drawing, which is to obtain the length of path through PathMeasure, and then calculate its coordinates at a certain time according to the animation time through ValueAnimator. Then redraw path:

    private void startPathAnim(long duration) {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mLineLength);
        valueAnimator.setDuration(duration);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                floatvalue = (Float) animation.getAnimatedValue(); MCurrentPosition mPathMeasure.getPostAn (value, mCurrentPosition, null); mLineDrawPath.lineTo(mCurrentPosition[0], mCurrentPosition[1]); invalidate(); }}); valueAnimator.start(); }Copy the code

Four. Percentage circle chart implementation

Percentage chart



In fact, this implementation, compared to the previous one is much less, mostly concentrated in the onDraw method inside, the key point is in the percentage of the number, how to display in the sector area horizontally, here I will put forward the main calculation rules:

 private void drawText(Canvas canvas, float sweepAngle, float startAngle, ArcVo temp) {
        float middleAngle;
        middleAngle = startAngle + sweepAngle / 2;
        float startX;
        float startY;
        float endX;
        float endY;
        String drawText = temp.getPercentInCircle() * 100 + "%";
        if(middleAngle <= 90) {// Double Angle = middleAngle; angle = Math.toRadians(angle); startY = endY = (float) (Math.sin(angle) * mRaduis + mRaduis);
            endX = (float) (mRaduis + Math.cos(angle) * mRaduis);
            startX = endX - UiUtils.getTextWidth(drawText, mTextPaint);
        } else if(middleAngle <= 180) {// Double Angle = 180-middleangle; angle = Math.toRadians(angle); startY = endY = (float) (Math.sin(angle) * mRaduis + mRaduis);
            startX = (float) (mRaduis - Math.cos(angle) * mRaduis);
            endX = startX + UiUtils.getTextWidth(drawText, mTextPaint);
        } else if(middleAngle <= 270) {// Double Angle = 270-middleangle; angle = Math.toRadians(angle); startY = endY = (float) (mRaduis - Math.cos(angle) * mRaduis);
            startX = (float) (mRaduis - Math.sin(angle) * mRaduis);
            endX = startX + UiUtils.getTextWidth(drawText, mTextPaint);
        } else{// Double Angle = 360-middleangle; angle = Math.toRadians(angle); startY = endY = (float) (mRaduis - Math.sin(angle) * mRaduis);
            endX = (float) (mRaduis + Math.cos(angle) * mRaduis);
            startX = endX - UiUtils.getTextWidth(drawText, mTextPaint);
        }

        mTextPath.reset();
        mTextPath.moveTo(startX, startY);
        mTextPath.lineTo(endX, endY);
        if (middleAngle > 180) {
            canvas.drawTextOnPath(drawText, mTextPath, 0, UiUtils.getTextHeight(drawText, mTextPaint), mTextPaint);
        } else {
            canvas.drawTextOnPath(drawText, mTextPath, 0, -UiUtils.getTextHeight(drawText, mTextPaint), mTextPaint);

        }
    }

     @Override
    protected void onDraw(Canvas canvas) {
        if(! canDraw())return;
        float sweepAngle;
        float startAngle = 0;
        for (int i = 0, size = mDisArcList.size(); i < size; i++) {
            ArcVo temp = mDisArcList.get(i);
            mArcPaint.setColor(temp.getScanColor());
            sweepAngle = temp.getPercentInCircle() * 360;
            canvas.drawArc(mDrawCircleRect, startAngle, sweepAngle, true, mArcPaint); drawText(canvas, sweepAngle, startAngle, temp); startAngle = startAngle + sweepAngle; }}Copy the code

Five. Use mode:

If you think your project just want to use a similar icon, in the project of gradle file, add the compile ‘wellijohn.org.simplelinechart:linechart:0.0.2’ specific methods, welcome look set to making up, It has been packaged into a library and uploaded to jCenter with specific usage methods (chart address). There are not many methods exposed at present, you can leave a message and add github address: github.com/WelliJohn/L…) If you think the project has some inspiration for your custom view, please help star, if you have a better implementation scheme, welcome to leave a message!!