折线图合集效果-自定义的折线图
实现效果图:
参考网址:
第一种样式:https://github.com/igeek-YZ/LineChartView
第二种样式:https://github.com/qht1003077897/hellocharts-line
实现效果图:
(1)在res/values目录下,新建一个attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LineView"> <attr name="textColor" format="color"/> <attr name="lineColor" format="color"/> <attr name="lineWidth" format="dimension"/> <attr name="dotRadius" format="dimension"/> <attr name="dataFactor" format="integer"/> <attr name="dashColor" format="color"/> <attr name="isShowColumnLine" format="boolean"/> <attr name="isShowRowLine" format="boolean"/> <attr name="isLinearGradient" format="boolean"/> </declare-styleable> </resources>
(2)在res/values目录下,新建一个dimens.xml
<resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="toolbar_height">45dp</dimen> <dimen name="image_height">20dp</dimen> <dimen name="font_smalll">11sp</dimen> <dimen name="font_normal">13sp</dimen> <dimen name="font_middle">15sp</dimen> <dimen name="font_big">18sp</dimen> <dimen name="font_super_big">22sp</dimen> </resources>
(2)在res/values目录下,在colors.xml文件中添加
<color name="font_color">#999999</color> <color name="line">#e6e6e6</color> <color name="font_color1">#666666</color> <color name="bg_color">#e9e9e9</color> <color name="white">#ffffff</color> <color name="black">#000000</color>(3)在res/layout目录下,新建一个activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/activity_main" android:background="@color/bg_color" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:textSize="@dimen/font_middle" android:gravity="center" android:padding="@dimen/activity_horizontal_margin" android:text="2010-1017年度销售额变化曲线图" android:layout_height="wrap_content" /> <com.wzh.linechart.view.LineView app:textColor="@android:color/black" app:isShowColumnLine="true" app:isShowRowLine="false" app:dashColor="@color/colorPrimary" app:dataFactor="100" app:dotRadius="4dp" app:isLinearGradient="true" app:lineColor="@color/colorAccent" app:lineWidth="2dp" android:layout_width="match_parent" android:padding="@dimen/activity_horizontal_margin" android:layout_height="match_parent" android:id="@+id/lineView" /> </LinearLayout>
(4)在MainActivity中调用方法
public class MainActivity extends AppCompatActivity { private LineView lineView ; private String yTitle = "销售额(单位:万元)"; private String xTitle = "(年份)"; private int[] data = {70, 80, 90, 60, 80, 70, 40}; private String[] lables = {"2010", "2011", "2012", "2013", "2014", "2015", "2016"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lineView = (LineView) findViewById(R.id.lineView); lineView.setData(data); lineView.setLables(lables); lineView.setxTitle(xTitle); lineView.setyTitle(yTitle); lineView.setDataFactor(10); } }
(5)新建一个自定义的折线图的类, LineView
public class LineView extends View { private int width = 200, height = 200; /** * 绘制坐标轴的画笔 */ private Paint axesPaint; /** * 绘制图的时候画笔 */ private Paint linePaint; private Path path; private Path p; private final int marginBorder = 80; private int[] data ; private String[] lables; /** * 存放横坐标的值 */ private float x[]; /** * 存放纵坐标的值 */ private float y[]; private int yMaxNum = 0; private float h1, h2; private int valueMax, valueMin; private DisplayMetrics dm; private Shader mShader; /** * shape的是底部渐变效果所用的 */ private Paint shapePaint; private Path shapePath; /** * 文字的颜色 */ private int textColor; /** * 折线的颜色 */ private int lineColor; /** * 折线的粗细 */ private float lineWidth; /** * 折线点的半径大小 */ private float dotRadius; /** * 数据的放大因子,比如数据是2位数的就是10,2位数的就是100。根据数据来定 */ private int dataFactor = 100; /** * 是否显示纵向的垂直线 */ private boolean isShowColumnLine; /** * 是否显示横向的垂直线 */ private boolean isShowRowLine; /** * 是否显示下面的渐变颜色 */ private boolean isLinearGradient; /** * 垂直的虚线的颜色 */ private int dashColor; private String yTitle = "y坐标" ; private String xTitle ="x坐标"; public LineView(Context context) { this(context, null); } public LineView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LineView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LineView); textColor = ta.getColor(R.styleable.LineView_textColor, ContextCompat.getColor(getContext(), R.color.black)); lineColor = ta.getColor(R.styleable.LineView_lineColor, ContextCompat.getColor(getContext(), R.color.colorAccent)); lineWidth = ta.getDimension(R.styleable.LineView_lineWidth, 3); dotRadius = ta.getDimension(R.styleable.LineView_dotRadius, 10); dataFactor = ta.getInteger(R.styleable.LineView_dataFactor, 100); isShowColumnLine = ta.getBoolean(R.styleable.LineView_isShowColumnLine, false); isShowRowLine = ta.getBoolean(R.styleable.LineView_isShowRowLine, false); isLinearGradient = ta.getBoolean(R.styleable.LineView_isLinearGradient, true); dashColor = ta.getColor(R.styleable.LineView_dashColor, ContextCompat.getColor(getContext(), R.color.colorPrimary)); ta.recycle(); init(); } private void init() { dm = new DisplayMetrics(); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getMetrics(dm); axesPaint = new Paint(); axesPaint.setColor(textColor); axesPaint.setAntiAlias(true); axesPaint.setTextSize(getResources().getDimension(R.dimen.font_smalll)); linePaint = new Paint(); linePaint.setColor(lineColor); linePaint.setAntiAlias(true); path = new Path(); p = new Path(); shapePath = new Path(); mShader = new LinearGradient(10, 0, 0, dm.heightPixels / 2, new int[]{Color.parseColor("#00ffffff"), Color.parseColor("#BF00e5ff")}, null, Shader.TileMode.MIRROR); shapePaint = new Paint(); shapePaint.setAntiAlias(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); if (modeWidth == MeasureSpec.EXACTLY) { width = sizeWidth; } if (modeHeight == MeasureSpec.EXACTLY) { height = Math.min(sizeHeight, sizeWidth); } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (data==null || lables==null){ return; } if (data.length==0 || lables.length==0){ return; } yMaxNum = Math.round(getMax() / dataFactor) + 1; x = new float[lables.length]; y = new float[data.length]; drawYAxes(canvas); drawXAxes(canvas); drawLine(canvas); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); // postInvalidate(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); } /** * 绘制折现 * * @param canvas */ private void drawLine(Canvas canvas) { for (int i = 0; i < y.length; i++) { /** * 重置画笔,绘制折线 */ linePaint.setColor(lineColor); linePaint.setStyle(Paint.Style.FILL);//设置空 linePaint.setShader(null); linePaint.setPathEffect(null); canvas.drawCircle(x[i], y[i], dotRadius, linePaint); /** * 绘制点上的值,如果是V字型的点就在底下显示,默认在下面 */ if (i < y.length - 1 && i > 1) { if (y[i] > y[i - 1] && y[i] > y[i + 1]) { canvas.drawText(data[i] + "", x[i] - marginBorder / 3, y[i] + (marginBorder / 3) * 2, axesPaint); } else { canvas.drawText(data[i] + "", x[i] - marginBorder / 4, y[i] - (marginBorder / 3), axesPaint); } } else { canvas.drawText(data[i] + "", x[i] - marginBorder / 3, y[i] + (marginBorder / 3) * 2, axesPaint); } /** * 绘制折线 */ if (i == 0) { shapePath.moveTo(x[i], y[i]); path.moveTo(x[i], y[i]); } else { shapePath.lineTo(x[i], y[i]); path.lineTo(x[i], y[i]); } linePaint.setStrokeWidth(lineWidth); linePaint.setStyle(Paint.Style.STROKE);//设置空 canvas.drawPath(path, linePaint); /** * 绘制竖直方向的虚线 */ linePaint.setStrokeWidth(dip2px(1)); if (isShowColumnLine) { DashPathEffect pathEffect = new DashPathEffect(new float[]{15, 15, 15, 15}, 1); linePaint.setPathEffect(pathEffect); linePaint.setColor(dashColor); p.reset(); p.moveTo(x[i], y[i]); p.lineTo((width / (lables.length + 1)) * (i + 1) + getPaddingLeft(), height - marginBorder - 20); canvas.drawPath(p, linePaint); } if (isShowRowLine) { /** * 横向的虚线 */ DashPathEffect pathEffect = new DashPathEffect(new float[]{15, 15, 15, 15}, 1); linePaint.setPathEffect(pathEffect); linePaint.setColor(dashColor); p.reset(); p.moveTo(x[i], y[i]); p.lineTo(getPaddingLeft() + marginBorder + 20, y[i]); canvas.drawPath(p, linePaint); } } /** * 绘制下部分渐变颜色 */ shapePath.lineTo((width / (lables.length + 1)) * (y.length) + getPaddingLeft(), height - marginBorder); shapePath.lineTo((width / (lables.length + 1)) + getPaddingLeft(), height - marginBorder); shapePath.close(); // 使这些点构成封闭的多边形 if (isLinearGradient){ shapePaint.setShader(mShader); canvas.drawPath(shapePath, shapePaint); } } /** * 绘制y相关坐标 * * @param canvas */ private void drawYAxes(Canvas canvas) { /** * 绘制Y纵坐标 */ canvas.drawLine(getPaddingLeft() + marginBorder, height - marginBorder, getPaddingLeft() + marginBorder, marginBorder, axesPaint); for (int i = 0; i < yMaxNum; i++) { if (i == 0) { valueMax = (yMaxNum - i) * dataFactor; h2 = ((height - marginBorder) / (yMaxNum + 1)) * (i + 1); } else { canvas.drawLine(getPaddingLeft() + marginBorder, ((height - marginBorder) / (yMaxNum + 1)) * (i + 1), getPaddingLeft() + marginBorder + 20, ((height - marginBorder) / (yMaxNum + 1)) * (i + 1), axesPaint); canvas.drawText((yMaxNum - i) * dataFactor + "", getPaddingLeft() / 2, ((height - marginBorder * 4 / 5) / (yMaxNum + 1)) * (i + 1), axesPaint); } } for (int i = 0; i < data.length; i++) { y[i] = height - marginBorder - ((data[i] * 1.0f / (valueMax * 1.0f))) * (height - marginBorder - h2); } /** * 绘制小箭头 */ canvas.drawLine(getPaddingLeft() + marginBorder - 20, marginBorder + 20, getPaddingLeft() + marginBorder, marginBorder, axesPaint); canvas.drawLine(getPaddingLeft() + marginBorder + 20, marginBorder + 20, getPaddingLeft() + marginBorder, marginBorder, axesPaint); /** * 绘制数据单位 */ axesPaint.setTextSize(getResources().getDimension(R.dimen.font_smalll)); canvas.drawText(yTitle, getPaddingLeft() / 2, marginBorder / 2, axesPaint); } /** * 绘制x相关坐标 * * @param canvas */ private void drawXAxes(Canvas canvas) { /** * 绘制X横坐标 */ canvas.drawLine(getPaddingLeft() + marginBorder, height - marginBorder, width - getPaddingRight() / 2, height - marginBorder, axesPaint); for (int i = 0; i < lables.length; i++) { canvas.drawLine((width / (lables.length + 1)) * (i + 1) + getPaddingLeft(), height - marginBorder, (width / (lables.length + 1)) * (i + 1) + getPaddingLeft(), height - marginBorder - 20, axesPaint); canvas.drawText(lables[i], (width / (lables.length + 1)) * (i + 1) - marginBorder * 2 / 3 + getPaddingLeft(), height - marginBorder / 3, axesPaint); x[i] = (width / (lables.length + 1)) * (i + 1) + getPaddingLeft(); } canvas.drawText(xTitle, getPaddingLeft() / 2, height - marginBorder / 3, axesPaint); /** * 绘制小箭头 */ canvas.drawLine(width - getPaddingRight() / 2 - 20, height - marginBorder - 20, width - getPaddingRight() / 2, height - marginBorder, axesPaint); canvas.drawLine(width - getPaddingRight() / 2 - 20, height - marginBorder + 20, width - getPaddingRight() / 2, height - marginBorder, axesPaint); } /** * 求最大值 * * @return */ public int getMax() { int max = data[0]; for (int i = 1; i < data.length; i++) { if (max < data[i]) { max = data[i]; } } return max; } public void setLables(String[] lables) { this.lables = lables; postInvalidate(); } public void setData(int[] data) { this.data = data; postInvalidate(); } public void setyTitle(String yTitle) { this.yTitle = yTitle; postInvalidate(); } public void setxTitle(String xTitle) { this.xTitle = xTitle; postInvalidate(); } public void setDataFactor(int dataFactor) { this.dataFactor = dataFactor; postInvalidate(); } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ private int dip2px(float dpValue) { return (int) (dpValue * dm.density + 0.5f); } }