TrendChartBaseView
/**
* @author created by zhanghaochen
* @date 2019-04-17 下午5:42
* 描述:折线图基类
*/
public abstract class TrendChartBaseView extends View {
public Paint mPaint = new Paint();
public TrendChartBaseView(Context context) {
super(context);
setLayerType(LAYER_TYPE_NONE, null);
initViews();
}
public TrendChartBaseView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_NONE, null);
initViews();
}
public TrendChartBaseView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayerType(LAYER_TYPE_NONE, null);
initViews();
}
public void initPaint() {
if (mPaint == null) {
mPaint = new Paint();
} else {
mPaint.reset();
}
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
}
protected abstract void initViews();
}
TrendDataBean
/**
* @author created by zhanghaochen
* @date 2019-08-09 10:11 AM
* 描述:折线图的实例
*/
public class TrendDataBean {
public float newValue;
public float vol;
}
TrendView
/**
* @author created by zhanghaochen
* @date 2019-08-07 3:31 PM
* 描述:折线图
*/
public class TrendView extends TrendChartBaseView {
private static final float LEFT_TEXT_WIDTH = SysUtils.convertDpToPixel(30);
private static final float RIGHT_TEXT_WIDTH = SysUtils.convertDpToPixel(35);
private static final float VOL_DISTANCES = SysUtils.convertDpToPixel(1f);
private static final float DP_10 = SysUtils.convertDpToPixel(10);
private static final float DP_5 = SysUtils.convertDpToPixel(5);
private static final float DP_3 = SysUtils.convertDpToPixel(3);
private static final float CHART_LEFT_MARGIN = SysUtils.convertDpToPixel(2);
private static final float TOP_MARGIN = SysUtils.convertDpToPixel(10);
private static final float BOTTOM_MARGIN = SysUtils.convertDpToPixel(3);
private static final float RECT_LINE_WIDTH = SysUtils.convertDpToPixel(0.5f);
private static final int COLOR_BG = Color.parseColor("#FEFEFE");
private static final int COLOR_LINE = Color.parseColor("#D8D8D8");
private static final int COLOR_LABEL_TEXT = Color.parseColor("#AAA8A8");
private static final int COLOR_TREND_LINE = Color.parseColor("#FC3539");
private static final int COLOR_GRADIENT_TOP = Color.parseColor("#B2FF9C9D");
private static final int COLOR_GRADIENT_BOTTOM = Color.parseColor("#B2FFF3EF");
/**
* 分时图的矩形框
*/
private RectF mChartRect;
/**
* 成交量的矩形框
*/
private RectF mVolRect;
/**
* 一小格的长度
*/
private float mStep = 0;
private int mWidth, mHeight;
/**
* 折线图中线的y坐标
*/
private float mChartMiddleY;
private int mMaxSub;
/**
* 总共的数据量,
*/
private int mMaxCount;
/**
* 记录绘制折线图中最高的y的坐标,用来绘制折线下面的渐变阴影,没有渐变的话,不需要这个值
*/
private float mHighestY;
/**
* 记录分时的最大值,最小值,成交量最大值
*/
private float[] mMaxMin = {Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_VALUE};
private float mMoveX, mMoveY;
private List<TrendDataBean> mTrendDatas = new ArrayList<>();
/**
* 是否在按下中
*/
private boolean mIsPressed;
public TrendView(Context context) {
super(context);
}
public TrendView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TrendView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void initViews() {
}
@Override
protected void onSizeChanged(int width, int height, int oldw, int oldh) {
super.onSizeChanged(width, height, oldw, oldh);
if (width > 0 && height > 0) {
mChartRect = new RectF(LEFT_TEXT_WIDTH, TOP_MARGIN, width - RIGHT_TEXT_WIDTH, height * 0.7f);
mVolRect = new RectF(LEFT_TEXT_WIDTH, height * 0.8f, width - RIGHT_TEXT_WIDTH, height - BOTTOM_MARGIN);
mChartMiddleY = mChartRect.top + mChartRect.height() / 2;
mWidth = width;
mHeight = height;
}
}
@Override
protected void onDraw(Canvas canvas) {
drawFrame(canvas);
drawText(canvas);
drawLines(canvas);
drawVol(canvas);
if (mIsPressed) {
drawCrossLine(canvas);
}
}
/**
* 画基础框框
*/
private void drawFrame(Canvas canvas) {
initPaint();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(COLOR_BG);
mPaint.setStrokeWidth(RECT_LINE_WIDTH);
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
// 画线条框
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(COLOR_LINE);
canvas.drawRect(mChartRect, mPaint);
canvas.drawRect(mVolRect, mPaint);
// 画横线
canvas.drawLine(mChartRect.left, (mChartRect.top + mChartMiddleY) / 2, mChartRect.right, (mChartRect.top + mChartMiddleY) / 2, mPaint);
canvas.drawLine(mChartRect.left, mChartMiddleY, mChartRect.right, mChartMiddleY, mPaint);
canvas.drawLine(mChartRect.left, (mChartRect.bottom + mChartMiddleY) / 2, mChartRect.right, (mChartRect.bottom + mChartMiddleY) / 2, mPaint);
canvas.drawLine(mVolRect.left, mVolRect.top + mVolRect.height() / 2, mVolRect.right, mVolRect.top + mVolRect.height() / 2, mPaint);
// 画竖线
canvas.drawLine(mChartRect.left + mChartRect.width() / 4, mChartRect.top, mChartRect.left + mChartRect.width() / 4, mChartRect.bottom, mPaint);
canvas.drawLine(mChartRect.left + mChartRect.width() / 2, mChartRect.top, mChartRect.left + mChartRect.width() / 2, mChartRect.bottom, mPaint);
canvas.drawLine(mChartRect.right - mChartRect.width() / 4, mChartRect.top, mChartRect.right - mChartRect.width() / 4, mChartRect.bottom, mPaint);
canvas.drawLine(mVolRect.left + mVolRect.width() / 4, mVolRect.top, mVolRect.left + mVolRect.width() / 4, mVolRect.bottom, mPaint);
canvas.drawLine(mVolRect.left + mVolRect.width() / 2, mVolRect.top, mVolRect.left + mVolRect.width() / 2, mVolRect.bottom, mPaint);
canvas.drawLine(mVolRect.right - mVolRect.width() / 4, mVolRect.top, mVolRect.right - mVolRect.width() / 4, mVolRect.bottom, mPaint);
}
private void drawText(Canvas canvas) {
if (mMaxMin[0] != Float.MIN_VALUE && mMaxMin[1] != Float.MAX_VALUE) {
initPaint();
// 基准的最大值要比数据的最大值大才行,同理最小值也一样
mPaint.setStyle(Paint.Style.FILL);
int spSize = 9;
float scaledSizeInPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spSize,
getResources().getDisplayMetrics());
mPaint.setTextSize(scaledSizeInPx);
mPaint.setTextAlign(Paint.Align.RIGHT);
mPaint.setColor(COLOR_LABEL_TEXT);
canvas.drawText(mMaxMin[1] + "", mChartRect.left - DP_5, mChartRect.bottom + DP_3, mPaint);
canvas.drawText(mMaxMin[0] + "", mChartRect.left - DP_5, mChartRect.top + DP_3, mPaint);
canvas.drawText((mMaxMin[0] + mMaxMin[1]) / 2f + "", mChartRect.left - DP_5, mChartRect.top + mChartRect.height() / 2f + DP_3, mPaint);
// 画延x轴的几个数字,使用数据的数量作为x轴
mPaint.setTextAlign(Paint.Align.CENTER);
int size = mTrendDatas.size();
canvas.drawText("0", mChartRect.left, mChartRect.bottom + DP_10, mPaint);
canvas.drawText(size / 2 + "", mChartRect.left + mChartRect.width() / 2, mChartRect.bottom + DP_10, mPaint);
canvas.drawText(size + "", mChartRect.right, mChartRect.bottom + DP_10, mPaint);
}
}
private void drawLines(Canvas canvas) {
if (mMaxMin[0] != Float.MIN_VALUE && mMaxMin[1] != Float.MAX_VALUE && mTrendDatas.size() > 0) {
initPaint();
mPaint.setStrokeWidth(SysUtils.convertDpToPixel(1));
mPaint.setColor(COLOR_TREND_LINE);
// 来个曲线
Path path = new Path();
ArrayList<TrendDataBean> tempBeans = new ArrayList<>(mTrendDatas);
for (int i = 0; i < tempBeans.size(); i++) {
TrendDataBean bean = tempBeans.get(i);
if (i == 0) {
// 移动到第一个点
path.moveTo(mChartRect.left, mChartRect.bottom - getOffsetY(bean.newValue));
} else {
path.lineTo(mChartRect.left + mStep * i, mChartRect.bottom - getOffsetY(bean.newValue));
}
}
canvas.drawPath(path, mPaint);
// 绘制阴影
if (tempBeans.size() > 1) {
path.lineTo(mChartRect.left + (tempBeans.size() - 1) * mStep, mChartRect.bottom);
path.lineTo(mChartRect.left, mChartRect.bottom);
path.lineTo(mChartRect.left, mChartRect.bottom - getOffsetY(tempBeans.get(0).newValue));
path.close();
initPaint();
mPaint.setStyle(Paint.Style.FILL);
LinearGradient linearGradient = new LinearGradient(mChartRect.left, mHighestY, mChartRect.left, mChartRect.bottom,
COLOR_GRADIENT_TOP, COLOR_GRADIENT_BOTTOM, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);
canvas.drawPath(path, mPaint);
}
}
}
private void drawVol(Canvas canvas) {
if (mStep <= 0 || mTrendDatas.size() < 1) {
return;
}
initPaint();
ArrayList<TrendDataBean> tempBeans = new ArrayList<>(mTrendDatas);
for (int i = 0; i < tempBeans.size(); i++) {
TrendDataBean bean = tempBeans.get(i);
// 假设第一条是绿色的
if (i == 0) {
mPaint.setColor(ColorUtils.upPriceColor);
// 两根成交量线之间距离0.5dp
// 第一条线的粗细是正常的一半
mPaint.setStrokeWidth((mStep - VOL_DISTANCES) / 2f);
canvas.drawLine(mVolRect.left + mPaint.getStrokeWidth() / 2, mVolRect.bottom,
mVolRect.left + mPaint.getStrokeWidth() / 2, mVolRect.bottom - getVolOffsetY(bean.vol), mPaint);
} else {
// 和前一个价格比较,得出红绿
TrendDataBean preBean = tempBeans.get(i - 1);
if (preBean != null) {
mPaint.setColor(bean.newValue >= preBean.newValue ? ColorUtils.upPriceColor : ColorUtils.downPriceColor);
// 画线,
// 如果是最后一根线,与第一根画法类似
if (i == tempBeans.size() - 1) {
mPaint.setStrokeWidth((mStep - VOL_DISTANCES) / 2f);
canvas.drawLine(mVolRect.left + i * mStep - mPaint.getStrokeWidth() / 2, mVolRect.bottom,
mVolRect.left + i * mStep - mPaint.getStrokeWidth() / 2, mVolRect.bottom - getVolOffsetY(bean.vol), mPaint);
} else {
// 两根线之间的距离为VOL_DISTANCES
mPaint.setStrokeWidth(mStep - VOL_DISTANCES);
canvas.drawLine(mVolRect.left + i * mStep, mVolRect.bottom,
mVolRect.left + i * mStep, mVolRect.bottom - getVolOffsetY(bean.vol), mPaint);
}
}
}
}
}
/**
* 绘制按下去的十字线
*
* @param canvas 画布
*/
private void drawCrossLine(Canvas canvas) {
if (mStep <= 0 || mTrendDatas.size() < 1) {
return;
}
initPaint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(SysUtils.convertDpToPixel(1));
List<TrendDataBean> items = new ArrayList<>(mTrendDatas);
// 获取x在分时图中实际移动的x距离,mMoveX是相对整个View的x
float trueMoveX = mMoveX - mChartRect.left;
float crossX = 0f;
float crossY = 0f;
// 去y值,y使用分时的y坐标
int index = (int) (trueMoveX / mStep);
// 如果index比分时数据多,那么去最后一条分时数据
if (index > items.size() - 1) {
index = items.size() - 1;
}
if (index <= 0) {
index = 0;
}
crossY = mChartRect.bottom - getOffsetY(items.get(index).newValue);
// 取x值(相对分时图的x)
// x是step的整数倍
crossX = index * mStep;
// 画横线
canvas.drawLine(mChartRect.left, crossY, mChartRect.right, crossY, mPaint);
// 画竖线
if (trueMoveX <= 0) {
trueMoveX = 0;
}
if (trueMoveX >= index * mStep) {
trueMoveX = index * mStep;
}
canvas.drawLine(mChartRect.left + trueMoveX, mVolRect.top, mChartRect.left + trueMoveX, mVolRect.bottom, mPaint);
canvas.drawLine(mChartRect.left + trueMoveX, mChartRect.top, mChartRect.left + trueMoveX, mChartRect.bottom, mPaint);
}
public void setData(List<TrendDataBean> data) {
if (data != null && data.size() > 0) {
this.mTrendDatas.clear();
mTrendDatas.addAll(data);
mMaxMin = getMaxMin(mTrendDatas);
// 使用数据的数量作为x轴
try {
mStep = (float) mChartRect.width() / (data.size() - 1);
} catch (Exception e) {
mStep = mChartRect.width();
}
postInvalidate();
}
}
private float getOffsetY(float value) {
float result = 0f;
// 确保差值在最大值之内
float sub = mMaxMin[0] - mMaxMin[1];
if (value <= mMaxMin[0] && mChartRect.height() != 0 && sub > 0) {
return (value - mMaxMin[1]) / sub * (mChartRect.height());
}
return result;
}
private float getVolOffsetY(float vol) {
float result = 0f;
if (vol >= 0 && mMaxMin[2] > 0 && vol <= mMaxMin[2]) {
return vol / mMaxMin[2] * mVolRect.height();
}
return result;
}
/**
* 获取数据的最大值,最小值,和成交最大值
*/
private float[] getMaxMin(List<TrendDataBean> trendDataBeans) {
float[] maxMin = {Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_VALUE};
if (trendDataBeans == null || trendDataBeans.size() < 1) {
return maxMin;
}
for (int i = 0; i < trendDataBeans.size(); i++) {
TrendDataBean bean = trendDataBeans.get(i);
maxMin[0] = Math.max(maxMin[0], bean.newValue);
maxMin[1] = Math.min(maxMin[1], bean.newValue);
maxMin[2] = Math.max(maxMin[2], bean.vol);
}
return maxMin;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
mMoveX = event.getX();
mMoveY = event.getY();
if (action == MotionEvent.ACTION_DOWN) {
mIsPressed = true;
return true;
} else if (action == MotionEvent.ACTION_MOVE) {
postInvalidate();
return true;
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mIsPressed = false;
postInvalidate();
return true;
}
return super.onTouchEvent(event);
}
}
工具类(方法)
这里面涉及到的工具方法只有一个,就是Dp转Px,其他没啥了,单独抠出来吧:
public static int convertDpToPixel(float dp) {
DisplayMetrics metrics = GlobalParams.mApplication.getResources().getDisplayMetrics();
return (int) (metrics.density * dp + 0.5f);
}
网友评论