Android折线图,柱状图,股票走势图,基金走势图

相信折线图大家都不陌生,有些数据统计,基金股票都是通过通过折线图的方式去展示数据,接下来就介绍一种方法,去通过代码编写一个类似的功能。

首先看一下效果图

在这里插入图片描述

在这里插入图片描述
编写之前要了解一下怎么样去入手,首先从界面出发,看似复杂的一张折线图,如果放大就会发现其实是有一条条长度、大小不一的直线拼凑而成

在这里插入图片描述
在代码中直线的绘制比较简单了,一句代码而已,参数分别为 起始 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();

        //设置X轴数据的终止位置,无需设置起始位置,因为每条线的起始位置就是上一条线的终点位置
        int stopX = new SplittableRandom().nextInt(startX, startX+10);
        startX = stopX;
        t.setStopX(startX);


        //设置Y轴数据的终止位置,无需设置起始位置,因为每条线的起始位置就是上一条线的终点位置
        Random ra = new Random();
        int stopY =  ra.nextInt(tv_view.getMeasuredHeight()/2);
        t.setStopY(stopY);

        //给分时图设置数据
        mTimeList.add(t);
        tv_view.setViewData(mTimeList);
    }


    /**
     * 通过线程来动态设置View达到动画效果
     * */
    private final Thread thread = new Thread(){
        @RequiresApi(api = Build.VERSION_CODES.N)
        @Override
        public void run() {
            while(true){
                try {
                    //通过此开关进行伪停止添加数据
                    if (isSetData){
                        initData();
                    }
                    //如果数据终止X的值大于界面宽度停止线程或者其他操作
                    if (mTimeList.size() > 0 && mTimeList.get(mTimeList.size()-1).getStopX() > tv_view.getMeasuredWidth()){
                        //清除容器数据,重新添加
                        startX = 0;
                        mTimeList.clear();
                        //thread.stop(); //终止线程
                    }
                    /**休息时间*/
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };

}

TimeView

/**
 * 作者:zch
 * 时间:2022/3/18 9:37
 * 描述:自定义分时图View
 */
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);
        //Y轴的起止都在折线图的四分之一处开始,因为数据的范围是折线图的二分之一
        //这样数据波动位置就处于折线图居中位置
        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;
        //刷新界面 - 无需在UI线程,在工作线程即可被调用,invalidate()必须在UI线程
        postInvalidate();
    }
    /**
     *注释:
     *设置分时图背景和画笔颜色
     * p - 画笔颜色
     * c - 背景颜色
     * b - 折线下背景颜色
     */
    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

/**
 * 作者:zch
 * 时间:2022/3/18 10:04
 * 描述:分时图数据类
 */
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币下载,需要的去下载吧。。 传送门

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值