在2020年的最后一天,来一个滚动折线图收尾吧

1609405639(1).jpg
不多说直接看view
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mWidth = getWidth();
mHeight = getHeight();
// 测量Y轴数据的文本宽度
Rect rect = getTextBounds(mYValue.get(mYValue.size() - 1).value, mYTextPaint);
// 计算X轴的左边距
mYLeftInterval = rect.width() + mYTextLeftInterval * 2;
// 设置选中的位置在最后一个
mCurrentSelectPoint = mXValue.size();
// 第一个X轴点的位置
mXFirstPoint = mYLeftInterval + mInterval;
// 遍历数据最大值 如果为0那么默认为1
for (int i = 0; i
获取宽高,测量Y轴数据文本的宽度加上边距,有注释,相信能看明白
下面是取数据最大值为计算做准备,最大和最小第一个点的距离
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(mBackgroundColor);
drawXYLine(canvas);
drawYText(canvas);
drawBrokenLineAndPoint(canvas);
if (!isScrolling && !aniLock) {
scrollAtStart();
}
}
/**
* 绘制X、Y轴
*/
private void drawXYLine(Canvas canvas) {
mXYPaint.setColor(mXYColor);
// 绘制X轴
canvas.drawLine(mYLeftInterval, mHeight - getPaddingBottom() - mXBottomInterval,
mWidth - getPaddingRight(), mHeight - getPaddingBottom() - mXBottomInterval, mXYPaint);
// 绘制Y轴
canvas.drawLine(mYLeftInterval, 0, mYLeftInterval, mHeight - getPaddingBottom() - mXBottomInterval, mXYPaint);
//绘制y轴箭头
mXYPaint.setStyle(Paint.Style.STROKE);
Path path = new Path();
path.moveTo(mYLeftInterval - dpToPx(5), mXBottomInterval);
path.lineTo(mYLeftInterval, 0);
path.lineTo(mYLeftInterval + dpToPx(5), mXBottomInterval);
canvas.drawPath(path, mXYPaint);
}
/**
* 绘制Y轴文本
*/
private void drawYText(Canvas canvas) {
for (int i = 0; i
注释写的很清楚, 上面最后注释的那些事我为了测试文本的距离写的,不必在意。
/**
* 绘制折线和折线交点处对应的点
*/
private void drawBrokenLineAndPoint(Canvas canvas) {
//重新开一个图层
int layerId = canvas.saveLayer(0, 0, mWidth, mHeight, null, Canvas.ALL_SAVE_FLAG);
drawLine(canvas);
drawLinePoint(canvas);
// 将折线超出x轴坐标的部分截取掉
mXYPaint.setStyle(Paint.Style.FILL);
mXYPaint.setColor(mBackgroundColor);
mXYPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
RectF rectF = new RectF(0, 0, mYLeftInterval, mHeight);
canvas.drawRect(rectF, mXYPaint);
mXYPaint.setXfermode(null);
//保存图层
canvas.restoreToCount(layerId);
}
接下来就是主要的折线和折线点位了
/**
* 绘制折线
**/
private void drawLine(Canvas canvas) {
if (mXValue.size()
以上都用注释, 只要肯看一下就能看懂,看不懂直接拿去拷贝。
提示框那个注释是为了让提示框好看些, 就画个矩形, 周边圆角, 不然路径画出来是直角。
/**
* 当宽度不足以呈现全部数据时 滚动
*/
private void scrollAtStart() {
// 整体数据的宽度 大于 绘制区域宽度
if (mInterval * mXValue.size() > mWidth - mYLeftInterval) {
float scrollLength = maxXFirstPoint - minXFirstPoint;
ValueAnimator animator = ValueAnimator.ofFloat(0, scrollLength);
animator.setDuration(500L);//时间最大为1000毫秒,此处使用比例进行换算
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
mXFirstPoint = (int) Math.max(mXFirstPoint - value, minXFirstPoint);
invalidate();
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
isScrolling = true;
}
@Override
public void onAnimationEnd(Animator animator) {
isScrolling = false;
aniLock = true;
}
@Override
public void onAnimationCancel(Animator animator) {
isScrolling = false;
aniLock = true;
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
}
private float startX;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isScrolling) return super.onTouchEvent(event);
this.getParent().requestDisallowInterceptTouchEvent(true);//当该view获得点击事件,就请求父控件不拦截事件
obtainVelocityTracker(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
if (mInterval * mXValue.size() > mWidth - mYLeftInterval) {//当期的宽度不足以呈现全部数据
float scrollX = event.getX() - startX;
startX = event.getX();
if (mXFirstPoint + scrollX = x - dp8 && eventX = y - dp8 && eventY
以上是点击事件, 点击点位返回点位的数据
基本上没有什么难点,就是滑动的时候计算那地方需要注意一下, 之前就没注意导致刚滑动一点就会到最边上,后来才发现滑动的时候没计算X轴。
地址:https://github.com/xiaobinAndroid421726260/Android_CustomAllCollection.git