相信折线图大家都不陌生,有些数据统计,基金股票都是通过通过折线图的方式去展示数据,接下来就介绍一种方法,去通过代码编写一个类似的功能。
首先看一下效果图
编写之前要了解一下怎么样去入手,首先从界面出发,看似复杂的一张折线图,如果放大就会发现其实是有一条条长度、大小不一的直线拼凑而成
在代码中直线的绘制比较简单了,一句代码而已,参数分别为 起始 x、y坐标和结束 x、y 坐标 通过一组组不同的数据不就能拼凑成一张看似复杂的折线了吗,艾玛,讲完了。
其实这里面还是有许多需要注意的地方的,不妨接着看下去。
上面讲完了折线,再讲一下折线下的阴影部分,其实这也没有太复杂,只是不同的绘画,同折线频率一样,一次次拼凑而成。实际中间空隙是不存在的,当然也要看代码中怎么写了,这里添加空隙是为了更直观些。 阴影部分的代码部分也不是很复杂。。和直线差不多,就是需要四条线去画出范围,然后通过 .close() 方法将范围内进行填充 以上就是对界面的解析,接下来是对逻辑的解析。
通过上面的解析图可以看出,每次画线和画背景的时候,下一次的开始坐标都是上一次的结束坐标,这也说明每次数据更新时只需要一组 x、y 数据即可,所以在计算时只需拿到这组数据就行,首先要建一个实体类了 entity,来定义一下 x、y 坐标,这里只写了需要用到的数据。
因为数据展示一般都是即时展示,比如股票、基金,目前股票是三秒进行一次数据刷新,每次刷新都进行一次 onDraw() 进行绘画,就形成了我们看到的分时图实时刷新的效果,这里通过线程每一秒进行一次数据刷新达到类似效果。
因为数据是上下波动的,这里通过随机数的形式进行数据伪造。 要明白一点,折线图是一直向前展示的,所以 X 轴是一直比上一次的数据要大的(如果是反向折线图,那相反),数据也要尽可能与我们的界面相配合,不能说我们的界面高度为 100,你给我的数据是 200,那界面不就混乱了吗,需要对返回的数据进行适配,比如说股票是有涨停与跌停的,当达到某极限值时设置Y轴为当前界面最大值即可。或者规定给我的数据在我界面的承受范围内,在此基础上进行改造。
数据有了,接下来就要进行绘制了,将折线数据给折线View之后,拿到数据之后进行界面重新绘制,这里注意一下哦,不是讲过下一直线的起始坐标为上个直线的结束坐标么,第一条直线的坐标呢,他前面似乎没有直线,不要把第一条线的起始坐标设置为 ( 0,0 )哦,否则你会发现一开始会有一个飞流直下的线,那咋办,还没开始结束了。 你可以把第一条直线想想成一个点,因为最开始是从没有数据到有了第一条数据,那这个第一条数据是开始也是结束。 具体表现在代码中如下: 直线完成了,背景部分也差不多,这里我将背景单独拿出来,根据需求进行判断是否绘制,和其他参数的修改,具体表现在: 这里不用担心,单独设置之后会不会有效果,因为在赋值之后有一个刷新操作,会重新拿起设置的这些值。
为了更直观些,将折线下背景逻辑画出来,加深一下印象,从第一个坐标开始,到第二个,第三个,第四个,最后别忘了回到第一个,然后将该范围内进行颜色填充,就形成了印象部分
以上就是走势图的大体逻辑,接下来将源码贴上,供需要的同学进行参考
目录结构 MainActivity
public class MainActivity extends AppCompatActivity {
private ArrayList<TimeViewEntity> mTimeList = new ArrayList<>();
private TimeView tv_view;
int startX = 0;
boolean isSetData = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_view = findViewById(R.id.tv_view);
tv_view.setViewColor(Color.YELLOW,Color.GRAY,R.color.purple_200);
tv_view.setShowBgBottom(false);
tv_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
isSetData = !isSetData;
}
});
thread.start();
}
@RequiresApi(api = Build.VERSION_CODES.N)
private void initData(){
TimeViewEntity t = new TimeViewEntity();
int stopX = new SplittableRandom().nextInt(startX, startX+10);
startX = stopX;
t.setStopX(startX);
Random ra = new Random();
int stopY = ra.nextInt(tv_view.getMeasuredHeight()/2);
t.setStopY(stopY);
mTimeList.add(t);
tv_view.setViewData(mTimeList);
}
private final Thread thread = new Thread(){
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void run() {
while(true){
try {
if (isSetData){
initData();
}
if (mTimeList.size() > 0 && mTimeList.get(mTimeList.size()-1).getStopX() > tv_view.getMeasuredWidth()){
startX = 0;
mTimeList.clear();
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
TimeView
public class TimeView extends View {
private Paint paint;
private int mCanvasHeight;
private int mCanvasWidth;
private int mStartY;
private int paintColor = 0;
private int canvasColor = 0;
private int bgColor = 0;
private boolean isShowBg = false;
private ArrayList<TimeViewEntity> mTimeList = new ArrayList<>();
public TimeView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint = new Paint();
paint.setAntiAlias(true);
mCanvasHeight = getMeasuredHeight();
mCanvasWidth = getMeasuredWidth();
paint.setColor(paintColor == 0 ? Color.YELLOW : paintColor);
canvas.drawColor(canvasColor == 0 ? Color.GRAY : canvasColor);
paint.setStrokeWidth((float)3);
mStartY = mCanvasHeight / 4;
drawView(canvas);
}
private void drawView(Canvas canvas){
if (mTimeList.size() > 0){
int startX = 0,startY = 0;
int stopX,stopY;
for (int i = 0; i < mTimeList.size(); i++){
TimeViewEntity t = mTimeList.get(i);
stopY = t.getStopY() + mStartY;
stopX = t.getStopX();
if (i == 0){
startX = stopX;
startY = stopY;
}
canvas.drawLine(startX,startY,stopX,stopY,paint);
if (isShowBg){
setDrawBg(startX,startY,stopX,stopY,canvas);
}
startX = stopX;
startY = stopY;
}
}
}
private void setDrawBg(int tx,int ty,int px,int py,Canvas canvas){
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(bgColor == 0 ? Color.WHITE : bgColor);
Path bg = new Path();
bg.moveTo(tx, ty);
bg.lineTo(tx, mCanvasHeight);
bg.lineTo(px, mCanvasHeight);
bg.lineTo(px, py);
bg.lineTo(tx, ty);
bg.close();
canvas.drawPath(bg, paint);
}
public void setViewData(ArrayList<TimeViewEntity> m) {
this.mTimeList = m;
postInvalidate();
}
public void setViewColor(int p,int c,int b){
this.paintColor = p;
this.canvasColor = c;
this.bgColor = b;
}
public void setShowBgBottom(boolean b){
this.isShowBg = b;
}
}
TimeViewEntity
public class TimeViewEntity {
private int stopX;
private int stopY;
public int getStopX() {
return stopX;
}
public void setStopX(int stopX) {
this.stopX = stopX;
}
public int getStopY() {
return stopY;
}
public void setStopY(int stopY) {
this.stopY = stopY;
}
}
activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.example.fsview.TimeView
android:id="@+id/tv_view"
android:layout_width="match_parent"
android:layout_height="200dp"/>
</LinearLayout>
以上就是走势图基本用法,千篇一律,下面是在以上基础上做了一些改动,看一下效果图:
这里就不贴代码了,代码写法与上面的基本相似,全部代码上传到 资源了,免C币下载,需要的去下载吧。。 传送门
|