你的浏览器禁用了JavaScript, 请开启后刷新浏览器获得更好的体验!
首页
热门
推荐
精选
登录
|
注册
一款“灵动”的滑动按钮
立即下载
用AI写一个
金额:
2
元
支付方式:
友情提醒:源码购买后不支持退换货
立即支付
我要免费下载
发布时间:2019-03-02
3人
|
浏览:1972次
|
收藏
|
分享
技术:Android+Kotlin
运行环境:AndroidStudio+SDK+JDK
概述
一款“灵动”的滑动按钮,为什么说灵动呢?因为很丝滑...
详细
> 前言:为什么起这个标题呢?因为,这个滑动按钮看起来不是那么的僵硬,哈哈。限于篇幅原因,不会把所有的知识点都讲解一遍,只会挑选一些需要注意的点及不太好理解的地方进行讲解。 ### 效果图 先放张最后实现效果图,大家可以看着这个效果,思考一下怎么实现的。 ![](https://user-gold-cdn.xitu.io/2019/3/2/1693c896e6d9129b?w=720&h=425&f=gif&s=488784) ### 主要讲解的内容 文章将会选择以下内容进行讲解 - 怎样让按钮随手指移动 - 处理越界问题的方法 - 怎样处理回弹(就是没有滑动到指定位置,返回到原点) - 怎样在滑动到指定位置后禁止滑动 - 让文字跟随按钮移动的方法 - 自定义View添加阴影的方法 这里会选择按钮初始位置在中间的这种情况来讲解,因为,按钮初始位置在左边的时候就是按钮位置在中间的时候一种状态。 ### 让按钮随手指移动 要想让按钮随着手指的移动而移动,就需要重写View的`onTouchEvent`方法,在这个方法中可以监听手指的几个动作,如手指的“按下”、“滑动”、“抬起”, 捕获到这些动作后,就可以针对每个动作做相应的处理,最终达到让按钮随手指移动的效果。具体的代码如下 ```kotlin override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { //手指按下 MotionEvent.ACTION_DOWN -> { val clickX = event.x } //手指移动 MotionEvent.ACTION_MOVE -> { slideX = event.x //刷新view postInvalidate() } //手指抬起 MotionEvent.ACTION_UP -> { } } return super.onTouchEvent(event) } ``` 简单解释一下上面的代码,在手指按下的时候,获取手机按下位置的坐标,用`clickX`变量保存按下的X轴的位置`(后面会用到)`,在手指滑动的时候用全局变量`slideX`来保存滑动到的X的坐标,然后刷新view。手指抬起的动作这里不用处理。上面的代码只是获取到了手指移动的X轴的坐标,下面就要通过View的`onDraw`方法来绘制中间按钮,代码如下 ```kotlin private fun drawSnake(canvas: Canvas) { //计算圆的半径 val circleRadius = mSnakeRadius.toFloat() - mShadowRadius / 2 //计算中间按钮的X坐标,在初始的时候,slideX的值为0 val circleCenter = if (slideX == 0f) { if (mSlideState == SlideState.INIT_LEFT) { //按钮位于左边的时候圆心的X坐标 mSnakeRadius + mShadowRadius / 2 } else { //按钮在中间的时候圆心的X坐标,mResultWidth为View的宽度 mResultWidth / 2.toFloat() } } else { //手指滑动后 slideX } //这里根据手指移动到的位置来确定圆形的X坐标 canvas.drawCircle(circleCenter, mResultHeight / 2.toFloat(), circleRadius, mSnakePaint) } ``` 上面的每行代码都进行了注释,这里就不再讲解每句代码的了。 ### 处理越界问题的方法 如果你是按上面的代码来让按钮跟随手指进行移动的话,当移动到边缘的时候你会发现移动的按钮超出背景了!那这是什么问题呢?因为,这里是把手指移动的位置的X坐标作为圆心的X坐标了,当手指移动到边缘的时候,圆心的X坐标也在边缘了,所以,就会出现按钮超出背景的问题了。那么该怎么解决这个问题呢?其实很简单,就是当手指移动的位置的X坐标大于View的总长度减去按钮半径长度的时候,将`slideX`变量重新赋值。代码如下 ```kotlin override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { //手指按下 MotionEvent.ACTION_DOWN -> { val clickX = event.x } //手指移动 MotionEvent.ACTION_MOVE -> { slideX = event.x if (slideX > mResultWidth - mSnakeRadius - mShadowRadius / 2) { //防止超出右边界 slideX = mResultWidth - mSnakeRadius-mShadowRadius / 2 } else if (slideX < mSnakeRadius + mShadowRadius / 2) { //防止超出左边界 slideX = mSnakeRadius+mShadowRadius / 2 } //刷新view postInvalidate() } //手指抬起 MotionEvent.ACTION_UP -> { } } return super.onTouchEvent(event) } ``` 从上面的代码中可以可看到,处理按钮超出边界的方法是,**当圆心的X的坐标将要大于或小于要求的坐标的时候,不直接让手指的X的坐标作为圆心的坐标了,而是重新计算圆心X轴的坐标。** ### 处理回弹 这部分要处理的就是,当按钮没有移动到目标位置时,抬起手指,是让按钮返回到初始的位置,还是让按钮到达目标位置。处理这个问题其实也很简单,就是需要事先设定一个位置,当抬起手指的时候判断圆心的X的坐标大于这个位置还是小于这个位置,如果大于就直接将按钮移到目标位置,如果小于就将按钮移动到初始位置。如下图 ![](https://user-gold-cdn.xitu.io/2019/3/2/1693d552813f9a79?w=358&h=171&f=png&s=17431) 这里规定的位置如上图,这个位置距离黑色背景边界的长度刚好等于按钮的半径。当往左边滑动时,手指抬起的时候,如果圆心的X坐标大于2倍的按钮的半径即直径的话,就回到初始位置,否则滑动到左边,往右边滑动时的判断同理。具体的代码如下 ```kotlin override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { //手指按下 MotionEvent.ACTION_DOWN -> { val clickX = event.x } //手指移动 MotionEvent.ACTION_MOVE -> { slideX = event.x if (slideX > mResultWidth - mSnakeRadius - mShadowRadius / 2) { //防止超出右边界 slideX = mResultWidth - mSnakeRadius-mShadowRadius / 2 } else if (slideX < mSnakeRadius + mShadowRadius / 2) { //防止超出左边界 slideX = mSnakeRadius+mShadowRadius / 2 } //刷新view postInvalidate() } //手指抬起 MotionEvent.ACTION_UP -> { //判断左滑还是右滑 if (slideX > center) { //按钮的边界超出右边规定的位置 if (slideX > mResultWidth - 2 * mSnakeRadius - mShadowRadius) { //直接滑到目标点 slideAnimate(slideX.toInt(), (mResultWidth - mSnakeRadius - mShadowRadius / 2).toInt()) mSlideState = SlideState.RIGHT_FINISH if (slideListener != null) { slideListener!!.onSlideRightFinish() } } else { setInitText() slideAnimate(slideX.toInt(), center) } } else if (slideX < center) { if (slideX < 2 * mSnakeRadius + mShadowRadius) { //直接滑到目标点 slideAnimate(slideX.toInt(), (mSnakeRadius + mShadowRadius / 2).toInt()) mSlideState = SlideState.LEFT_FINISH if (slideListener != null) { slideListener!!.onSlideLiftFinish() } } else { setInitText() slideAnimate(slideX.toInt(), center) } } else { //松手后回到原点 slideAnimate(slideX.toInt(), center) } } } return super.onTouchEvent(event) } ``` 上面的代码中`slideAnimate`这个方法是添加位移动画,代码如下 ```kotlin private fun slideAnimate(start: Int, end: Int) { val valueAnimator = ValueAnimator.ofInt(start, end) valueAnimator.addUpdateListener { animation -> val animatedValue = animation.animatedValue as Int slideX = animatedValue.toFloat() postInvalidate() } valueAnimator.start() } ``` ### 怎样在滑动到指定位置后禁止滑动 什么叫滑动到指定位置呢?这里拿按钮在中间时候的View举例,比如当按钮滑动到两头的时候,就是到指定位置了,这时抬起手指,在下次再滑动按钮的时候就不能滑动了。这个问题也很好解决,解决的方法也有很多,本文采用的方法就是初始化一个成员变量,每次进入`onTouchEvent`方法时,首先判断这个变量,条件成立则直接返回`false`。不成立就继续响应手指的动作,当按钮滑动到指定的位置的就改变这个变量的值,使下次再进入这个方法时条件成立。代码如下 ```kotlin override fun onTouchEvent(event: MotionEvent): Boolean { //滑动完成后禁止滑动 if (mSlideState == SlideState.LEFT_FINISH || mSlideState == SlideState.RIGHT_FINISH) { return false } when (event.action) { //手指按下 MotionEvent.ACTION_DOWN -> { val clickX = event.x } //手指移动 MotionEvent.ACTION_MOVE -> { slideX = event.x if (slideX > mResultWidth - mSnakeRadius - mShadowRadius / 2) { //防止超出右边界 slideX = mResultWidth - mSnakeRadius-mShadowRadius / 2 } else if (slideX < mSnakeRadius + mShadowRadius / 2) { //防止超出左边界 slideX = mSnakeRadius+mShadowRadius / 2 } //刷新view postInvalidate() } //手指抬起 MotionEvent.ACTION_UP -> { //判断左滑还是右滑 if (slideX > center) { //按钮的边界超出右边规定的位置 if (slideX > mResultWidth - 2 * mSnakeRadius - mShadowRadius) { //直接滑到目标点 slideAnimate(slideX.toInt(), (mResultWidth - mSnakeRadius - mShadowRadius / 2).toInt()) //改变变量的状态 mSlideState = SlideState.RIGHT_FINISH if (slideListener != null) { slideListener!!.onSlideRightFinish() } } else { setInitText() slideAnimate(slideX.toInt(), center) } } else if (slideX < center) { if (slideX < 2 * mSnakeRadius + mShadowRadius) { //直接滑到目标点 slideAnimate(slideX.toInt(), (mSnakeRadius + mShadowRadius / 2).toInt()) //改变变量的状态 mSlideState = SlideState.LEFT_FINISH if (slideListener != null) { slideListener!!.onSlideLiftFinish() } } else { setInitText() slideAnimate(slideX.toInt(), center) } } else { //松手后回到原点 slideAnimate(slideX.toInt(), center) } } } return super.onTouchEvent(event) } ``` ### 让文字跟随按钮移动 这部分可能算是自定义这个View最复杂的部分了,其实只要想明白了,也不复杂。这里就拿按钮从中间往右滑来讲解,按钮从中间往左边滑计算方法和往右滑的差不多,先看图 ![](https://user-gold-cdn.xitu.io/2019/3/2/1693d75fed26fdc6?w=601&h=427&f=png&s=80240) 好了,现在根据上面的图,就可以算出初始和最终的文字的X轴的位置了, > 初始状态X = (mResultWidth / 2- mSnakeRadius)/2 > > 最终状态X=mResultWidth / 2 > > mResultWidth为View的宽度,mSnakeRadius为按钮的半径 知道了初始和最终的X轴的位置,下面就是根据按钮移动的距离来改变文字的X作坐标了 > val distance = end - start > > val resultLeftX = start + distance * proportion > > end和start就是最终状态X和初始状态X,resultLeftX就是要画的文字的X坐标,proportion就是百分比,根据公式可知这个proportion值的变化范围值从0到1的。 现在,最重要的一点就是怎么根据按钮的移动来改变上面公示中的`proportion`,应该怎么办呢?继续看图 ![](https://user-gold-cdn.xitu.io/2019/3/2/1693d86686558e18?w=368&h=240&f=png&s=15487) 这个`proportion`就是上图中的x比y的值,上图中中间的一条线就是`slideX`,它是根据按钮的滑动不断变化的,这样就能满足`proportion`的值从0到1的条件了。计算的公式如下 > proportion = (slideX - halfResultWidth) / (mResultWidth - mSnakeRadius - halfResultWidth - mShadowRadius / 2) 这部分具体的代码如下 ```kotlin private fun drawInnerText(canvas: Canvas) { //这部分是裁剪画布,因为当viwe加阴影的时候,文字会越界,想看效果的话,可以把裁剪画布的代码注释掉 canvas.save() //裁剪的大小是黑色背景的大小,形状是矩形 canvas.clipRect( mShadowRadius, mShadowRadius, mResultWidth.toFloat() - mShadowRadius, mResultHeight.toFloat() - mShadowRadius ) val fontMetrics = mInnerTextPaint.fontMetrics //画圆环内的文字 val baseline = mResultHeight / 2 + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom //文字的起始位置 val halfResultWidth = mResultWidth / 2 val start = (halfResultWidth - mSnakeRadius) / 2 //文字的最终位置 // val end = (mResultWidth - 2 * mSnakeRadius) / 2 val end = mResultWidth / 2 //距离 val distance = end - start //根据状态不同设置按钮的初始位置 var proportion = if (mSlideState == SlideState.INIT_LEFT) { -1f } else { 0f } if (slideX != 0f) { //计算比例来移动文字的距离 proportion = (slideX - halfResultWidth) / (mResultWidth - mSnakeRadius - halfResultWidth - mShadowRadius / 2) } val resultLeftX = start + distance * proportion //画按钮左边的文字 canvas.drawText( leftContent, resultLeftX, baseline, mInnerTextPaint ) val resultRightX = halfResultWidth + (mSnakeRadius / 2) + (halfResultWidth / 2) + (distance * proportion) //画按钮右边的文字 canvas.drawText( rightContent, resultRightX, baseline, mInnerTextPaint ) canvas.restore() } ``` 这里重点解释一下下面的一段代码 ```kotlin private fun drawInnerText(canvas: Canvas) { canvas.save() canvas.clipRect( mShadowRadius, mShadowRadius, mResultWidth.toFloat() - mShadowRadius, mResultHeight.toFloat() - mShadowRadius ) //...省略部分代码 canvas.restore() } ``` **这段代码是为了解决给View画阴影的时候,文字移动超出背景仍然显示的问题。**当view添加阴影时,可以把这段代码注释掉,看下有什么不同。还有一点就是在指定位置写文字的问题,大家可以参考我的这篇文章[Android自定义View之定点写文字](https://juejin.im/post/5b5fbffd5188251b3d79e33c)。 ### 自定义View添加阴影的方法 添加阴影的方法就是为画笔设置`setShadowLayer`,我们来看下这个方法 ```java setShadowLayer(float radius, float dx, float dy, int shadowColor) ``` 方法中有四个参数,四个参数的作用如下 - radius:设置阴影的模糊半径,其实就是设置阴影的大小,当为0时没有阴影 - dx:阴影在X轴上的偏移量 - dy:阴影在Y轴上的偏移量 - shadowColor:阴影的颜色
注:只为画笔设置这个是不起作用的,还需要关闭硬件加速。
### 项目文件结构 项目的文件结构如下图所示 ![项目结构](/contentImages/image/20190302/zTHRQcBeWxdQ6DEf9rB.jpg "项目结构")
本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
感谢
0
手机上随时阅读、收藏该文章 ?请扫下方二维码
相似例子推荐
评论
作者
wizardev
6
例子数量
273
帮助
29
感谢
评分详细
可运行:
4.5
分
代码质量:
4.5
分
文章描述详细:
4.5
分
代码注释:
4.5
分
综合:
4.5
分
作者例子
Android蓝牙
Android低功耗蓝牙(BLE)使用详解
手撸一款精美的水波气泡
利用RecyclerView实现无限轮播广告条
一款“灵动”的滑动按钮
搞懂Nfc刷卡看这篇就够了