android自定义图表的手势处理 - ViewTransHelper工具类

       本文介绍自己写的一个处理手势的工具类ViewTransHelper,可以方便的放在自己的图表中来处理手势。用内置封装好的InsetTransView或者OutsetTransHelper更加简单,解耦,不像第三方库里面图表的手势部分与其它部分耦合。ViewTransHelper适合自定义图表,或者查看图片,支持tap、double tap、多点scale、drag、fling。后面有效果图。

       项目地址:https://github.com/fornana/ViewTransHelper

       开发中碰到图表的情况还是很多的,但是一般比较尴尬的是,项目里面只需要折线图或者柱状图,需求简单。如果是饼图还好,相对简单一些。折线图和柱状图就不好办了。用第三方库例如MPAndroidChart,稍微大了点。而且如果细节部分比较特别,需要根据MPAndroidChart来定制,也不是那么方便的。也就是说,ui设计的图表类型单一,但是效果上又稍有特色的时候。直接使用MPAndroidChart,第一是大了点,第二是细节部分不好处理。

       这个时候选择自定义怎么样?常见的做法是直接套一层HorizontalScrollView,然后一次性把数据全部绘制。两个问题:一次性绘制所有数据,但是只显示部分,效率低;手势单一,不能缩放。所以,要想自定义稍微好点,就必须自己处理手势,简单点就只处理左右滑动。难点就只能使用第三方库了。第三方库里面的手势部分往往与其具体图表关联太深,不好提取出来放在自己的图表里使用。所以,写了ViewTransHelper,还有两个对ViewTransHelper的封装,InsetTransView和OutsetTransHelper。它们都与图表没有关联,只处理手势部分。

       继续说自定义图表,首先要面对手势判断,然后是图表绘制,而绘制是需要坐标的,特别是计算滑动缩放以后应该绘制哪部分数据,剩下的怎么封装数据之类的比较好处理。而且如果只是应付一般的小项目里面的图表,在不用考虑手势的情况下处理起来就更简单了。归纳一下,自定义图表主要难点:手势判断、坐标计算。以下对其进行分析,并给出解决方法。

一、手势判断

      像图表里面碰到的这种问题,我们在图片查看的需求里面也会碰到。查看图片需要能够double tap放大,缩放,拖拽。和这里是一致的。这个已经有很好的方法了,例如PhotoView,SubsamplingScaleImageView。当然,将要提到的ViewTranHelper也可以用来做查看图片的功能。

      那么,PhotoView是怎么处理这些手势的呢?缩放使用ScaleGestureDetector,滑动、double tap放大使用GestureDetector。github上面开源的图表部分也是这么做的。最初是想将PhotoView中手势部分直接提取出来放在图表中使用的。但是ScaleGestureDetector、GestureDetector都是通过设置callback的方式做的,而onTouchEvent是顺序型处理,这样ScaleGestureDetector与GestureDetector夹杂在一起很别扭,细节部分不好控制。所以没有采用。

       实际上,这个地方不好做的就是tap、double tap、scale。tap、double tap就交给GestureDetector,它也只处理这部分。剩下的就是scale比较难,drag、fling自行处理。至于scale,既然ScaleGestureDetector里面有,那就提取出来,而不是直接使用,就避免了callback。ViewTranHelper的scale部分是从ScaleGestureDetector(android4.4)里面提取出来的。

       之所以从ScaleGestureDetector里面提取,是因为它支持任意多点的控制,考虑了很多细节,官方的更值得信赖。其它的,例如MPAndroidChart的scale是作者自己写的,对于多点的处理不是很完美。测试一下多点,实际上一次性只有两个手指的移动对于缩放是有效的,而且多手指按下再放开以后缩放不能进行下去。ScaleGestureDetector在任意多点的缩放,多点按下松开,各种情况下都很好。

       ViewTransHelper是独立出来处理view手势的工具类,在它的基础上可以很简单的就自定义图表以及图片查看功能,集合了GestureDetector与ScaleGestureDetector的手势部分。ViewTransHelper的输入是event,输出是dx、dy,就是滑动偏移的大小,以及sx、sy,就是缩放的大小。

      ViewTranHelper的使用:

       transHelper = new ViewTransHelper(view,callback);

       transHelper.onTouchEvent(e);

       这里的callback接口方法如下:

      canDragHorizontal()、canDragVertical()、canScaleHorizontal()、canScaleVertical()指明是否能够滑动或者缩放。

      getScaleLevel(),返回值例如1.2f,这意味着double tap以后,放大1.2f。

      onScale(float sx,float sy,float px,float py),手指缩放时的缩放大小以及缩放中心。

      onDrag(int dx,int dy),手指滑动时,滑动的距离。

      onFling(int dx,int dy),手指滑动然后松开,此时如果速度达到要求就移动直到速度减为0,或者达到了边界点。

      就像ViewDragHelper一样,很简单的。但是看来还是觉得稍微麻烦还要设置callback。下面提供两个类对ViewDragHelper进行封装,然后,就可以不用设置callback就能处理手势了。

       在ViewTransHelper的callback中,可以想象我们根据输出的dx、dy、sx、sy去控制canvas上的图形的绘制。ViewTransHelper只是识别出手势,怎么移动,怎么缩放图形就不由它控制了。例如,我去控制某个方形移动,一般会给它的移动加上一个边界。怎么处理呢?InsetTransHelper和OutsetTransHelper就是做这个工作的,它们对ViewTransHelper进行封装,使用时设置需要变换的图形,输入是从ViewTransHelper传过来的dx、dy、sx、sy,输出则是变换后的图形以及一个matrix。

       这样如果你要控制canvas中的某个图形,不再需要callback。只要设置显示的viewport,图形的大小,图形最大最小宽高。然后在onDraw()中,获取当前变换后的图形绘制即可。详细的使用见示例InsetTransView、OutsetTransView。

InsetTransHelper效果如下:


InsetTransHelper的使用:

       transHelper = new InsetTransHelper(view);

       transHelper.setup(viewport,shapeWidth,shapeHeight,minWidth,minHeight);

       transHelper.onTouchEvent(e);

      这里viewport是显示的窗口为rect,指在view的哪里显示;shapeWidth、shapeHeight是希望控制的图形shape的宽高,它们起点是viewport的起点;minWidth、minHeight是最小宽高,因为能够缩放所以不能为0。所有的点坐标都是相对于canvas而言的。如果需要设置初始的shape,调用transHelper.setCurrentShape(rect)即可,rect也是相对于canvas坐标系而言的。

       InsetTransHelper保证不管怎么缩放都使得shape在viewport内部。

OutsetTransHelper的效果如下:


       OutsetTransHelper的使用类似,它保证任意时刻shape都是包裹viewport,任何变换下都如此。

       在图表中,我们需要显示的数据如果全部绘制出来就是一个长方形,viewport是显示的方框,手势滑动,就会显示相应的部分。任意时刻这个长方形都将viewport包裹在其中。OutsetTransHelper包裹viewport与图表中的情形是不是一模一样。使用OutsetTransHelper以后完全不需要自己处理手势,滑动缩放,边界判断之类的问题。而且它返回一个matrix,根据这个matrix可以计算出当前显示图表哪部分。

二、坐标计算

      前面提到一次性绘制全部的数据,但是,显示的只有那么几个。效率上存在问题,而且本身自定义图表在绘制的时候坐标计算就不好弄,现在又要做到显示多少绘制多少,就更麻烦了。MPAndroidChart是怎么做的呢?使用matrix。

       计算机图形学或者3d数学里面会讲到怎样对局部坐标系中的物体进行各种变换,然后将局部坐标系的物体变换到全局坐标系,再将其变换到viewport窗口部分,然后剪切显示。它就是通过matrix来做的(不考虑旋转)。这里所面对的问题是一样的,只是换成了2d。所以,使用matrix是可以计算出手指各种缩放、移动以后,图表的哪些数据是可见的。

       matrix来自于哪里呢?就是上面提到的InsetTransHelper、OutsetTransHelper中维护的matrix。

      所以,使用ViewTranHelper就可以解决图表中的两个难点。剩下的例如各种样式,各种效果,就比较方便了。后续将会分析如何绘制曲线图,主要是坐标计算以及OutsetTransHelper的使用,并使用OutsetTransHelper实现简单的折线图。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容