自定义View:画布实现自定义View(折线图的实现)

来源:互联网 发布:遇见聊天软件 编辑:程序博客网 时间:2024/06/01 11:30

今天道长打算说一下用画布实现自定义View,这是道长说的自定义View的第四种实现方式了。
第一种:是放好布局后使用NineOldAndroid监听动画实现,想看一下的话点击传送门属性动画(二):如何自定义View以及自定义View:侧滑菜单动画实现。
第二种:是放好布局后使用TouchEvent监听实现,传送门在此自定义View:侧滑菜单实现。
第三种:是继承相关的View,拓展相关View的功能,传送门在此PopWindow:基本使用与自定义PopWindow。
第三种自定义View就是拓展相关View的功能。比如自定义PopWindow要增加出现动画或者展示方式。前两种都是使用已经存在的布局,一种继承FrameLayout,另一种继承ViewGroup。放置好位置后监听事件实现。应该说前两种是自定义组合View。今天说的这种方式继承View,可以用画布绘制各种形状的图形,然后监听事件实现。这里以折线图的实现为例,折线图可以左右滑动。好了咱们开车……

一、效果图

动态图没有,先把效果图放在这里,然后绘制View。
这里写图片描述

二、绘制View

上面的效果图都看到折线图有网格,有横向限制区域,有标记点,有目标点,有坐标轴单位,Y轴分割为两个区域,还可以左右滑动。
把画布Canvas与生活中的纸张看成一样就可以了,要知道咱们在纸张上写东西时先写的会被后写的遮盖住。所以说绘制View时要注意分层。

  • 构造函数,初始化画布,画笔
    public CanvasView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context);    }    public CanvasView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public CanvasView(Context context) {        this(context, null);    }private void init(Context context) {        mTextColorSize = sp2px(context, mTextColorSize);        mTextColorSmall = sp2px(context, mTextColorSmall);        mTrendLineSize = dp2px(context, mTrendLineSize);        mInnerCircleSize = (int) dp2px(context, mInnerCircleSize);        mOuterCircleSize = (int) dp2px(context, mOuterCircleSize);        mOuterCircleRadius = (int) dp2px(context, mOuterCircleRadius);        mInnerCircleRadius = (int) dp2px(context, mInnerCircleRadius);        mYCenterSize = (int) dp2px(context, mYCenterSize);        focusTextSize = (int) dp2px(context, focusTextSize);        mPaint = new Paint();        mPaint.setTextAlign(Align.CENTER);        mPaint.setStyle(Style.STROKE);        mPaint.setAntiAlias(true);        mInnerCirclePaint = new Paint();        mInnerCirclePaint.setTextAlign(Align.CENTER);        mInnerCirclePaint.setColor(mInnerCircleColor);        mInnerCirclePaint.setTextSize(mInnerCircleSize);        mInnerCirclePaint.setAntiAlias(true);        mInnerCirclePaint.setTextSize(mInnerCircleSize);        mOuterCirclePaint = new Paint();        mOuterCirclePaint.setTextAlign(Align.CENTER);        mOuterCirclePaint.setTextSize(mOuterCircleSize);        mOuterCirclePaint.setAntiAlias(true);        mOuterCirclePaint.setTextSize(mOuterCircleSize);        mTitlePaint = new Paint();        mTitlePaint.setTextAlign(Align.CENTER);        mTitlePaint.setTextSize(sp2px(context, 20));        mTitlePaint.setTextAlign(Align.CENTER);        mRangeTrendBackgroundPaint = new Paint();        nRangeTrendBackgroundPaint = new Paint();        mPulsePaint = new Paint();        final ViewConfiguration configuration = ViewConfiguration.get(context);        mTouchSlop = configuration.getScaledTouchSlop();        mYTitleRect = new Rect();        nYTitleRect = new Rect();        pYTitleRect = new Rect();        mPointColors = new int[]{0xFF349800, 0xFF0082b4};    // 画笔的颜色        mYTitleWidth = (int) dp2px(context, mYTitleWidth);        mRangeTrendColors = new int[]{0XFFDBF9CC, 0XFFDBF9CC, 0XFFDBF9CC};        nRangeTrendColors = new int[]{0XFFE0F6FF, 0XFFE0F6FF, 0XFFE0F6FF, 0XFFE0F6FF};        mPulseColors = new int[]{0XFFFFFBE4, 0XFFFFFBE4, 0XFFFFFBE4};    }
  • 第一层绘制折线图限制区域
    /**     * 设置界限的区域的     *     * @param canvas     * @param paint     * @param rangeTrendColors     * @param up     * @param down     */    private void drawBackground(Canvas canvas, Paint paint, int[] rangeTrendColors, float up, float down) {        Map<String, Object> params = getViewParams();        Rect rect = new Rect();        rect.set((int) ((Float) params.get("scrollX") + 0), (int) ((Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") * up), (int) ((Float) params.get("scrollX") + getWidth()), (int) (getHeight() - (Integer) params.get("xAxisTitleHeight") - (Integer) params.get("trendHeight") * down));        LinearGradient gradient = new LinearGradient((Float) params.get("scrollX") + getWidth(), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") * up, (Float) params.get("scrollX") + getWidth(), getHeight() - (Integer) params.get("xAxisTitleHeight") - (Integer) params.get("trendHeight") * down, rangeTrendColors, null, Shader.TileMode.CLAMP);        paint.setShader(gradient);        canvas.drawRect(rect, paint);        canvas.save();    }



效果图如下:
这里写图片描述

  • 第二层绘制表格
 /**     * 绘制表格     *     * @param canvas     * @param paint     */    private void drawForm(Canvas canvas, Paint paint) {        for (int i = 0; i < mDrawCount; i++) {            drawColumnLine(canvas, paint, 0xffd5edff, (int) mTextColorSize, i);        }        for (int i = 1; i <= mDrawCount + 1; i++) {            if (i == 1 || i == 5 || i == 6 || i == 8) {                drawRowLine(canvas, paint, 0xff7ecef9, 1, i);            } else {                drawRowLine(canvas, paint, 0xffe5e5e5, 1, i);            }        }    }    /**     * 绘制表格竖线     *     * @param canvas     * @param paint     * @param lineColor     * @param lineWith     * @param position     */    private void drawColumnLine(Canvas canvas, Paint paint, int lineColor, int lineWith, int position) {        Map<String, Object> params = getViewParams();        paint.setColor(lineColor);        paint.setTextSize(lineWith);        canvas.drawLine((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight"), (Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1), paint);    }    /**     * 绘制表格横线     *     * @param canvas     * @param paint     * @param lineColor     * @param lineWith     * @param position     */    private void drawRowLine(Canvas canvas, Paint paint, int lineColor, int lineWith, int position) {        Map<String, Object> params = getViewParams();        paint.setColor(lineColor);        paint.setStrokeWidth(lineWith);        canvas.drawLine((Float) params.get("scrollX") + mYTitleWidth - 20, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (position - 1), (Float) params.get("scrollX") + getWidth(), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (position - 1), paint);    }



效果图如下:
这里写图片描述

  • 第三层绘制网格分割区域
    /**     * 分割网格     *     * @param canvas     */    private void setSplitForm(Canvas canvas, Paint paint, int color, int width) {        Map<String, Object> params = getViewParams();        paint.setColor(color);        paint.setStrokeWidth(width);        canvas.drawRect((Float) params.get("scrollX") + 0, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (5 - 1) + 1, (Float) params.get("scrollX") + getWidth(), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (6 - 1), paint);// 长方形    }
  • 第四层绘制中心标记
    /**     * 绘制中心标志     *     * @param canvas     * @param paint     * @param position     */    private void drawCenterSign(Canvas canvas, Paint paint, int position) {        drawCenterLine(canvas, paint, position);        drawTriangle(canvas, paint, position);    }    /**     * 绘制中心线     *     * @param canvas     * @param paint     * @param position     */    private void drawCenterLine(Canvas canvas, Paint paint, int position) {        Map<String, Object> params = getViewParams();        paint.setColor(mCenterColor);  // 修改中心竖线颜色        paint.setStrokeWidth(mYCenterSize);        canvas.drawLine((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight"), (Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1) - 20, paint);    }    /**     * 画三角形     *     * @param canvas     * @param paint     * @param position     */    public void drawTriangle(Canvas canvas, Paint paint, int position) {        Map<String, Object> params = getViewParams();        paint.setStyle(Style.STROKE);        paint.setStrokeWidth(2);        paint.setColor(0xff7ecef9);        Path path = new Path();        path.reset();        path.moveTo((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position), (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1) - 20);// 开始坐标 也就是三角形的顶点        path.lineTo((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position) - 20, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1));        path.lineTo((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position) + 20, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1));        path.close();        canvas.drawPath(path, paint);        // 去掉底边        mTitlePaint.setColor(Color.WHITE);        mTitlePaint.setStrokeWidth(3);        canvas.drawLine((Float) params.get("scrollX") + mYTitleWidth + mDistance * (position) - 19, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1), (Float) params.get("scrollX") + mYTitleWidth + mDistance * (position) + 19, (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1), mTitlePaint);    }



效果图如下:
这里写图片描述

  • 第五层绘制折线
    /**     * 绘制转折线     *     * @param canvas     * @param paint     * @param canvasLine     * @param color     */    private void drawCanvasLine(Canvas canvas, Paint paint, List<Integer> canvasLine, int color) {        Path mPath = new Path();  // 绘制趋势图对于的Path对象        ArrayList<Integer> LinePosition = dealCanvasData(canvasLine);        Map<String, Object> params = getViewParams();        int startPosition = LinePosition.get(0);        int endPosition = LinePosition.get(1);        paint.setColor(color);        // draw trend        if (endPosition > startPosition && endPosition > 0) {            for (int i = startPosition; i < endPosition; i++) {                int currentY = (int) ((Integer) params.get("xAxisHeadHeight") + (mMaxHeight - canvasLine.get(i)) / mScaleValue * ((Integer) params.get("trendHeight") / 8));                // 处理为负的数据,不需要可以屏蔽                if (canvasLine.get(i) < 0) {                    double Y = canvasLine.get(i) * 32.0 / 40.0;                    currentY = (int) ((Integer) params.get("xAxisHeadHeight") + (mMaxHeight - Y) / scaleValue * ((Integer) params.get("trendHeight") / 8));                }                if (i == startPosition) {                    mPath.moveTo(i * mDistance, currentY);                } else {                    mPath.lineTo(i * mDistance, currentY);                }            }        }        canvas.drawPath(mPath, paint);        canvas.save();        mPath.reset();    }



效果图如下:
这里写图片描述

  • 第六层绘制圆点
    /**     * 绘制圆     *     * @param canvas     * @param paint     * @param canvasLine     * @param color     * @param condition     */    private void drawCircles(Canvas canvas, Paint paint, List<Integer> canvasLine, int color, ArrayList<Integer> condition) {        ArrayList<Integer> LinePosition = dealCanvasData(canvasLine);        int startPosition = LinePosition.get(0);        int endPosition = LinePosition.get(1);        Map<String, Object> params = getViewParams();        mOuterCirclePaint.setStrokeWidth(mTrendLineSize);        mInnerCirclePaint.setStrokeWidth(mTrendLineSize);        mOuterCirclePaint.setColor(color);        if (endPosition > startPosition && endPosition > 0) {            for (int i = startPosition; i < endPosition; i++) {                int currentY = (int) ((Integer) params.get("xAxisHeadHeight") + (mMaxHeight - canvasLine.get(i)) / mScaleValue * ((Integer) params.get("trendHeight") / 8));                // 处理为负的数据,不需要可以屏蔽                if (canvasLine.get(i) < 0) {                    double Y = canvasLine.get(i) * 32.0 / 40.0;                    currentY = (int) ((Integer) params.get("xAxisHeadHeight") + (mMaxHeight - Y) / scaleValue * ((Integer) params.get("trendHeight") / 8));                }                if (canvasLine.get(i) > condition.get(1) || canvasLine.get(i) < condition.get(0)) {                    drawCircle(canvas, i * mDistance, currentY);  // 实心                } else {                    // 下面需要对 90~140的数据处理                    drawCirque(canvas, i * mDistance, currentY);  // 空心                }            }        }    }    /**     * 绘制实心圆     *     * @param canvas     * @param positionX     * @param positionY     */    private void drawCircle(Canvas canvas, int positionX, int positionY) {        canvas.drawCircle(positionX, positionY, mOuterCircleRadius, mOuterCirclePaint);    }    /**     * 绘制空心圆     *     * @param canvas     * @param positionX     * @param positionY     */    private void drawCirque(Canvas canvas, int positionX, int positionY) {        canvas.drawCircle(positionX, positionY, mOuterCircleRadius, mOuterCirclePaint);        canvas.drawCircle(positionX, positionY, mInnerCircleRadius, mInnerCirclePaint);    }



效果图如下:
这里写图片描述

  • 第七层绘制X轴Title文字
    /**     * 绘制x轴Title文字     *     * @param canvas     * @param paint     * @param data     */    private void drawXTitle(Canvas canvas, Paint paint, List<String[]> data) {        paint.setColor(0xff888888);        paint.setTextSize(mTextColorSize);        paint.setStyle(Style.FILL);        List<Integer> maxItem = getMaxItem();        Map<String, Object> params = getViewParams();        int startPosition = ((Integer) params.get("firstPosition") - (Integer) params.get("offsetCount")) >= 0 ? ((Integer) params.get("firstPosition") - (Integer) params.get("offsetCount")) : 0;        int endPosition = maxItem.size();        if (endPosition > startPosition && endPosition > 0) {            float textBaseY_x_up = (Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (8 - 1) + 40;            for (int i = startPosition; i < endPosition; i++) {                drawCenterTextColor(i);                // draw x axis up                mYTitleRect.set(mDistance * (i - 1), (getHeight() - (Integer) params.get("xAxisHeight_up") - (Integer) params.get("xAxisHeight_blow") - 10), mDistance * (i + 1), getHeight() - (Integer) params.get("xAxisHeight_blow") - 10);                canvas.drawText(data.get(i)[0].toString(), mYTitleRect.centerX(), textBaseY_x_up, paint);            }        }    }
  • 第八层绘制Y轴Title文字
    /**     * 绘制Y轴Title文字     *     * @param canvas     * @param paint     * @param backgroundColor     * @param textColor     */    private void drawYTitle(Canvas canvas, Paint paint, int backgroundColor, int textColor) {        Map<String, Object> params = getViewParams();        FontMetrics fontMetrics = mPaint.getFontMetrics();        float fontHeight = fontMetrics.bottom - fontMetrics.top;        // 由于折线图是左右贯通的,Y轴Title在画布上会造成显示混乱,所以添加底部遮挡        paint.setColor(backgroundColor);        paint.setStyle(Style.FILL);        mYTitleRect.set((int) ((Float) params.get("scrollX") + 0), 0, (int) ((Float) params.get("scrollX") + mYTitleWidth) - 30, getHeight() - 80);        canvas.drawRect(mYTitleRect, paint);        // and y-axis values        paint.setColor(textColor);        paint.setTextSize(mTextColorSize);        //绘制Y轴值        for (int i = 0; i <= 8; i++) {            String showTitle;            if (i >= 6 && i <= 8) {                showTitle = 40 * (6 - i) + 120 + "";            } else {                showTitle = 40 + (5 - i) * 35 + "";            }            float textBaseY = ((Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (i - 1)) * 2 - (((Integer) params.get("xAxisHeadHeight") + (Integer) params.get("trendHeight") / 8 * (i - 1)) * 2 - fontHeight) / 2 - fontMetrics.bottom;            canvas.drawText(showTitle, ((Float) params.get("scrollX") + mYTitleWidth / 2) - 20, textBaseY, paint);        }    }
  • 在onDraw中添加绘制View代码
    @Override    protected void onDraw(Canvas canvas) {        mDistance = (getWidth() - mYTitleWidth) / mDrawCount;        currentCenter = (getWidth() - mDistance);        if (mCenterPosition == -1) {            int positionLocal = mCenterRecorded * mDistance;            scrollTo(positionLocal - currentCenter, 0); // 根据可显示的区域 动态计算中点            mCenterPosition = 0;        }        // 设置字体、笔画宽度//        mTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);//        mTitlePaint.setStrokeWidth(4);        // draw trend background        // 设置dataOne界限的区域        drawBackground(canvas, mRangeTrendBackgroundPaint, mRangeTrendColors, RatioUp, RatioDown);        // 设置dataTwo界限的区域        drawBackground(canvas, nRangeTrendBackgroundPaint, nRangeTrendColors, colorUp, colorDown);        // 设置dataThree界限的区域        drawBackground(canvas, mPulsePaint, mPulseColors, mPulseUp, mPulseDown);        // draw form        drawForm(canvas, mTitlePaint);        // split form        setSplitForm(canvas, mTitlePaint, Color.WHITE, 2);        // draw sign        drawCenterSign(canvas, mTitlePaint, 6);        if (mPoints != null) {            // draw canvas line            drawCanvasLine(canvas, mPaint, mPoints[0], mPointColors[0]);            drawCanvasLine(canvas, mPaint, mPoints[1], mPointColors[1]);            // draw circles            ArrayList<Integer> conditionsOne = new ArrayList<>();            conditionsOne.add(90);            conditionsOne.add(140);            ArrayList<Integer> conditionsTwo = new ArrayList<>();            conditionsTwo.add(60);            conditionsTwo.add(90);            drawCircles(canvas, mPaint, mPoints[0], mPointColors[0], conditionsOne);            drawCircles(canvas, mPaint, mPoints[1], mPointColors[1], conditionsTwo);        }        if (mData != null) {            // draw canvas line            drawCanvasLine(canvas, mPaint, mData[0], mPulseColor);            // draw circles            ArrayList<Integer> conditionsThree = new ArrayList<>();            conditionsThree.add(-70);            conditionsThree.add(-10);            drawCircles(canvas, mPaint, mData[0], mPulseColor, conditionsThree);        }        //and x-axis values        if (mXAxisValues != null && mXAxisValues.size() > 0) {            drawXTitle(canvas, mTitlePaint, mXAxisValues);        }        // draw y r xis rect        drawYTitle(canvas, mTitlePaint, Color.WHITE, 0xff888888);    }



效果图如下:
这里写图片描述

现在咱们的界面绘制完成,要记住顺序,不然会遮挡。然后咱们实现监听。

二、监听事件,实现逻辑

  • 实现onTouchEvent监听事件逻辑
    @Override    public boolean onTouchEvent(MotionEvent event) {        int action = event.getAction();        switch (action) {            case MotionEvent.ACTION_DOWN:                oldX = (int) event.getX();                if ((mIsBeingDragged)) {                    final ViewParent parent = getParent();                    if (parent != null) {                        parent.requestDisallowInterceptTouchEvent(true);                    }                }                invalidate();                mActivePointerId = event.getPointerId(0);                return true;            case MotionEvent.ACTION_MOVE:                final int activePointerIndex = event.findPointerIndex(mActivePointerId);                if (activePointerIndex == -1) {                    break;                }                final int x = (int) event.getX(activePointerIndex);                int deltaX = oldX - x;                if (!mIsBeingDragged && Math.abs(deltaX) > mTouchSlop) {                    final ViewParent parent = getParent();                    if (parent != null) {                        parent.requestDisallowInterceptTouchEvent(true);                    }                    mIsBeingDragged = true;                    if (deltaX > 0) {                        deltaX -= mTouchSlop;                    } else {                        deltaX += mTouchSlop;                    }                }                // HorizontalScrollView                if (mIsBeingDragged && Math.abs(deltaX) > mTouchSlop) {                    oldX = x;                    mTowards = deltaX;                    scrollBy(deltaX, 0);                    invalidate();                    if (mPCenterListener != null) {                        int nextCenter = getToNextCenter();                        mPCenterListener.passCenter(nextCenter);                    }                }                invalidate();                return true;            default:                if (mIsBeingDragged) {                    mIsBeingDragged = false;                    int nextCenter = getToNextCenter();                    mTowards = 0;                    int halfWidth = currentCenter;                    int positionLocal = nextCenter * mDistance;                    scrollTo(positionLocal - halfWidth, 0);                    if (mAEndListener != null) {                        mAEndListener.actionEnd(nextCenter);                        invalidate();                        centerPosition = nextCenter;                    }                } else {                }                invalidate();                return true;        }        return super.onTouchEvent(event);    }

在监听事件中不只有左右滑动监听,还添加了滑动过中心的监听。这里道长不多说了,这篇博客还是这么干巴巴的,以后道长可能只贴代码了[手动滑稽]。有不明白的地方看Demo。现在自定义View,自定义组合View道长都说了,怎么会不说一下自定义属性,这个在自定义View中也是很常用的。咱们在开一篇博客,画布实现自定义View暂时到这里,希望这篇博客能为你提供一些帮助。

源码下载

CanvasDemo


0 0