共计 9389 个字符,预计需要花费 24 分钟才能阅读完成。
导读 | 这篇文章主要为大家详细介绍了 android 实现可以滑动的平滑曲线图,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 |
本文实例为大家分享了 android 实现可以滑动的平滑曲线图的具体代码,供大家参考,具体内容如下
直接上代码,里面有详细注解
1 attr 属性编写
2 ChartView
package com.laisontech.commonuilibrary.customviews;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.laisontech.commonuilibrary.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 自定义折线图
*/
public class ChartView extends View {
private static final int FIRST = 1;
private static final int MIDDLE = 2;
private static final int END = 3;
//xy 坐标轴颜色
private int xyLineColor = 0xffCFE2CF;
// 折线选中的圆形颜色
private int selectCircleColor = 0xff00A8FF;
// 选中数据提示框颜色
private int selectReminderColor = 0xff00A8FF;
// 折线中圆形内部部颜色
private int xyTextColor = 0xff0014FF;
// 折线图中折线的颜色
private int lineColor = 0xffFD00FF;
//xy 坐标轴宽度
private int xyLineWidth = dpToPx(1);
//xy 坐标轴文字大小
private int xyTextSize = spToPx(12);
// x 轴各个坐标点水平间距
private int interval = dpToPx(40);
// 背景颜色
private int bgColor = 0xffffffff;
// 是否有起手时的滑动感
private boolean isScroll = false;
// 提示框显示位置
private int mShowPositionType = 3;
// 绘制 XY 轴坐标对应的画笔
private Paint mXYPaint;
// 绘制 XY 轴的文本对应的画笔
private Paint mXYTextPaint;
// 画折线对应的画笔
private Paint mSpinnerLinePaint;
private int width;
private int height;
// x 轴的原点坐标
private int mXOri;
// y 轴的原点坐标
private int mYOri;
// 第一个点 X 的坐标
private float mXInit;
// 第一个点对应的最大 X 坐标
private float maxXInit;
// 第一个点对应的最小 X 坐标
private float minXInit;
// x 轴坐标对应的数据
private List mXData = new ArrayList();
// y 轴坐标对应的数据
private List mYData = new ArrayList();
// 折线对应的数据
private Map mSpinnerValue = new HashMap();
// 点击的点对应的 X 轴的第几个点,默认 1
private int selectIndex = 1;
// X 轴刻度文本对应的最大矩形,为了选中时,在 x 轴文本画的框框大小一致,获取从数据中得到的 x 轴数据,获得最长数据
private Rect xValueRect;
// 速度检测器
private VelocityTracker mTracker;
// 是否为短距离滑动
private boolean isShortSlide = false;
// 获取尺寸的的中间
private int mSelectMiddle = 0;
// 曲线切率
private float mLineSmoothness = 0.18f;
public ChartView(Context context) {this(context, null);
}
public ChartView(Context context, AttributeSet attrs) {this(context, attrs, 0);
}
public ChartView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
initPaint();}
// 设置切率
public void setLineSmoothness(float lineSmoothness) {if (lineSmoothness != this.mLineSmoothness) {this.mLineSmoothness = lineSmoothness;}
}
/**
* 初始化
*/
private void initPaint() {mXYPaint = new Paint();
mXYPaint.setAntiAlias(true);
mXYPaint.setStrokeWidth(xyLineWidth);
mXYPaint.setStrokeJoin(Paint.Join.ROUND);
mXYPaint.setColor(xyLineColor);
mXYTextPaint = new Paint();
mXYTextPaint.setAntiAlias(true);
mXYTextPaint.setTextSize(xyTextSize);
mXYTextPaint.setStrokeJoin(Paint.Join.ROUND);
mXYTextPaint.setColor(xyTextColor);
mXYTextPaint.setStyle(Paint.Style.STROKE);
mSpinnerLinePaint = new Paint();
mSpinnerLinePaint.setAntiAlias(true);
mSpinnerLinePaint.setStrokeWidth(xyLineWidth);
mSpinnerLinePaint.setColor(lineColor);
mSpinnerLinePaint.setStyle(Paint.Style.STROKE);
mSpinnerLinePaint.setStrokeJoin(Paint.Join.ROUND);
}
/**
* 初始化
*
* @param context
* @param attrs
* @param defStyleAttr
*/
private void init(Context context, AttributeSet attrs, int defStyleAttr) {TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ChartView, defStyleAttr, 0);
int count = array.getIndexCount();
for (int i = 0; i textYWdith)
textYWdith = temp;
}
int dp2 = dpToPx(2);
int dp3 = dpToPx(3);
mXOri = (int) (dp2 + textYWdith + dp2 + xyLineWidth);
// 获取 x 轴的最长文本的宽度所占的矩形
xValueRect = getTextBounds(mXData.get(getListItemMaxIndex(mXData)), mXYTextPaint);
// X 轴文本高度
float textXHeight = xValueRect.height();
for (int i = 0; i textXHeight)
textXHeight = rect.height();
if (rect.width() > xValueRect.width())
xValueRect = rect;
}
mYOri = (int) (height - dp2 - textXHeight - dp3 - xyLineWidth);
mXInit = mXOri + xValueRect.width() / 2 + dpToPx(5);
minXInit = width - (width - mXOri) * 0.1f - interval * (mXData.size() - 1);
maxXInit = mXInit;
}
selectIndex = getSelectIndexFromShowType(mShowPositionType);
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {canvas.drawColor(bgColor);
drawXY(canvas);
drawBrokenLineAndPoint(canvas);
}
/**
* 绘制交点处对应的点
*/
private void drawBrokenLineAndPoint(Canvas canvas) {if (mXData.size() 0) {previousPointX = getSpinnerPoint(i - 1).x;
previousPointY = getSpinnerPoint(i - 1).y;
} else {
// 用当前点表示上一个点
previousPointX = currentPointX;
previousPointY = currentPointY;
}
}
if (Float.isNaN(prePreviousPointX)) {
// 是前两个点?
if (i > 1) {prePreviousPointX = getSpinnerPoint(i - 2).x;
prePreviousPointY = getSpinnerPoint(i - 2).y;
} else {
// 当前点表示上上个点
prePreviousPointX = previousPointX;
prePreviousPointY = previousPointY;
}
}
// 判断是不是最后一个点了
if (i = mXOri) {// 只绘制从原点开始的区域
mXYTextPaint.setColor(xyTextColor);
canvas.drawLine(x, mYOri, x, mYOri - length, mXYPaint);
// 绘制 X 轴文本
String text = mXData.get(i);
Rect rect = getTextBounds(text, mXYTextPaint);
if (i == selectIndex - 1) {mXYTextPaint.setColor(lineColor);
canvas.drawText(text, 0, text.length(), x - rect.width() / 2, mYOri + xyLineWidth + dpToPx(2) + rect.height(), mXYTextPaint);
canvas.drawRoundRect(x - xValueRect.width() / 2 - dpToPx(3), mYOri + xyLineWidth + dpToPx(1), x + xValueRect.width() / 2 + dpToPx(3), mYOri + xyLineWidth + dpToPx(2) + xValueRect.height() + dpToPx(2), dpToPx(2), dpToPx(2), mXYTextPaint);
} else {canvas.drawText(text, 0, text.length(), x - rect.width() / 2, mYOri + xyLineWidth + dpToPx(2) + rect.height(), mXYTextPaint);
}
}
}
}
private float startX;
private float startx;
@Override
public boolean onTouchEvent(MotionEvent event) {if (isScrolling)
return super.onTouchEvent(event);
// 当该 view 获得点击事件,就请求父控件不拦截事件
this.getParent().requestDisallowInterceptTouchEvent(true);
obtainVelocityTracker(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startx = event.getX();
Log.e("XXXX", "down:" + startX + "");
break;
case MotionEvent.ACTION_MOVE:
// 滑动距离小于等于 8 的时候任务为短距离滑动
// 当前 x 轴的尺寸与设置的 x 轴间隔的距离之乘积大于 屏幕中的显示布局宽度与 x 轴七点之差时,开始移动
if (interval * mXData.size() > width - mXOri) {
// 获取滑动的距离
float dis = event.getX() - startX;
// 重新赋值给 startX
startX = event.getX();
// 当前 x 原点距离与左右滑动的距离之和没有最小值大,则将当前 x 距离赋值为最小,以下相似
if (mXInit + dis maxXInit) {mXInit = maxXInit;} else {mXInit = mXInit + dis;}
invalidate();}
break;
case MotionEvent.ACTION_UP:
isShortSlide = Math.abs(event.getX() - startx) minXInit) {// 向左滑动
if (mXInit - value 0 && mXInit = maxXInit)
mXInit = maxXInit;
else
mXInit = mXInit + value;
}
invalidate();}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {isScrolling = true;}
@Override
public void onAnimationEnd(Animator animator) {isScrolling = false;}
@Override
public void onAnimationCancel(Animator animator) {isScrolling = false;}
@Override
public void onAnimationRepeat(Animator animator) {}});
animator.start();}
/**
* 获取速度
*
* @return
*/
private float getVelocity() {if (mTracker != null) {mTracker.computeCurrentVelocity(1000);
return mTracker.getXVelocity();}
return 0;
}
/**
* 点击 X 轴坐标或者折线节点
* */
// 44 142 139
private void clickAction(MotionEvent event) {int dp8 = dpToPx(8);
float eventX = event.getX();
float eventY = event.getY();
if (!isShortSlide) {for (int i = 0; i = start + (mSelectMiddle - 1) * interval && x = x - dp8 && eventX = y - dp8 && eventY = x - rect.width() / 2 - dp8 && eventX = y - dp8 && eventY value) {
this.mSpinnerValue = value;
invalidate();}
public void setValue(Map value, List xValue, List yValue) {
this.mSpinnerValue = value;
this.mXData = xValue;
this.mYData = yValue;
invalidate();}
public Map getValue() {return mSpinnerValue;}
/**
* 获取丈量文本的矩形
*
* @param text
* @param paint
* @return
*/
private Rect getTextBounds(String text, Paint paint) {Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
return rect;
}
/**
* dp 转化成为 px
*
* @param dp
* @return
*/
private int dpToPx(int dp) {float density = getContext().getResources().getDisplayMetrics().density;
return (int) (dp * density + 0.5f * (dp >= 0 ? 1 : -1));
}
/**
* sp 转化为 px
*
* @param sp
* @return
*/
private int spToPx(int sp) {float scaledDensity = getContext().getResources().getDisplayMetrics().scaledDensity;
return (int) (scaledDensity * sp + 0.5f * (sp >= 0 ? 1 : -1));
}
/**
* 获取集合中最长的 index
*/
private static final int NULL_INDEX = -1;
public int getListItemMaxIndex(List> data) {if (data == null || data.size() max) {return i;}
}
return NULL_INDEX;
}
// 获得在滑动结束的时候在屏幕内的点
private int middleIndex(int size) {if (size % 2 == 0) {return size / 2;} else {return size / 2 + 1;}
}
/**
* 根据两点坐标获取中间某个点
*
* @param from 坐标 1
* @param to 坐标 2
*/
// 获取已知点的斜率 y = kx+b
private float getSlope(Point from, Point to) {float k = (to.y - from.y) / (to.x - from.x);
Log.e("Point", "参数 b:" + k);
return k;
}
// 获取参数 b
private float getParams(Point from, Point to) {float b = from.y - (getSlope(from, to) * from.x);
Log.e("Point", "参数 b:" + b);
return b;
}
// 根据两点间的坐标获取 x 轴的任意一个坐标 x 值,
private float getArbitrarilyX(Point from, Point to, int grade, int needGrade) {
// 获得输入的新坐标
float x = ((to.x - from.x) * needGrade) / grade + from.x;
Log.e("Point", "x 坐标值:" + x);
return x;
}
// 获取坐标值
private Point getPoint(Point from, Point to, int grade, int needGrade) {Point point = new Point();
point.setX(getArbitrarilyX(from, to, grade, needGrade));
float slope = getSlope(from, to);
point.setY(slope * point.x + getParams(from, to));
return point;
}
// 获取绘制折线的点
private Point getSpinnerPoint(int valueIndex) {float x = mXInit + interval * (valueIndex);
float y = mYOri - mYOri * (1 - 0.1f) * mSpinnerValue.get(mXData.get(valueIndex)) / mYData.get(mYData.size() - 1);
return new Point(x, y);
}
private class Point {
float x;
float y;
public Point() {}
public float getX() {return x;}
public void setX(float x) {this.x = x;}
public float getY() {return y;}
public void setY(float y) {this.y = y;}
public Point(float x, float y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助。
正文完
星哥说事-微信公众号
![post-qrcode](https://www.xgss.net/wp-content/uploads/2023/07/二维码-微信公众号256x256.jpg)