Android 自定义View之绘制折线图、曲线图

    上一篇文章根据之前大学同学的一个需求绘制了一个矩形渐变对比效果图,这篇文章给大家分享一个绘制折线图和曲线图的案列。效果如下:



我们知道在安卓中我们可以将手机的屏幕看做一个坐标系,


以屏幕左上方为坐标原点(0,0)。绘制折线图和曲线图,就是在坐标系上按照我们的需求,将不同的点连接起来。下面首先介绍下需要用到的几个方法:

drawLine(float startX , float startY , float stopX , float stopY , Paint paint  );

/**
 * Draw a line segment with the specified start and stop x,y coordinates,
 * using the specified paint.
 *
 * <p>Note that since a line is always "framed", the Style is ignored in the paint.</p>
 *
 * <p>Degenerate lines (length is 0) will not be drawn.</p>
 *
 * @param startX The x-coordinate of the start point of the line
 * @param startY The y-coordinate of the start point of the line
 * @param paint  The paint used to draw the line
 */
public void drawLine(float startX, float startY, float stopX, float stopY,
        @NonNull Paint paint) {
    native_drawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
}

对应参数分别表示在X轴方向的起点坐标,Y轴方向的起点坐标,X轴方向的终点坐标,Y轴方向的终点坐标,画笔对象。


drawText(String text , float x , float y , Paint paint);

/**
 * Draw the text, with origin at (x,y), using the specified paint. The
 * origin is interpreted based on the Align setting in the paint.
 *
 * @param text  The text to be drawn
 * @param x     The x-coordinate of the origin of the text being drawn
 * @param y     The y-coordinate of the baseline of the text being drawn
 * @param paint The paint used for the text (e.g. color, size, style)
 */
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
    native_drawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
            paint.getNativeInstance(), paint.mNativeTypeface);
}

对应参数分别表示,绘制的文本内容,绘制起点的X轴坐标,绘制起点的Y轴坐标。画笔对象。


然后还要介绍下android.graphics包下的Path类中用到的几个方法,具体细节这里就不在赘述了,这里我只说明下用的几个方法。


Path.moveTo(float x , float y);

/**
 * Set the beginning of the next contour to the point (x,y).
 *
 * @param x The x-coordinate of the start of a new contour
 * @param y The y-coordinate of the start of a new contour
 */
public void moveTo(float x, float y) {
    native_moveTo(mNativePath, x, y);
}

对应参数分别表示,在X轴方向的起始坐标,在Y轴方向的起始坐标。


path.lineTo(float x , float y);

/**
 * Add a line from the last point to the specified point (x,y).
 * If no moveTo() call has been made for this contour, the first point is
 * automatically set to (0,0).
 *
 * @param x The x-coordinate of the end of a line
 * @param y The y-coordinate of the end of a line
 */
public void lineTo(float x, float y) {
    isSimplePath = false;
    native_lineTo(mNativePath, x, y);
}
对应参数分别表示,路径终点在X轴方向的坐标,路径终点在Y轴方向的坐标;未设置起点,默认起点为(0,0),两点一线,这个大家都是明白的。


Path.cubicTo(float x1 , float y1 , float x2 , float y2 , float x3 , float y3);

/**
 * Add a cubic bezier from the last point, approaching control points
 * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
 * made for this contour, the first point is automatically set to (0,0).
 *
 * @param x1 The x-coordinate of the 1st control point on a cubic curve
 * @param y1 The y-coordinate of the 1st control point on a cubic curve
 * @param x2 The x-coordinate of the 2nd control point on a cubic curve
 * @param y2 The y-coordinate of the 2nd control point on a cubic curve
 * @param x3 The x-coordinate of the end point on a cubic curve
 * @param y3 The y-coordinate of the end point on a cubic curve
 */
public void cubicTo(float x1, float y1, float x2, float y2,
                    float x3, float y3) {
    isSimplePath = false;
    native_cubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
}


对应的参数分别表示,第一个点在X、Y轴方向的坐标,第二个点在X、Y轴方向的坐标,第三个点在X、Y轴方向的坐标,这个方法用来绘制贝赛尔曲线。Path类中还提供了一个类似的方法,也是用来绘制赛贝尔曲线的


Path.qudTo(float x1 , float y1 , float x2 , flaot y2);

/**
 * Add a quadratic bezier from the last point, approaching control point
 * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
 * this contour, the first point is automatically set to (0,0).
 *
 * @param x1 The x-coordinate of the control point on a quadratic curve
 * @param y1 The y-coordinate of the control point on a quadratic curve
 * @param x2 The x-coordinate of the end point on a quadratic curve
 * @param y2 The y-coordinate of the end point on a quadratic curve
 */
public void quadTo(float x1, float y1, float x2, float y2) {
    isSimplePath = false;
    native_quadTo(mNativePath, x1, y1, x2, y2);
}


两者的区别,官方是这么说的Same as cubicTo,but the coordinates are considered relative to the current point on this contour.字面上理解就是比cubicTo少了一个控制点。


Path.close();

/**
 * Close the current contour. If the current point is not equal to the
 * first point of the contour, a line segment is automatically added.
 */
public void close() {
    isSimplePath = false;
    native_close(mNativePath);
}

简单的理解这个方法就是,将当前path的终点和起点连接起来,构成一个封闭的图形。

用到的方法这里已经简单的介绍完了,下面就是具体的操作了,还是4个步骤;

1.在attrs.xml文件中申明我们的自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="LineView">
        <attr name="viewMargin" format="dimension" />
        <attr name="lineColor" format="color" />
        <attr name="shadowColor" format="color" />
        <attr name="lineTextSize" format="dimension" />
        <attr name="lineTextColor" format="color" />
    </declare-styleable>
</resources>

2.获取我们的自定义属性

3.重写onMeasure()方法,不一定需要实现

4.重写onDraw()方法


下面直接贴出源码:

public class LineView extends View {

    /**
     * View起点距离顶部和底部的距离
     */
    private int mViewMargin;
    /**
     * 网格线的颜色
     */
    private int mLineColor;
    /**
     * 阴影部分的颜色
     */
    private int mShadowColor;
    /**
     * 字体大小
     */
    private int mTextSize;
    /**
     * 字体颜色
     */
    private int mTextColor;
    /**
     * 纵坐标刻度
     */
    private List<String> mYList;
    /**
     * 横坐标刻度
     */
    private List<String> mXList;
    /**
     * 网格线的高度
     */
    private int mHeight;
    /**
     * 网格线距离左边的距离
     */
    private float mMarginLeft;

    private List<Point> mListPoint;
    private LineType mLineType = LineType.LINE;
    private Paint mPaint;

    public LineView(Context context) {
        this(context, null);
    }

    public LineView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LineView, defStyleAttr, 0);
        int count = array.getIndexCount();
        for (int i = 0; i < count; i++) {
            int index = array.getIndex(i);
            switch (index) {
                case R.styleable.LineView_viewMargin:
                    mViewMargin = array.getDimensionPixelSize(index, (int) TypedValue.
                            applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
                    break;
                case R.styleable.LineView_lineColor:
                    mLineColor = array.getColor(index, Color.BLACK);
                    break;
                case R.styleable.LineView_shadowColor:
                    mShadowColor = array.getColor(index, Color.BLACK);
                    break;
                case R.styleable.LineView_lineTextColor:
                    mTextColor = array.getColor(index, Color.BLACK);
                    break;
                case R.styleable.LineView_lineTextSize:
                    mTextSize = array.getDimensionPixelSize(index, (int) TypedValue.
                            applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
                    break;
            }
        }
        array.recycle();
        init();
    }

    private void init() {
        /**
         * 设置画笔的属性
         */
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(mLineColor);
        mPaint.setTextSize(mTextSize);
        mPaint.setAntiAlias(true);                    //取消锯齿
        mPaint.setStyle(Paint.Style.STROKE);          //设置画笔为空心
        mMarginLeft = (mViewMargin * 2);              //设置左边的偏移距离
        mPaint.setStrokeWidth(2);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mHeight == 0) {
            mHeight = getHeight() - mViewMargin * 2;
        }
        drawLine(canvas);
        drawXScale(canvas);
        drawYScale(canvas);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mTextColor);
        mPaint.setStrokeWidth(3);
        mPaint.setAntiAlias(true);
        mListPoint = getPointList();
        if (mLineType == LineType.ARC) {
            drawScrollLine(canvas);
        } else {
            drawLineView(canvas);
        }
        /**
         * 画阴影
         */
        if (mLineType == LineType.ARC) {
            Point pStart = new Point();
            Point pEnd = new Point();
            Path path = new Path();
            for (int i = 0; i < 5; i++) {
                pStart = mListPoint.get(i);
                pEnd = mListPoint.get(i + 1);
                Point point3 = new Point();
                Point point4 = new Point();
                float wd = (pStart.x + pEnd.x) / 2;
                point3.x = wd;
                point3.y = pStart.y;
                point4.x = wd;
                point4.y = pEnd.y;
                path.moveTo(pStart.x, pStart.y);
                path.cubicTo(point3.x, point3.y, point4.x, point4.y, pEnd.x, pEnd.y);
                path.lineTo(pEnd.x, getHeight() - mViewMargin);
                path.lineTo(pStart.x, getHeight() - mViewMargin);
            }
            path.close();
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mShadowColor);
            canvas.drawPath(path, mPaint);
        } else {
            Path path = new Path();
            path.moveTo(mListPoint.get(0).x, mListPoint.get(0).y);
            for (int i = 1; i < 6; i++) {
                path.lineTo(mListPoint.get(i).x, mListPoint.get(i).y);
            }
            /**
             * 链接最后两个点
             */
            int index = mListPoint.size() - 1;
            path.lineTo(mListPoint.get(index).x, getHeight() - mViewMargin);
            path.lineTo(mListPoint.get(0).x, getHeight() - mViewMargin);
            path.close();
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mShadowColor);
            canvas.drawPath(path, mPaint);
        }
    }

    /**
     * 绘制网格线
     */
    private void drawLine(Canvas canvas) {
        /**
         * 左边第一条竖线
         */
        canvas.drawLine(mMarginLeft, mViewMargin, mMarginLeft, getHeight() - mViewMargin, mPaint);

        /**
         * 5条水平的横线
         */
        for (int i = 0; i < mXList.size(); i++) {
            canvas.drawLine(mMarginLeft, mViewMargin + i * ((getHeight() - mViewMargin * 2) / 5), getWidth() - mViewMargin,
                    mViewMargin + i * ((getHeight() - mViewMargin * 2) / 5), mPaint);
        }

        /**
         * 右边最后一条竖线
         */
        canvas.drawLine(getWidth() - mViewMargin, mViewMargin,
                getWidth() - mViewMargin, mViewMargin + ((getHeight() - mViewMargin * 2)), mPaint);
    }

    /**
     * 绘制y轴刻度
     */
    private void drawYScale(Canvas canvas) {
        for (int i = 0; i < mYList.size(); i++) {
            if (i == 0) {
                canvas.drawText(mYList.get(i).toString(), mViewMargin,
                        mViewMargin, mPaint);
            }
            if (i != 0 && i != 5) {
                canvas.drawText(mYList.get(i).toString(), mViewMargin,
                        mViewMargin + i * ((getHeight() - mViewMargin * 2) / 5), mPaint);
            }
            if (i == 5) {
                canvas.drawText(mYList.get(i).toString(), mViewMargin,
                        mViewMargin + i * ((getHeight() - mViewMargin * 2) / 5) - dpToPx(getContext(), 3), mPaint);
            }
        }
    }

    /**
     * 绘制x轴刻度
     */
    private void drawXScale(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mTextColor);
        for (int i = 0; i < mXList.size(); i++) {
            if (i == 0) {
                canvas.drawText(mXList.get(i), mMarginLeft - dpToPx(getContext(), 3),
                        getHeight() - mViewMargin + dpToPx(getContext(), 10), mPaint);
            }
            if (i != 0 && i != 6) {
                canvas.drawText(mXList.get(i), mMarginLeft + i * (getWidth() / 6),
                        getHeight() - mViewMargin + dpToPx(getContext(), 10), mPaint);
            }
            if (i == 6) {
                canvas.drawText(mXList.get(i), mMarginLeft + i * (getWidth() / 6),
                        getHeight() - mViewMargin + dpToPx(getContext(), 10), mPaint);
            }
        }
    }

    /**
     * 绘制折线图
     */
    private void drawLineView(Canvas canvas) {
        Path path = new Path();
        path.moveTo(mListPoint.get(0).x, mListPoint.get(0).y);
        for (int i = 1; i < 6; i++) {
            path.lineTo(mListPoint.get(i).x, mListPoint.get(i).y);
        }
        canvas.drawPath(path, mPaint);
    }

    /**
     * 绘制曲线图
     */
    private void drawScrollLine(Canvas canvas) {
        Point pStart = new Point();
        Point pEnd = new Point();
        Path path = new Path();
        for (int i = 0; i < 5; i++) {
            pStart = mListPoint.get(i);
            pEnd = mListPoint.get(i + 1);
            Point point3 = new Point();
            Point point4 = new Point();
            float wd = (pStart.x + pEnd.x) / 2;
            point3.x = wd;
            point3.y = pStart.y;
            point4.x = wd;
            point4.y = pEnd.y;
            path.moveTo(pStart.x, pStart.y);
            path.cubicTo(point3.x, point3.y, point4.x, point4.y, pEnd.x, pEnd.y);
            canvas.drawPath(path, mPaint);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    public void setViewData(List<String> yList, List<String> xList) {
        this.mYList = yList;
        this.mXList = xList;
    }

    /**
     * 根据手机分辨率将px 转为 dp
     */
    private float dpToPx(Context context, float pxValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (pxValue * scale + 0.5f);
    }

    private List<Point> getPointList() {
        List<Point> mList = new ArrayList<>();
        float width = getWidth() - (mMarginLeft + mViewMargin);
        float height = getHeight() - mViewMargin * 2;
        for (int i = 0; i < 6; i++) {
            Point point = new Point();
            if (i == 0) {
                point.x = mMarginLeft;
                point.y = getHeight() - mViewMargin;
            }
            if (i == 1) {
                point.x = mMarginLeft + width * 0.2f;
                point.y = mViewMargin + height * 0.6f;
            }
            if (i == 2) {
                point.x = mMarginLeft + width * 0.4f;
                point.y = mViewMargin + height * 0.8f;
            }
            if (i == 3) {
                point.x = mMarginLeft + width * 0.6f;
                point.y = mViewMargin + height * 0.6f;
            }
            if (i == 4) {
                point.x = mMarginLeft + width * 0.8f;
                point.y = mViewMargin + height * 0.8f;
            }
            if (i == 5) {
                point.x = mMarginLeft + width;
                point.y = mViewMargin + height * 0.6f;
            }
            mList.add(point);
        }
        return mList;
    }

    /**
     * 枚举类型直线或者是弧线
     */
    public enum LineType {
        LINE, ARC
    }

    public void setmLineType(LineType mLineType) {
        this.mLineType = mLineType;
    }

    private class Point {

        public float x;
        public float y;

        public Point() {

        }

        public Point(float x, float y) {
            this.x = x;
            this.y = y;
        }
    }

}

在xml中申明我们的自定义控件并在Activity中进行调用:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.customeview04.LineView
        android:id="@+id/lineView"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        app:lineColor="#808080"
        app:lineTextColor="@android:color/holo_orange_dark"
        app:lineTextSize="7sp"
        app:shadowColor="#9fDCDFE1"
        app:viewMargin="15dp"/>

    <com.customeview04.LineView
        android:id="@+id/lineView2"
        android:layout_below="@id/lineView"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginTop="20dp"
        app:lineColor="#808080"
        app:lineTextColor="@android:color/holo_orange_dark"
        app:lineTextSize="7sp"
        app:shadowColor="#9fDCDFE1"
        app:viewMargin="15dp"/>

</RelativeLayout>

public class MainActivity extends AppCompatActivity {

    private LineView lineView,lineView2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lineView= (LineView) findViewById(R.id.lineView);
        lineView2= (LineView) findViewById(R.id.lineView2);
        List<String> yList=new ArrayList<>();
        yList.add("100%");
        yList.add("80%");
        yList.add("60%");
        yList.add("40%");
        yList.add("20%");
        yList.add("0.0%");
        List<String> xList=getCurrentWeekDay();
        lineView.setViewData(yList,xList);
        lineView2.setViewData(yList,xList);
        lineView2.setmLineType(LineView.LineType.ARC);
    }

    /**
     * 获取最近一周的时间 MM-dd
     */
    private List<String> getCurrentWeekDay() {
        List<String> data = new ArrayList<>();
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_MONTH, -7);
        SimpleDateFormat sdf = new SimpleDateFormat("MM-dd");
        for (int i = 0; i < 7; i++) {
            calendar.add(Calendar.DAY_OF_MONTH, 1);
            data.add(sdf.format(calendar.getTime()));
        }
        return data;
    }

}


好了,这个案列到这里就分享结束了。这里只是提供了一种绘制的思路,在具体的项目中可能还需要做一定的修改。


 源码下载地址


 

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值