前言

之前我们已经把canvas的绘制部分搞定了,现在最关键的就剩下手势滑动了,想到手势滑动,我第一个想到的是Scroller,什么也别说了,赶紧搞起来。

正文

因为手势滑动的功能和canvas没有什么直接的联系,为了防止我手贱误改了之前的功能,所以我们新建一个基类:BaseScrollerView,专门实现和手势移动有关的功能,然后让CanvasChart继承BaseScrollerView就完美了。

手势处理肯定重写onTouchEvent方法,代码并不多,我就直接全部贴出来,一起分析:

/*** Created by li.zhipeng on 2018/5/3.*/
open class BaseScrollerView(context: Context, attributes: AttributeSet?, defStyleAttr: Int): View(context, attributes, defStyleAttr) {constructor(context: Context, attributes: AttributeSet?) : this(context, attributes, 0)constructor(context: Context) : this(context, null)companion object {/*** 摩擦系数,根据速度计算滑行的距离* */private const val FLING_COEFFICIENT = 25}/*** x轴的刻度间隔** 因为x轴是可以滑动的,所以只有刻度的数量这一个属性* */var xLineMarkCount: Int = 5/*** 数据适配器* */var adapter: BaseDataAdapter? = nullset(value) {field = valueinvalidate()value?.addObserver { _, _ ->// 当数据发生改变的时候,立刻重绘invalidate()}// 计算最大宽度calculateMaxWidth()}/*** 最大宽度,大于等于width* */protected var maxWidth: Int = 0/*** 是否能滑动* */private var canScroll: Boolean = false/*** 手指按下的X坐标*/private var xDown: Float = 0f/*** 手指移动的X坐标*/private var xMove: Float = 0f/*** 滚动器Scroller* */private val scroller: Scroller = Scroller(context)/*** 用于计算手指滑动的速度。*/private var velocityTracker: VelocityTracker? = null/*** 计算最大宽度* */private fun calculateMaxWidth() {// 得到数据的数量val count = adapter?.maxDataCount ?: 0maxWidth = if (count < xLineMarkCount) {canScroll = falsewidth} else {canScroll = truewidth / xLineMarkCount * count}}/*** 创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。** @param event* 右侧布局监听控件的滑动事件*/private fun createVelocityTracker(event: MotionEvent) {if (velocityTracker == null) {velocityTracker = VelocityTracker.obtain()}velocityTracker!!.addMovement(event)}/*** 获取手指在绑定布局上的滑动速度。** @return 滑动速度,以每秒钟移动了多少像素值为单位。*/private fun getScrollVelocity(): Float {velocityTracker!!.computeCurrentVelocity(1000)val velocity = velocityTracker!!.xVelocityreturn Math.abs(velocity)}/*** 回收VelocityTracker对象。*/private fun recycleVelocityTracker() {velocityTracker!!.recycle()velocityTracker = null}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)calculateMaxWidth()}/*** 重写手势* */@SuppressLint("ClickableViewAccessibility")override fun onTouchEvent(event: MotionEvent): Boolean {// 如果不能滑动,不处理手势滑动if (!canScroll) {return false}// 计算滑动的速度createVelocityTracker(event)when (event.action) {// 记录手指按下的坐标MotionEvent.ACTION_DOWN -> {xDown = event.rawX}// 手势移动MotionEvent.ACTION_MOVE -> {// 更新xDown的坐标if (xMove != -1f) {xDown = xMove}// 记录当前的x坐标xMove = event.rawX// 计算移动的位置val scrolledX = (xDown - xMove).toInt()// 如果已经滚动到最左边了,设置scrollTo 为0if (scrollX + scrolledX < 0) {scrollTo(0, 0)return true}// 如果已经滑动到最右边了,设置scrollTo 为宽度else if (scrollX + width + scrolledX > maxWidth) {scrollTo(maxWidth - width, 0)return true}scrollBy(scrolledX, 0)}// 手势抬起MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {val dx = calculateFlingDistance()// startScroll()方法来初始化滚动数据并刷新界面scroller.startScroll(scrollX, 0, dx, 0)invalidate()recycleVelocityTracker()// 重置配置信息reset()}}return true}/*** 手势结束后,重置一些信息* */private fun reset() {xMove = -1f}/*** 计算滑动的惯性距离* */private fun calculateFlingDistance(): Int {// 判断是左滑还是右滑val isLeft = (xMove - xDown) >= 0val velocity = getScrollVelocity()Log.e("lzp", "velocity is :$velocity")var dx = Math.abs(velocity / FLING_COEFFICIENT)if (isLeft) {dx = -dx}// 如果滑动到了最左边,if (scrollX + dx < 0) {dx = -scrollX.toFloat()}// 如果已经滑动到了最右边else if (scrollX + dx > maxWidth - width) {dx = maxWidth - width - scrollX.toFloat()}Log.e("lzp", "scrollX is :$scrollX")Log.e("lzp", "dx is :$dx")return dx.toInt()}override fun computeScroll() {// 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑if (scroller.computeScrollOffset()) {scrollTo(scroller.currX, scroller.currY)invalidate()}}}

我们把之前在CanvasChartView中定义的adapter和xLineMarkCount移动到这里,因为我们要计算图表的最宽宽度,如果数据的长度比最大刻度要大,图表是可以滑动的,图表宽度的计算公式:

chartWidth = width(宽度)/ xLineMarkCount(刻度数量)* count(要绘制的数据的长度)

这个宽度用来做边界检查,滚动到最右边就没有不需要再向右滚动了,那么可以scrollX的范围就是:[ 0, chartWidth - width]。

我们还使用了计算手势滑动速度的辅助类VelocityTracker,帮助我们在手势抬起的时候计算惯性滚动的距离,我这里很明显是偷懒了,用一种比较笨的方式来设置惯性滚动的值,不过后面我们会进行优化。

主要内容是Scroller的使用,我们重点看一下:

/*** Created by li.zhipeng on 2018/5/3.*/
open class BaseScrollerView(context: Context, attributes: AttributeSet?, defStyleAttr: Int): View(context, attributes, defStyleAttr) {.../*** 滚动器Scroller* */private val scroller: Scroller = Scroller(context).../*** 重写手势* */@SuppressLint("ClickableViewAccessibility")override fun onTouchEvent(event: MotionEvent): Boolean {// 如果不能滑动,不处理手势滑动if (!canScroll) {return false}// 计算滑动的速度createVelocityTracker(event)when (event.action) {// 记录手指按下的坐标MotionEvent.ACTION_DOWN -> {xDown = event.rawX}//MotionEvent.ACTION_MOVE -> {// 更新xDown的坐标if (xMove != -1f) {xDown = xMove}// 记录当前的x坐标xMove = event.rawX// 计算移动的位置val scrolledX = (xDown - xMove).toInt()// 如果已经滚动到最左边了,设置scrollTo 为0if (scrollX + scrolledX < 0) {scrollTo(0, 0)return true}// 如果已经滑动到最右边了,设置scrollTo 为宽度else if (scrollX + width + scrolledX > maxWidth) {scrollTo(maxWidth - width, 0)return true}scrollBy(scrolledX, 0)}// 手势抬起MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {val dx = calculateFlingDistance()// startScroll()方法来初始化滚动数据并刷新界面scroller.startScroll(scrollX, 0, dx, 0)invalidate()recycleVelocityTracker()// 重置配置信息reset()}}return true}override fun computeScroll() {// 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑if (scroller.computeScrollOffset()) {scrollTo(scroller.currX, scroller.currY)invalidate()}}}

当我们手势在MOVE的时候,直接调用View.scrollTo或者View.scrollBy就可以了:

scrollTo(x, y) : 的参数是绝对位置,滚动到x,y的位置;

scrollBy(x, y) : 的参数是相对位置,滚动到相对现在的位置,偏移x, y相应的坐标距离。

当时手势抬起的时候我们调用

scroller.startScroll(scrollX, 0, dx, 0)

通知scroller我们还要继续滚动:从scrollX到dx,为了完成滚动的效果,我们还需要重写实现computeScroll方法,否则是没有效果的,先判断是否有需要滚动的偏移值,然后直接滚动到scroller计算的x,y坐标。

最后看一下效果:

???,好像不太对啊,为什么我们的坐标轴也跟着一起滑动了?看来是整个View都滑动了,所以我们的Canvas画布也跟着一起滑动了,所以才出现了这么尴尬情况,刚才的成就感一扫而空。

总结

看来用Scroller这种形式是不行了,不过我们从中获得了非常宝贵的经验:

1、scrollerTo,scrollerBy会移动Canvas;

2、View上只能看到做多6个点,但是我们把所有的点都画出来了,性能上可能是个坑;

如果我们不用Scroller,而是自己去控制Canvas的偏移呢,例如画坐标轴时Canvas不偏移,画虚线和数据的时候再根据ScrollX偏移Canvas?

嗯,这个方法可行,而且计算偏移值的逻辑我们已经写好了,只是要修改scrollTo,scrollBy,scroller有关的地方就可以了,那么下一篇我们来实现刚才总结的第二套方案。

github下载地址(本文的内容请看view包中的类)

图表CanvasChartView(二):手势滑动方案一相关推荐

  1. 图表CanvasChartView(四):基于方案二的优化

    前言 之前我们已经讨论并实现了两种实现滑动的方案,最终第二种实现了我们想要的效果,今天我们对方案二优化一下,让我们的CanvasChartView体验起来更屌. 都有哪些地方需要优化呢: Fling效 ...

  2. Android-通过SlidingMenu高仿微信6.2最新版手势滑动返回(二)

    转载请标明出处: http://blog.csdn.net/hanhailong726188/article/details/46453627 本文出自:[海龙的博客] 一.概述 在上一篇博文中,博文 ...

  3. 一、uniapp项目(封装异步请求、moment.js时间处理、封装手势滑动组件、下载图片到本地)

    一.封装异步请求: 1. 为什么要封装? 2. 封装的思路 export default (params) => {// 显示加载中uni.showLoading({title: "加 ...

  4. vue手势滚动_vue-router 手势滑动触发返回功能

    vue-router的路由变换只存在"变换前"和"变换后",不存在"切换中"的状态,所以做不到大多数app(微信那样的)在滑动过程中让界面跟 ...

  5. vue手势滚动_Vue-router 手势滑动触发返回功能_晴枙_前端开发者

    微博的滑动返回基本上就是这样的原理:先滑动.再触发返回事件,但用起来很是怪异,有严重的滞后感.夸克浏览器做的就比较好:一是滑动时界面虽然不动,但是界面上有小图标提示,能让用户接受到反馈:二是返回过程很 ...

  6. Tab页面手势滑动切换以及动画效果

    . 3张页卡之间的切换.带动画效果. 工程结构. 主要应用到android-support-v4.jar这个jar包. 布局文件. 1.main.xml中的代码 [html] <?xml ver ...

  7. boostrap 鼠标滚轮滑动图片_BootStrap 轮播插件(carousel)支持左右手势滑动的方法(三种)...

    Bootstrap 轮播(Carousel)插件是一种灵活的响应式的向站点添加滑块的方式.除此之外,内容也是足够灵活的,可以是图像.内嵌框架.视频或者其他您想要放置的任何类型的内容. 因为最近开发的项 ...

  8. android 横向滑动事件,android左右手势滑动事件处理

    建了个交流群:416157653,欢迎大家加入讨论 要实现手指在屏幕上左右滑动的事件需要实例化对象GestureDetector,new GestureDetector(MainActivity.th ...

  9. boostrap 鼠标滚轮滑动图片_Bootstrap幻灯片轮播图支持触屏左右手势滑动的实现方法...

    最近ytkah在学习用bootstrap搭建网站,Bootstrap能自适应pc端和手机端,并且移动设备优先,适合现如今移动营销.bootstrap是封装好的框架,需要某些功能只需调用相应的组件就可以 ...

最新文章

  1. 链表删除最小值,倒叙
  2. 线程间的通信 共享数据安全问题
  3. 在浏览器中内嵌word_关于项目浏览器内核的选取解读
  4. union和union all有什么区别_Django基础(29):select_related和prefetch_related的用法与区别...
  5. 天地与我并存/万物与我为一 2
  6. django与mysql实现增删_django与mysql实现简单的增删查改
  7. Gson的使用,对于不需要html escape的情况的处理
  8. 英语笔记:写作:Nothing succeeds without a strong will
  9. macOS Big Sur 11.1更新了!苹果macOS Big Sur 11.1正式版发布
  10. Linux centos 使用yum安装MySQL
  11. Mike Novogratz:比特币在未来几年内将继续大幅上涨
  12. php手工注入拿webshell
  13. android 没有gen文件,关于eclipse:对于Android项目,Gen文件夹为空
  14. linux certutil删除命令
  15. 简易语音助手—python
  16. 小学生python游戏编程3----拼图游戏-准备
  17. 【虚拟机】VirtualBox 安装 Windows 11 虚拟机简介
  18. 【rmzt】小清新美女win7主题_7.14
  19. 用javaScript制作星空特效
  20. 网络用户管理系统php,php之用户管理系统的实现!(从简单到复杂)

热门文章

  1. 武汉新时标文化传媒有限公司抖音电商探索全场景带货新模式
  2. 石油化工大学计算机专业排名,2019辽宁石油化工大学专业排名
  3. 黑马 Spring_day01
  4. 消息队列实践一之RabbitMQ消息推送(解决服务器错误:Whoops! Lost connection to ws://localhost:15674/ws)
  5. killProcesses
  6. The kernel appears to have died. It will restart automatically问题
  7. 展讯8910DM:LED驱动调试,支持一线脉冲调节
  8. SQL Server 2000 (SP4)笔记整理(二):数据库表
  9. android音频声调,Android自定义带拼音音调Textview
  10. 路由器连接显示多重网络连接服务器,电脑出现多重网络的原因及解决方法(图)...