android 深度图

2023-05-16

在 Android开发中自定义控件是一个范围很广的话题,讲起自定义控件,从广度上来划分的话,大体上可以划分为:

  • View、ViewGroup的绘制
  • 事件分发
  • 各种动画效果
  • 滚动嵌套机制
  • 还有涉及到相关的数学知识等等

本次来讲讲如何实现交易所中的K线图,首先通过一张深度图开始讲解下相关业务需求
这里写图片描述

深度图一般代表交易所当前买入和卖出的委托量(不是指成交),从这张图我们可以看出X轴代表价格,价格从左往右依次增高,Y轴代表销量,由下往上递增,要绘制出正常的深度图,每次获取的数据至少包含价格和销量,通常的时间,开盘,收盘,高,低价都可忽略。实际开发中这块获取数据使用长链接即可,后台每次返回规定的买入和卖出的数据数量,要先处理后台的返回数据:

  • 首先将返回的买入和卖出的数据分开按照价格从低到高排序
  • 然后再去处理每个价格对应的委托量,因为返回的当前价格对应的委托量是无法对应深度图的坐标轴,我们需要将按价格排序后,高的价格去加上上一个价格的委托量,这样才可保证委托量的展示是在递增

处理完返回的数据后重新填充数据即可。处理完数据后就要开始处理交互方面了,当用户点击或者长按的时候就要展示当前选中点的相关数据了,从图中可以看到选中的时候有个圆圈以及在X,Y轴上展示了此坐标的价格和委托量。
先上效果图:
这里写图片描述

由于上传大小的限制,修改了gif的质量,所以效果不是很好。
通过上面的实现效果可以看到做了一点功能上的简化,长按之后我并没有将结果展示在X,Y轴上而是直接显示在中间,不过这些都是次要的,最重要的是理解思路,看了源码后可以根据自己的需求修改。

首先讲下从后台获取到数据的处理,先将数据按价格进行排序,然后通过遍历下集合,将每个bean对象的委托量的数值累加下上一个的然后重新赋值,同时获取买入和卖出的最高和最低价格,后面会用到数据展示

    public void setData(List<DepthDataBean> buyData, List<DepthDataBean> sellData) {
        float vol = 0;
        if (buyData.size() > 0) {
            mBuyData.clear();
            //买入数据按价格进行排序
            Collections.sort(buyData, new comparePrice());
            DepthDataBean depthDataBean;
            //累加买入委托量
            for (int index = buyData.size() - 1; index >= 0; index--) {
                depthDataBean = buyData.get(index);
                vol += depthDataBean.getVolume();
                depthDataBean.setVolume(vol);
                mBuyData.add(0, depthDataBean);
            }
            //修改底部买入价格展示
            mBottomPrice[0] = mBuyData.get(0).getPrice();
            mBottomPrice[1] = mBuyData.get(mBuyData.size() > 1 ? mBuyData.size() - 1 : 0).getPrice();
            mMaxVolume = mBuyData.get(0).getVolume();
        }

        if (sellData.size() > 0) {
            mSellData.clear();
            vol = 0;
            //卖出数据按价格进行排序
            Collections.sort(sellData, new comparePrice());
            //累加卖出委托量
            for (DepthDataBean depthDataBean : sellData) {
                vol += depthDataBean.getVolume();
                depthDataBean.setVolume(vol);
                mSellData.add(depthDataBean);
            }
            //修改底部卖出价格展示
            mBottomPrice[2] = mSellData.get(0).getPrice();
            mBottomPrice[3] = mSellData.get(mSellData.size() > 1 ? mSellData.size() - 1 : 0).getPrice();
            mMaxVolume = Math.max(mMaxVolume, mSellData.get(mSellData.size() - 1).getVolume());
        }
        mMaxVolume = mMaxVolume * 1.05f;
        mMultiple = mMaxVolume / mLineCount;
        invalidate();
    }

数据处理好后就要开始绘制了

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(mBackgroundColor);
        canvas.save();
        //绘制买入区域
        drawBuy(canvas);
        //绘制卖出区域
        drawSell(canvas);
        //绘制界面相关文案
        drawText(canvas);
        canvas.restore();
    }
    
    private void drawBuy(Canvas canvas) {
        mGridWidth = (mDrawWidth * 1.0f / (mBuyData.size() - 1 == 0 ? 1 : mBuyData.size() - 1));
        mBuyPath.reset();
        mMapX.clear();
        mMapY.clear();
        float x;
        float y;
        for (int i = 0; i < mBuyData.size(); i++) {
            if (i == 0) {
                mBuyPath.moveTo(0, getY(mBuyData.get(0).getVolume()));
            }
            y = getY(mBuyData.get(i).getVolume());
            if (i >= 1) {
                canvas.drawLine(mGridWidth * (i - 1), getY(mBuyData.get(i - 1).getVolume()), mGridWidth * i, y, mBuyLinePaint);
            }
            if (i != mBuyData.size() - 1) {
                mBuyPath.quadTo(mGridWidth * i, y, mGridWidth * (i + 1), getY(mBuyData.get(i + 1).getVolume()));
            }

            x = mGridWidth * i;
            mMapX.put((int) x, mBuyData.get(i));
            mMapY.put((int) x, y);
            if (i == mBuyData.size() - 1) {
                mBuyPath.quadTo(mGridWidth * i, y, mGridWidth * i, mDrawHeight);
                mBuyPath.quadTo(mGridWidth * i, mDrawHeight, 0, mDrawHeight);
                mBuyPath.close();
            }
        }
        canvas.drawPath(mBuyPath, mBuyPathPaint);
	}

    private void drawSell(Canvas canvas) {
        mGridWidth = (mDrawWidth * 1.0f / (mSellData.size() - 1 == 0 ? 1 : mSellData.size() - 1));
        mSellPath.reset();
        float x;
        float y;
        for (int i = 0; i < mSellData.size(); i++) {
            if (i == 0) {
                mSellPath.moveTo(mDrawWidth, getY(mSellData.get(0).getVolume()));
            }
            y = getY(mSellData.get(i).getVolume());
            if (i >= 1) {
                canvas.drawLine((mGridWidth * (i - 1)) + mDrawWidth, getY(mSellData.get(i - 1).getVolume()),
                        (mGridWidth * i) + mDrawWidth, y, mSellLinePaint);
            }
            if (i != mSellData.size() - 1) {
                mSellPath.quadTo((mGridWidth * i) + mDrawWidth, y,
                        (mGridWidth * (i + 1)) + mDrawWidth, getY(mSellData.get(i + 1).getVolume()));
            }
            x = (mGridWidth * i) + mDrawWidth;
            mMapX.put((int) x, mSellData.get(i));
            mMapY.put((int) x, y);
            if (i == mSellData.size() - 1) {
                mSellPath.quadTo(mWidth, y, (mGridWidth * i) + mDrawWidth, mDrawHeight);
                mSellPath.quadTo((mGridWidth * i) + mDrawWidth, mDrawHeight, mDrawWidth, mDrawHeight);
                mSellPath.close();
            }
        }
        canvas.drawPath(mSellPath, mSellPathPaint);
    }

上面的是主要的绘制代码块,代码逻辑就是先绘制出区域的边线,同时通过path类记录下相关的位置,遍历到最后一个对象时直接将path的路径首位相连,再去绘制path所记录的区域

绘制文案的代码需要注意下

    private void drawText(Canvas canvas) {
        float value;
        String str;
        for (int j = 0; j < mLineCount; j++) {
            value = mMaxVolume - mMultiple * j;
            str = getVolumeValue(value);
            canvas.drawText(str, mWidth, mDrawHeight / mLineCount * j + 30, mTextPaint);
        }
        int size = mBottomPrice.length;
        int height = mDrawHeight + mBottomPriceHeight / 2 + 10;
        if (size > 0 && mBottomPrice[0] != null) {
            String data = getValue(mBottomPrice[0]);
            canvas.drawText(data, mTextPaint.measureText(data), height, mTextPaint);
            data = getValue(mBottomPrice[1]);
            canvas.drawText(data, mDrawWidth - 10, height, mTextPaint);
            data = getValue(mBottomPrice[2]);
            canvas.drawText(data, mDrawWidth + mTextPaint.measureText(data) + 10, height, mTextPaint);
            data = getValue(mBottomPrice[3]);
            canvas.drawText(data, mWidth, height, mTextPaint);
        }
        if (mIsLongPress) {
            mIsHave = false;
            for (int key : mMapX.keySet()) {
                if (key == mEventX) {
                    mLastPosition = mEventX;
                    drawSelectorView(canvas, key);
                    break;
                }
            }
            //这里这么处理是保证滑动的时候界面始终有选中的感觉,不至于未选中的时候没有展示,界面有闪烁感,体验不好
            if (!mIsHave) {
                drawSelectorView(canvas, mLastPosition);
            }
        }
    }

以上是此自定义控件的主要代码,项目已上传至github,欢迎各位老铁们star,fork,此库定期更新一些自定义控件,相互交流学习。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android 深度图 的相关文章

  • 如何做Android - 滑动抽屉从左到右滑动..?

    如何将android滑动抽屉从LTR滑动 使用时android orientation horizontal 它从 RTL 滑出 我该如何进行 LTR 谢谢 我找到了答案表格 Here http blog sephiroth it 2011
  • android edittext中的字符映射

    我想让我的编辑文本就像我写字符 g 时一样 它是相关的映射自定义字符应该写成印地语中的 我认为应该有字符映射 但没有知识任何人都可以帮助我 怎么做 其他应用程序https play google com store apps details
  • 如何获取每个StorageVolume的可用大小和总大小?

    背景 谷歌 悲伤 计划破坏存储权限 https www xda developers com android q storage access framework scoped storage 这样应用程序将无法使用标准文件 API 和文件
  • 我在布局上看不到任何 FirebaseRecyclerAdapter 项目

    我试图将数据从 Firebase 数据库检索到我的布局 但我看不到任何项目FirebaseRecyclerAdapter在布局中 请帮忙 我按照一个教程展示了如何做到这一点 当我运行应用程序时 我没有看到任何项目 但我可以滚动 public
  • Cheesesquare:enterAlways 会产生错误的布局

    Adding enterAlways到 Cheesesquare 演示的滚动标志
  • 如何在android中显示保存在sdcard文件夹中的图像[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 当我正在显
  • 如何正确释放Android MediaPlayer

    我正在尝试向我的 Android 应用程序添加一个按钮 当点击该按钮时它会播放 MP3 我已经让它工作了 但没有办法释放 mediaPlayer 对象 因此即使在我离开活动后它仍然会继续播放 如果我在react 方法之外初始化MediaPl
  • 如何在 Linux 内核中定义并触发我自己的新软中断?

    我想在 Linux 内核中创建自己的软中断 这是正确的方法吗 In the init我想触发该模块的softirq我将添加一个调用 394 void open softirq int nr void action struct softir
  • 如何使用 Google Maps for Android V2 处理地图移动结束?

    我想在地图中心更改后立即对地址进行地理编码 如何使用新的 Android 版 Google 地图 V2 处理地图移动 我说的是用户用手指拖动地图的情况 查看新的地图 API Override public void onMapReady G
  • 将现有 VARCHAR 列与 Room 结合使用

    我正在尝试将现有的数据库与 Android Room 一起使用 但是 我的一个表有一个 VARCHAR 列 Room 似乎只支持 TEXT 不支持 VARCHAR 而且 sqlite 不允许修改列类型 那么 有没有办法使用Room中现有的带
  • Dialog.setTitle 不显示标题

    我正在尝试向我的对话框添加自定义标题 但是每当我运行我的应用程序时 它都不会显示标题 我创建对话框的代码是 final Dialog passwordDialog new Dialog this passwordDialog setCont
  • 以编程方式将文本颜色设置为主要 Android 文本视图

    如何设置我的文本颜色TextView to android textColorPrimary以编程方式 我已经尝试了下面的代码 但它将 textColorPrimary 和 textColorPrimary Inverse 的文本颜色始终设
  • 当它的父级是 ConstraintLayout 时设计 CardView 吗?

    我在编辑包含Relativelayout的Cardview内的RelativeLayout时搞砸了 ConstraintLayout会将相对布局的wrap content更改为0并添加工具 layout editor absoluteX 1
  • 当 OnFocusChangeListener 应用于包装的 EditText 时,TextInputLayout 没有动画

    不能比标题说得更清楚了 我有一个由文本输入布局包裹的 EditText 我试图在 EditText 失去焦点时触发一个事件 但是 一旦应用了事件侦听器 TextInputLayout 就不再对文本进行动画处理 它只是位于 editText
  • 从 BroadcastReceiver 类调用活动方法

    我知道我可以做一个内部接收器类来调用接收器中的任何方法 但我的主要活动太大了 要做的事情也很多 因此 我需要一个扩展广播接收器的类 但它不是内部类 并且可以从我的主要活动中调用一种方法 我不知道是否可能 但我的活动是家庭活动和 single
  • 对于一个单元格,RecyclerView onBindViewHolder 调用次数过多

    我正在将 RecyclerView 与 GridLayoutManager 一起使用 对于网格中的每个项目 我需要调用 REST api 来检索数据 然后 从远程异步获取数据后 我使用 UIL 加载 显示图像 一切似乎都很好 但我发现 on
  • 在命令行上卸载 Android SDK 的选定部分

    这与 卸载旧的 Android SDK 版本 https stackoverflow com questions 15182377 uninstall old android sdk versions 除非我想在无头 Linux CI 服务
  • Android - 以编程方式选择菜单选项

    有没有办法以编程方式选择菜单选项 基本上 我希望视图中的按钮能够执行与按特定菜单选项相同的操作 我正在考虑尝试调用 onOptionsItemSelected MenuItem item 但我不知道要为菜单项添加什么 是的 有一种方法可以选
  • 在 Android 应用程序资源中使用 JSON 文件

    假设我的应用程序的原始资源文件夹中有一个包含 JSON 内容的文件 我如何将其读入应用程序 以便我可以解析 JSON See 开放原始资源 http developer android com reference android conte
  • Android 后台倒计时器

    我有一个 Android 应用程序 它管理一个倒计时器 类 CountDownTimer 它显示在应用程序屏幕中 以显示到达 00 00 还剩多少时间 我现在的问题是 当我按主页按钮或启动另一个应用程序时 应用程序 计时器不会在后台运行 所

随机推荐

  • ros没装全,gazebo模型加载不出来

    ros没装全 xff0c gazebo模型加载不出来 安装ros 关于rosdep update 首先换热点试一下 xff0c 在来回切换wifi 按照https blog csdn net yufeng1108 article detai
  • catkin_make遇到 gazebo_ros_controlConfig.cmake相关的问题

    现象 xff1a Could not find a package configuration file provided by gazebo ros control with any of the following names 解决方法
  • 一些vscode自动提示报错

    Pointer to incomplete class type is not allowed 通常是由于类声明了但是没有定义造成的 xff0c 需要做的是在错误文件里面引用下这个类 xff1a class Test xff1b a poi
  • bazel一些用法

    1 编译东西 要在根目录下 xff0c 和WORKSPACE在同一级 例如 xff0c 对于bazelbuild examples 而BUILD文件一般与源文件并列 examples cpp tutorial stage1 main BUI
  • 第十一节std::atomic原子操作

    一 原子操作 1 1原子操作概念例子 互斥量 xff1a 多线程编程中保护共享数据 xff1a 锁 xff0c 操作共享数据 xff0c 开锁 有两个线程 xff0c 对一个变量进行操作 xff0c 这个线程读 xff0c 另一个线程往变量
  • 用Ceres实现PnP

    在ceres中实现PnP优化 xff08 仅优化位姿 xff09 视觉SLAM十四讲 课后习题 ch7 xff08 更新中 xff09
  • deque insert()函数几种用法

    C 43 43 deque insert 用法及代码示例
  • Matplotlib error: No such file or directory: ‘latex‘: ‘latex‘

    Matplotlib error No such file or directory latex latex span class token function sudo span apt span class token function
  • 在ROS下Intel RealSense D435i 驱动的安装,避免踩坑,避免缺少imu话题等各种问题(适用于D400系列、SR300和T265跟踪模块等)

    版权声明 本文为博主原创文章 未经博主允许不得转载 https blog csdn net AnChenliang 1002 article details 109454465 目录 背景 方法1 使用apt安装 不建议使用此方法 了解一下
  • wsl作为开发主机与开发板联调

    linux开发经历记录 wsl作为开发主机与开发板联调 uboot使用nfs网络挂载时使用hanewin搭建win10的nfs servers 背景介绍 小白学习linux开学 xff0c 不想用VM虚拟机作为开发平台 xff0c 恰好了解
  • weka中文乱码解决办法

    由于weka的默认字符集编码是Cp1252 xff0c 所以如果你导入的数据中有中文字符 xff0c 就会出现乱码的情况 xff0c 所以需要weka的RunWeka ini文件 将cp1252替换成你的数据对应的字符集编码 xff0c 比
  • 动态库和静态库的区别

    静态库 xff1a 这类库的名字一般是libxxx a xff1b 1 利用静态函数库编译成的文件比较大 xff0c 因为整个函数库的所有数据都会被整合进目标代码中 xff0c 他的优点就显而易见了 xff0c 2 即编译后的执行程序不需要
  • 重复数据删除技术(Data Deduplication)

    我相信所有人都会同意 xff0c 数据存储正在以飞快地 xff0c 甚至是令人震惊的速度在增长 这意味着为了不影响普通用户的正常使用 xff0c 存储管理员们不得不加班加点地在幕后 工作着 他们的鲜为人知的工作包括 xff1a 配额管理 x
  • TCP/IP协议

    TCP IP 协议栈是一系列网络协议的总和 xff0c 是构成网络通信的核心骨架 xff0c 它定义了电子设备如何连入因特网 xff0c 以及数据如何在它们之间进行传输 TCP IP 协议采用4层结构 xff0c 即应用层 传输层 网络层和
  • 趣谈网络协议-云计算中的协议

  • ros里Catkin的CMakelists/package.xml

    Catkin是基于CMake的编译构建系统 xff0c 具有以下特点 xff1a Catkin沿用了包管理的传统像 find package 基础结构 pkg config扩展了CMake xff0c 例如 软件包编译后无需安装就可使用 自
  • js根据坐标进行图片截图,获取图片上指定位置的截图

    根据坐标截取图片上指定的区域 xff0c 坐标可以是规则的图片截取 xff0c 也可以是不规则的图片截取 实现思路 xff1a 规则裁剪自然不用多说 xff0c 我们使用画布的getImageData x y width height 方法
  • 浅谈APM系列-----update_flight_mode(ModeAltHold)

    update flight mode xff08 ModeAltHold xff09 这里只看ModeAltHold 位置 xff1a X ardupilot ArduCopter mode cpp update flight mode c
  • 多态的总结

    对于多态 xff0c 可以总结以下几点 xff1a 一 使用父类类型的引用指向子类的对象 xff1b 二 该引用只能调用父类中定义的方法和变量 xff1b 三 如果子类中重写了父类中的一个方法 xff0c 那么在调用这个方法的时候 xff0
  • android 深度图

    在 Android开发中自定义控件是一个范围很广的话题 xff0c 讲起自定义控件 xff0c 从广度上来划分的话 xff0c 大体上可以划分为 xff1a View ViewGroup的绘制事件分发各种动画效果滚动嵌套机制还有涉及到相关的