自定义View实现折线图:
运行效果:
少说废话,实现起来还是比较简单的,无非就是使用canvas进行绘图,以及坐标的计算,下面直接贴代码:
ChartView.java
/**
* Created by wangke on 2017/2/20.
* 自定义折线图
*/
public class ChartView extends View{
private int mViewHeight; //当前View的高
private int mViewWidth; //当前View的宽
private Paint mPaintCdt;// 绘制坐标系的画笔
private Paint mPaintSysPoint; //绘制坐标系上刻度点
private Paint mPaintLinePoint; //绘制折线上的点
private Paint mPaintText; //绘制文字
private Paint mPaintLine; //绘制折线
private Paint mPaintDash; //绘制虚线
private Paint mPaintSys; //x,y轴
private Rect mXBound;
private Rect mYBound;
private ArrayList<Point> pointList = null;
private int X_MAX; //传入点的X的最大坐标
private int Y_MAX; //传入点的Y的最大坐标
private float mScreenXdistance; //x轴刻度在屏幕上的间隔
private float mScreenYdistance; //y轴刻度在屏幕上的间隔
//折线图距离四周的像素大小
private int Margin = 80;
private int coordinateSystemColor;
private float coordinateSystemSize;
private int lineColor;
private float lineSize;
private int lineColorPoint;
private float lineColorPointRadius;
private int scalePointColor;
private float scalePointRadius;
private boolean isShowDash;
private int xScale;
private int yScale;
private float dashSize;
private int dashColor;
public ChartView(Context context) {
super(context);
InitPaint();
}
//设置点的数据
public void setPoint(ArrayList<Point> points) {
pointList = new ArrayList();
pointList = points;
int []xPointArray = new int[100];
int []yPointArray = new int[100];
//遍历传入的点的坐标,获取最大的x,y点的坐标,用来计算刻度
for(int i=0;i<pointList.size();i++){
Point point = pointList.get(i);
xPointArray[i] = point.x;
yPointArray[i] = point.y;
}
Arrays.sort(xPointArray);
Arrays.sort(yPointArray);
X_MAX = xPointArray[xPointArray.length-1];
Y_MAX = yPointArray[yPointArray.length-1];
Log.i("wk","X的最大坐标:"+xPointArray[xPointArray.length-1]);
Log.i("wk","y的最大坐标:"+yPointArray[yPointArray.length-1]);
//调用绘制
invalidate();
}
//初始化画笔
private void InitPaint() {
mPaintCdt = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置画线
mPaintCdt.setStyle(Paint.Style.STROKE);
//设置线的宽度
mPaintCdt.setStrokeWidth(lineSize);
mPaintCdt.setColor(lineColor);
mPaintSysPoint = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置填充
mPaintSysPoint.setStyle(Paint.Style.FILL);
mPaintSysPoint.setColor(scalePointColor);
mPaintLinePoint = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置填充
mPaintLinePoint.setStyle(Paint.Style.FILL);
Log.i("wk","线上点的颜色:"+lineColor);
mPaintLinePoint.setColor(lineColorPoint);
//绘制xy轴
mPaintSys = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置画线
mPaintSys.setStyle(Paint.Style.STROKE);
//设置线的宽度
mPaintSys.setStrokeWidth(coordinateSystemSize);
mPaintSys.setColor(coordinateSystemColor);
mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintText.setTextAlign(Paint.Align.CENTER);
mPaintText.setColor(Color.WHITE);
mPaintText.setTextSize(30);
mPaintLine = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置画线
mPaintLine.setStyle(Paint.Style.STROKE);
//设置线的宽度
mPaintLine.setStrokeWidth(lineSize);
//设置画笔的颜色
mPaintLine.setColor(lineColor);
mPaintDash = new Paint();
mPaintDash.setStyle(Paint.Style.STROKE);
mPaintDash.setStrokeWidth(dashSize);
mPaintDash.setColor(dashColor);
mPaintDash.setPathEffect(new DashPathEffect(new float[]{10,10},0));
mXBound = new Rect();
mYBound = new Rect();
invalidate();
}
public ChartView(Context context, AttributeSet attrs) {
super(context, attrs);
//获取属性值
getAttrs(context,attrs);
InitPaint();
}
//获取设置的属性
private void getAttrs(Context context,AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ChartView);
//坐标系颜色
coordinateSystemColor = ta.getColor(R.styleable.ChartView_coordinateSystemColor, Color.RED);
coordinateSystemSize = ta.getDimension(R.styleable.ChartView_coordinateSystemLineSize, 3f);
//折线颜色
lineColor = ta.getColor(R.styleable.ChartView_lineColor, Color.BLACK);
lineSize = ta.getDimension(R.styleable.ChartView_lineSize, 2f);
//折线上坐标点颜色
lineColorPoint = ta.getColor(R.styleable.ChartView_linePointColor, Color.RED);
//折线上坐标点的半径
lineColorPointRadius = ta.getDimension(R.styleable.ChartView_linePointRadius,6f);
//刻度点颜色
scalePointColor = ta.getColor(R.styleable.ChartView_scalePointColor, Color.RED);
//刻度点半径
scalePointRadius = ta.getDimension(R.styleable.ChartView_scalePointRadius, 6);
//设置是否显示虚线
isShowDash = ta.getBoolean(R.styleable.ChartView_showDash,false);
dashSize = ta.getDimension(R.styleable.ChartView_setDashSize,2f);
dashColor = ta.getColor(R.styleable.ChartView_setDashColor, Color.WHITE);
xScale = ta.getInt(R.styleable.ChartView_setXScale,5);
yScale = ta.getInt(R.styleable.ChartView_setYScale,5);
ta.recycle();
Log.i("wk","coordinateSystemColor:"+ coordinateSystemColor +"\n coordinateSystemSize:"+ coordinateSystemSize +"\n"+"lineColor:"+ lineColor +"\n"+"lineSize:"+ lineSize);
}
public ChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getAttrs(context,attrs);
InitPaint();
}
//测量view
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//获取当前View的宽高
mViewWidth = w;
mViewHeight = h;
Log.i("wk","宽度:"+w);
Log.i("wk","高度:"+h);
}
//绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制X轴Y轴 以及原点
canvas.drawLine(Margin,mViewHeight-Margin, Margin,5, mPaintSys);
canvas.drawLine(Margin,mViewHeight-Margin,mViewWidth-5,mViewHeight-Margin, mPaintSys);
//绘制原点
canvas.drawCircle(Margin,mViewHeight-Margin,scalePointRadius,mPaintSysPoint);
//
/**
* 计算两个刻度之间的间距:
*
* 1.刻度的个数 = 传入坐标最大的坐标点/坐标轴间距
* 2.两个刻度之间的间距 = 屏幕的宽或高 /刻度的个数
*
*/
int num_x = X_MAX/xScale; //x轴上需要绘制的刻度的个数
mScreenXdistance = (mViewWidth- Margin *2)/(num_x*1f);
Log.i("wk","需要绘制的刻度个数==>"+num_x+"两个刻度间间隔:"+ mScreenXdistance);
int num_y = Y_MAX/yScale;
mScreenYdistance = (mViewHeight-Margin*2)/(num_y*1f);
Log.i("wk","需要绘制的刻度个数==>"+num_x+"两个刻度间间隔:"+ mScreenXdistance);
//绘制 X,y轴刻度标记
for(int i=0;i<pointList.size();i++){
canvas.drawCircle(Margin +(i* mScreenXdistance),mViewHeight-Margin,scalePointRadius,mPaintSysPoint);
canvas.drawCircle(Margin,mViewHeight-Margin-(i* mScreenYdistance),scalePointRadius,mPaintSysPoint);
//计算刻度字体的宽高
String index_x = String.valueOf(i*xScale);
String index_y = String.valueOf(i*yScale);
mPaintText.getTextBounds(index_x,0,index_x.length(),mXBound);
mPaintText.getTextBounds(index_y,0,index_x.length(),mYBound);
int indexWidth_x = mXBound.width();
int indexHeight_x = mXBound.height();
int indexWidth_y = mYBound.width();
int indexHeight_y = mYBound.height();
Log.i("wk","字体的宽度:"+indexWidth_x+"字体的高度:"+indexHeight_x);
canvas.drawText(index_x, Margin +(i* mScreenXdistance),mViewHeight-Margin+indexHeight_x+indexHeight_x/2,mPaintText);
if(i!=0) {
canvas.drawText(index_y, Margin - indexHeight_y-indexWidth_y/2, mViewHeight - Margin - (i * mScreenYdistance), mPaintText);
}
}
/**
* 绘制折线
*/
Point LastPoint = new Point(); //记录上一个坐标点
LastPoint.x = Margin;
LastPoint.y = mViewHeight-Margin;
for(int i=1;i<pointList.size();i++){
/**
* 计算绘制点的坐标位置
* 绘制点的坐标 = (传入点的的最大的xy坐标/坐标轴上的间隔) * 坐标间隔对应的屏幕上的间隔
*/
// canvas.drawCircle(LastPoint.x,LastPoint.y,4f,mPaintPoint);
//计算出脱离坐标系的点所处的位置
float point_x = (pointList.get(i).x/(xScale*1f))* mScreenXdistance;
float point_y = (pointList.get(i).y/(yScale*1f))* mScreenYdistance;
//坐标系内的点的位置
float startX = LastPoint.x;
float startY = LastPoint.y;
float endX = Margin +point_x;
float endY = mViewHeight-Margin-point_y;
//需要计算此处
canvas.drawLine(startX,startY,endX,endY,mPaintLine);
//记录上一个坐标点的位置
LastPoint.x = (int) endX;
LastPoint.y = (int) endY;
if(isShowDash) {
//绘制横向虚线
canvas.drawLine(Margin, mViewHeight - Margin - point_y -lineColorPointRadius/2, Margin + point_x - lineColorPointRadius/2, mViewHeight - Margin - point_y -lineColorPointRadius/2, mPaintDash);
//绘制竖向虚线
canvas.drawLine(LastPoint.x, LastPoint.y, LastPoint.x, mViewHeight - Margin - lineColorPointRadius, mPaintDash);
}
canvas.drawCircle(LastPoint.x, LastPoint.y, lineColorPointRadius, mPaintLinePoint);
}
}
//测量view高度
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int specSize = MeasureSpec.getSize(heightMeasureSpec); //获取高的高度 单位 为px
int specMode = MeasureSpec.getMode(heightMeasureSpec);//获取测量的模式
//如果是精确测量,就将获取View的大小设置给将要返回的测量值
if(specMode == MeasureSpec.EXACTLY){
Log.i("wk","高度:精确测量 + specSize:==>"+specSize);
result = specSize;
}else{
Log.i("wk","高度:UNSPECIFIED + specSize:==>"+specSize);
result = 400;
//如果设置成wrap_content时,给高度指定一个值
if(specMode == MeasureSpec.AT_MOST){
Log.i("wk","高度:最大值模式 + specSize:==>"+specSize);
result = Math.min(result,specSize);
}
}
return result;
}
//测量view宽度
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int specSize = MeasureSpec.getSize(widthMeasureSpec); //获取高的高度
int specMode = MeasureSpec.getMode(widthMeasureSpec);//获取测量的模式
//如果是精确测量,就将获取View的大小设置给将要返回的测量值
if(specMode == MeasureSpec.EXACTLY){
Log.i("wk","宽度:精确测量 + specSize:==>"+specSize);
result = specSize;
}else{
Log.i("wk","宽度:UNSPECIFIED + specSize:==>"+specSize);
result = 400;
//如果设置成wrap_content时,给高度指定一个值
if(specMode == MeasureSpec.AT_MOST){
Log.i("wk","宽度:最大值模式 + specSize:==>"+specSize);
result = Math.min(result,specSize);
}
}
return result;
}
}
attrs.xml
自定义属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ChartView">
<!--设置坐标系的颜色-->
<attr name="coordinateSystemColor" format="color" />
<!--设置坐标系线条的尺寸-->
<attr name="coordinateSystemLineSize" format="dimension" />
<!--设置刻度点的颜色-->
<attr name="scalePointColor" format="color" />
<!--设置刻度点的半径-->
<attr name="scalePointRadius" format="dimension" />
<!--设置折线的颜色-->
<attr name="lineColor" format="color" />
<!--设置着折线的宽度-->
<attr name="lineSize" format="dimension" />
<!--设置折线点的颜色-->
<attr name="linePointColor" format="color" />
<!--设置折线点的半径-->
<attr name="linePointRadius" format="dimension" />
<!--设置是否显示虚线-->
<attr name="showDash" format="boolean" />
<attr name="setDashSize" format="dimension"/>
<attr name="setDashColor" format="color"/>
<!--设置坐标轴上刻度的间隔-->
<attr name="setXScale" format="integer" />
<attr name="setYScale" format="integer" />
</declare-styleable>
</resources>
activity_main.xml
<com.merpyzf.studymultimedia.ChartView
android:id="@+id/myChartView"
android:layout_marginTop="20dp"
android:background="@color/colorPrimary"
android:layout_width="match_parent"
android:layout_height="600dp"
app:linePointColor="@color/colorAccent"
app:scalePointColor="#f76"
app:linePointRadius="5dp"
app:lineColor="#fff600"
app:lineSize="2dp"
app:coordinateSystemColor="#000000"
app:coordinateSystemLineSize="2dp"
app:scalePointRadius="6dp"
app:setDashSize="1dp"
app:showDash="true"
/>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private MyService.MyBinder myBinder;
private ChartView MyChartView;
private ArrayList<Point> pointList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyChartView = (ChartView) findViewById(R.id.myChartView);
Random random = new Random();
pointList = new ArrayList<Point>();
for(int i=0;i<=10;i++){
Point p = new Point(i*5,random.nextInt(30));
pointList.add(p);
}
//给ChartView设置坐标
MyChartView.setPoint(pointList);
}
}
后面要花一段时间仔细的研究一下自定义View的知识,当然上面的这个折线图只是脑子一热敲出来的,不具备实用能力(⊙o⊙)…