分析:折线的实现可以用canvas对象调用drawLine方法绘制点与点之间的直线,或使用Path对象,调用canvas的drawPath方法来一次绘制所有的直线。在折线下面实现了颜色渐变,我们可以使用线性LinearGradient类来实现,最后处理触摸事件,重新绘绘制界面,效果如下图所示:
1.数据准备
public class Dot { private int x; private int y; private int value; private int transformValue; public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } public int getTransformValue() { return transformValue; } public void setTransformValue(int transformValue) { this.transformValue = transformValue; } }
2.具体代码实现
package com.cmj.linechart; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.graphics.Shader; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import java.util.ArrayList; import java.util.List; public class LineChart extends View { private Paint mAxisPaint; private Paint mDotPaint; private Paint mLinePaint; private Paint mGradientPaint; public static final int[] DEFAULT_GRADIENT_COLORS = {Color.BLUE, Color.GREEN}; private int[] mDataList; private int mMax; private String[] mHorizontalAxis; private int mRadius; private int mClickRadius; private List<Dot> mDots = new ArrayList<>(); private Rect mTextRect; private int mGap; private Path mPath; private Path mGradientPath; private int mStep; private int mSelectDotIndex = -1; private int mSelectDotColor; private int mNormalDotColor; private int mLineColor; public LineChart(Context context) { this(context, null); } public LineChart(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public LineChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LineChart); mLineColor = typedArray.getColor(R.styleable.LineChart_line_color, Color.BLACK); mNormalDotColor = typedArray.getColor(R.styleable.LineChart_dot_normal_color, Color.BLACK); mSelectDotColor = typedArray.getColor(R.styleable.LineChart_dot_selected_color, Color.RED); typedArray.recycle(); initView(); mPath = new Path(); mGradientPath=new Path(); mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()); mClickRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()); mGap = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()); mTextRect = new Rect(); } private void initView() { mAxisPaint = new Paint(); mAxisPaint.setAntiAlias(true); mAxisPaint.setTextSize(20); mAxisPaint.setTextAlign(Paint.Align.CENTER); mDotPaint = new Paint(); mDotPaint.setAntiAlias(true); mLinePaint = new Paint(); mLinePaint.setAntiAlias(true); mLinePaint.setStrokeWidth(3); mLinePaint.setStyle(Paint.Style.STROKE); mLinePaint.setColor(mLineColor); mGradientPaint = new Paint(); mGradientPaint.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { //清空点的集合 mDots.clear(); //去除padding,计算绘制区域的宽高 int width = w - getPaddingLeft() - getPaddingRight(); int height = h - getPaddingBottom() - getPaddingTop(); //根据点的个数平分宽度 mStep = width / (mDataList.length - 1); //根据文本画笔计算绘制x轴第一个个坐标文本占据的矩形边界,这里主要获取其高度 mAxisPaint.getTextBounds(mHorizontalAxis[0], 0, mHorizontalAxis[0].length(), mTextRect); //折线图的最大高度 int maxBarHeight = height - mTextRect.height() - mGap; //计算折线图最大高度和最大数据的比值 float heightRatio = maxBarHeight / mMax; for (int i = 0; i < mDataList.length; i++) { Dot dot = new Dot(); dot.setValue(mDataList[i]); dot.setTransformValue((int) (dot.getValue() * heightRatio)); dot.setX(mStep * i + getPaddingLeft()); dot.setY(getPaddingTop() + maxBarHeight - dot.getTransformValue()); //当时第一个点时,将路径移动到该点 if (i == 0) { mPath.moveTo(dot.getX(), dot.getY()); mGradientPath.moveTo(dot.getX(), dot.getY()); } else { mPath.lineTo(dot.getX(), dot.getY()); mGradientPath.lineTo(dot.getX(), dot.getY()); } //如果到了最后一个点 if (i == mDataList.length - 1) { int bottom = getPaddingTop() + maxBarHeight; //将渐变路径连接到最后一个点在竖直方向的最低点 mGradientPath.lineTo(dot.getX(), bottom); Dot firstDot = mDots.get(0); //连接到第一个点在竖直方向的最低点 mGradientPath.lineTo(firstDot.getX(), bottom); //连接到第一个点,形成闭合区域 mGradientPath.lineTo(firstDot.getX(), firstDot.getY()); } mDots.add(dot); } Shader linearGradient = new LinearGradient(0, 0, 0, getHeight(), DEFAULT_GRADIENT_COLORS, null, Shader.TileMode.CLAMP); mGradientPaint.setShader(linearGradient); } @Override protected void onDraw(Canvas canvas) { canvas.drawPath(mPath, mLinePaint); canvas.drawPath(mGradientPath, mGradientPaint); for (int i = 0; i < mDots.size(); i++) { //绘制文本 String text = mHorizontalAxis[i]; int x = getPaddingLeft() + i * mStep; int y = getHeight() - getPaddingBottom(); //绘制x轴 canvas.drawText(text, x, y, mAxisPaint); Dot dot = mDots.get(i); if (i == mSelectDotIndex) { mDotPaint.setColor(mSelectDotColor); canvas.drawText(String.valueOf(mDataList[i]), dot.getX(), dot.getY() - mRadius - mGap, mAxisPaint); } else { mDotPaint.setColor(mNormalDotColor); } canvas.drawCircle(dot.getX(), dot.getY(), mRadius, mDotPaint); } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mSelectDotIndex = getClicDotIndex(event.getX(), event.getY()); invalidate(); break; case MotionEvent.ACTION_UP: mSelectDotIndex=-1; invalidate(); break; } return true; } /** * 判断选中的点 * @param x * @param y * @return */ private int getClicDotIndex(float x, float y) { int index = -1; for (int i = 0; i < mDots.size(); i++) { Dot dot = mDots.get(i); int left = dot.getX() - mClickRadius; int top = dot.getY() - mClickRadius; int right = dot.getX() + mClickRadius; int bottom = dot.getY() + mClickRadius; if (x > left && x < right && y > top && y < bottom) { index = i; break; } } return index; } public void setHorizontalAxis(String[] horizontalAxis) { mHorizontalAxis = horizontalAxis; } /** * 设置柱状图数据 * * @param dataList * @param max */ public void setDataList(int[] dataList, int max) { mDataList = dataList; mMax = max; } }