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 深度图 的相关文章

  • 如何获取之前的碎片?

    为了在我的应用程序中重用某些片段 我需要知道哪个片段是返回堆栈上的第二个片段 为了做到这一点 我正在使用getFragmentManager getFragments 显示以下错误 但有效 FragmentManager getFragme
  • Android 在打开应用程序时会广播吗?

    例如 如果我想知道Youtube何时打开 是否有与之相关的广播 我当然知道我可以轮询 logcat 消息来检查活动 但我可以通过广播来做到这一点吗 因为它会少得多的耗电 此链接似乎表明这是不可能的 如何跟踪 Android 中的应用程序使用
  • Twitter 登录说明

    我想在 Android 中创建一个 Twitter 应用程序 为此 我想创建一个登录页面并登录到 Twitter 为此 我们需要消费者密钥和消费者密钥 这是什么意思 要创建此登录页面 除了 Twitter 帐户之外 我们还需要其他任何东西吗
  • FTS3 在 ORMLite 中搜索?

    我对 FTS3 一无所知 除了http developer android com guide topics search search dialog html http developer android com guide topics
  • Android 上的 SVG 支持

    Android 支持 SVG 吗 有什么例子吗 最完整的答案是这样的 Android 2 x 默认浏览器本身不支持 SVG Android 3 默认浏览器支持 SVG 要将 SVG 支持添加到 2 x 版本的平台 您有两个基本选择 安装功能
  • 如何在React Native Android中获取响应头?

    您好 我想在获取 POST 请求后获取响应标头 我尝试调试看看里面有什么response with console log response 我可以从以下位置获取响应机构responseData但我不知道如何获取标题 我想同时获得标题和正文
  • 如何解决 Firebase AuthUi 中无法找到显式活动?

    使用 firebase UI 时 我无法找到显式活动类 com firebase ui auth KickoffActivity protected void onCreate Bundle savedInstanceState super
  • Android在排序列表时忽略大小写

    我有一个名为路径的列表 我目前正在使用以下代码对字符串进行排序 java util Collections sort path 这工作正常 它对我的 列表进行排序 但是它以不同的方式处理第一个字母的情况 即它用大写字母对列表进行排序 然后用
  • android EditText 输入类型用于 StreetNumber 字段

    我试图在地址对话框的 streetNumber 字段中选择正确的 inputType 我想先显示数字键盘 然后让用户输入字母字符 对于一些非常特殊的情况 更接近这个的是 inputType datetime 但这不允许输入字母字符 那么如何
  • 如何检查 Android 中连接的 wifi 网络是否处于活动状态

    如何自动检查android中连接的WiFi网络上的互联网是否处于活动状态 我可以检查 wifi 是否已启用或 wifi 网络是否已连接 但我不确定如何检查互联网是否已连接 这可能吗 private boolean connectionAva
  • Android 相机未保存在特定文件夹 [MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA]

    当我在 Intent 中使用 MediaStore INTENT ACTION STILL IMAGE CAMERA 时遇到问题 相机正常启动 但它不会将文件保存在我的特定文件夹 photo 中 但是当我使用 MediaStore ACTI
  • PhoneStateListener 不调用

    这是我的完整代码 广播示例 java package com example broadcast gt import android app Activity import gt android content Context import
  • 如何在 Viewpager 中禁用预加载下一页? [复制]

    这个问题在这里已经有答案了 如何在 Viewpager 中禁用页面预加载 I tried viewPager setOffscreenPageLimit 0 但它不起作用 用这个viewPager setOffscreenPageLimit
  • 如何修改 Skobbler 注释而不重新添加它

    我必须修改 SKAnnotation 的图像 注释生成器代码 private SKAnnotation getAnnotationFromView int id int minZoomLvl View view SKAnnotation a
  • 如何在android asynctask中使用inputstream作为参数?

    我正在制作一个 Android 应用程序来跟踪股票详细信息 我将通过 csv 雅虎财经 检索数据 据我所知 在android 4 0中 网络连接无法在主线程上完成 因此 我将使用 asynctask 来建立连接 但是 我在参数方面遇到了一些
  • 如何在 Android 中使用 C# 生成的 RSA 公钥?

    我想在无法假定 HTTPS 可用的情况下确保 Android 应用程序和 C ASP NET 服务器之间的消息隐私 我想使用 RSA 来加密 Android 设备首次联系服务器时传输的对称密钥 RSA密钥对已在服务器上生成 私钥保存在服务器
  • Android:AsyncTask ProgressDialog 将不会在 ActivityGroup 中打开

    我试图在轮询我的服务器时打开一个进度对话框 该类是一个 ActivityGroup 因为它嵌套在选项卡栏中 为了将视图保持在框架内 需要 ActivityGroup 这是我的 ActivityGroup 类的声明 public class
  • 使用单选按钮更改背景颜色 Android

    我试图通过从单选组中选择单选按钮来更改应用程序选项卡的背景 但是我不确定如何执行此操作 到目前为止我已经 收藏夹 java import android app Activity import android os Bundle publi
  • Android 中带有组的列表视图

    我有一个列表视图 每行都有一些日期和文本 我可以像 iPhone 中那样将这个 listView 分组 组之间有标题吗 在 android 中是否可能 请帮忙 即 我需要在 Listview 行之间有标题栏 以便如果我使用日期对其进行分组
  • 如何在片段中实现 onBackPressed() 和意图?

    我知道 onBackPressed 是活动中的一种方法 但是 我想在片段中使用该功能 以便当按下后退按钮时 它会通过 Intent 重定向到另一个活动 有什么办法解决这个问题吗 public class News Events fragme

随机推荐

  • Xfce,KDE和Gome之间的自由切换。

    今天尝试了一下ubuntu 43 xfce 但是发现xfce虽然轻小 xff0c 但是反映和表现并不好 在兼容性上存在很大的问题 于是决定卸掉xfce 鉴于之前使用gnome和uity比较长久的历史 xff0c 今天决定尝试下kde 首先需
  • SimuLink示波器设置多路输入

    PS xff1a matlab版本 xff1a Matlab2019a step1 xff1a 打开 Simulink Library Browser xff0c 搜索 scope xff08 示波器 xff09 xff0c 拖拽入仿真面板
  • Ubuntu22.04上安装Xilinix Vivado 2018.3

    OpenWiFi的默认编译仿真工具是Xilinx Vivado 218 3 在Ubuntu22 04 LTS上安装Xilinx Vivado 2018 3之前 xff0c 首先要安装如下的库 sudo apt get install lib
  • 将应用加到Ubuntu的Favorites

    Ubuntu可以将从它的repo安装的应用程序加到Favorites xff0c 从而方便使用 但有很多应用程序并不是这样安装的 xff0c 就不能直接加到Favorites 下面的方法可以将任何应用加到Favorites 首先生成一个de
  • Build OpenAirInterface

    OpenAirInterface is a famous open source LTE NR implementation Its buidling guide doc BUILD md says that it can be build
  • Android技巧:学习使用GridLayout

    GridLayout是一个非常强大的网格类布局 xff0c 它不但能像TableLayout那样 xff0c 实现网格类布局 xff0c 但它更为强大的地方在于每个Cell的大小可以横向或者纵向拉伸 xff0c 每个Cell的对齐方式也有很
  • 说说Android的MVP模式

    更多精彩博客 安卓应用开发是一个看似容易 xff0c 实则很难的一门苦活儿 上手容易 xff0c 看几天Java xff0c 看看四大组件咋用 xff0c 就能整出个不太难看的页面来 但是想要做好 xff0c 却是很难 系统框架和系统组件封
  • Android实战技巧:如何在ScrollView中嵌套ListView

    前几天因为项目的需要 xff0c 要在一个ListView中放入另一个ListView xff0c 也即在一个ListView的每个ListItem中放入另外一个ListView 但刚开始的时候 xff0c 会发现放入的小ListView会
  • Android实战技巧:ViewStub的应用

    在开发应用程序的时候 xff0c 经常会遇到这样的情况 xff0c 会在运行时动态根据条件来决定显示哪个View或某个布局 那么最通常的想法就是把可能用到的View都写在上面 xff0c 先把它们的可见性都设为View GONE xff0c
  • 深入浅出Windows BATCH

    1 什么是Windows BATCH BATCH也就是批处理文件 xff0c 有时简称为BAT xff0c 是Windows平台上的一种可执行脚本 xff0c 与 nix Linux和Unix 上的Shell脚本和其他的脚本 xff08 P
  • Android实战技巧:深入解析AsyncTask

    AsyncTask的介绍及基本使用方法 关于AsyncTask的介绍和基本使用方法可以参考官方文档和Android实战技巧 xff1a 多线程AsyncTask 这里就不重复 AsyncTask引发的一个问题 上周遇到了一个极其诡异的问题
  • 学习Kotlin,看这一篇就够了

    人生苦短 xff0c 要用Kotlin 这是一种对程序猿更为友好的语言 xff0c 可以减少开发者的工作量 xff0c 原本由开发者干的事情 xff0c 其实很多都可以由编译器实现了 xff0c 这是一种更为高级的语言 Java虽然严谨 x
  • Ubuntu 超给力终端 Terminator(包含超全快捷键列表)

    Terminator是Ubuntu上的一款拥有强大能力的终端app 文章目录 1 Terminator 特性2 Terminator 安装3 Terminator 快捷键一览 1 Terminator 特性 Terminator 与xter
  • Sed与AWK入门教程之Sed篇

    Sed和AWK是 nix命令行里面文本处理的神器 相当的强大 它们都是面向行的 或者说它们处理文本的方式都是一行接着一行的处理 从标准输入或者文件中读取内容 一行一行的执行脚本命令 然后打印输出到标准输出 直到文件结尾 EOF Sed Se
  • 老鸟的Python入门教程

    重要说明 这不是给编程新手准备的教程 如果您入行编程不久 或者还没有使用过1到2门编程语言 请移步 这是有一定编程经验的人准备的 最好是熟知Java或C 懂得命令行 Shell等 总之 这是面向老鸟的 让老鸟快速上手Python教程 为什么
  • MATLAB自编自适应中值滤波算法

    代码原理 xff1a 在噪声密度不是很大的情况下 xff08 根据经验 xff0c 噪声的出现的概率小于0 2 xff09 xff0c 使用中值滤波的效果不错 但是当噪声出现的概率比较高时 xff0c 原来的中值滤波算法就不是很有效了 只有
  • 利用Github免费搭建个人主页(个人博客)

    之前闲着 利用Github搭了个免费的个人主页 colin1994 xyz 这里是我的一个简单主页 xff0c 有兴趣的朋友可以访问看看 先不公开了 今天到家了 趁着闲着 写一篇相关教程记录自己搭建的过程 涉及 Github注册 Githu
  • SpringMVC的配置

    SpringMVC的配置 一 拦截器 1 拦截器的配置 SpringMVC中的拦截器用于拦截控制器方法的执行 SpringMVC中的拦截器需要实现HandlerInterceptor xff0c 拦截器需要重写三个方法 xff0c 注意pr
  • Shell 命令基础

    Shell 命令基础 1 命令格式 常见的 Linux 命令的格式如下 xff1a 命令名称 span class token punctuation span 选项 span class token punctuation span 参数
  • android 深度图

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