Kotlin--›超高模仿QQ7.5 侧滑菜单

效果图:
这里写图片描述

特性模仿

  • 全屏可视区域滑动检测 (菜单关闭和打开状态, 都支持)
  • 内容区域滑动过程中自带阴影遮罩
  • 菜单打开状态, 点击阴影区域自动关闭
  • 滑动过程中, 视差效果
  • 可以嵌套在其他具有滚动特性的View中

实现方法如果使用 ViewDragHelper 那么局限性会很多, 所以这里我采用了最原始的TouchEvent控制.

以下代码, 只贴部分片段, 详细请下载源码

首先监听Touch事件
任何需要处理Touch事件的控件, 都必备回调2个方法onScrollonFling, 缺一个都是不完整的.
onScroll:用在简单的手指上下,左右移动. 这个方法会回调出, 手指与上一个事件的移动间隔距离
onFling:用在手指快速的上下,左右移动. 这个方法回调出的是手指与上一个事件之间的移动速度

/*用来检测手指滑动方向*/
protected val orientationGestureDetector = GestureDetectorCompat(context, object : GestureDetector.SimpleOnGestureListener() {
   override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
       //L.e("call: onFling -> \n$e1 \n$e2 \n$velocityX $velocityY")

       firstMotionEvent = e1
       secondMotionEvent = e2

       val absX = Math.abs(velocityX)
       val absY = Math.abs(velocityY)

       if (absX > flingVelocitySlop || absY > flingVelocitySlop) {
           if (absY > absX) {
               //竖直方向的Fling操作
               onFlingChange(if (velocityY > 0) ORIENTATION.BOTTOM else ORIENTATION.TOP, velocityY)
           } else if (absX > absY) {
               //水平方向的Fling操作
               onFlingChange(if (velocityX > 0) ORIENTATION.RIGHT else ORIENTATION.LEFT, velocityX)
           }
       }

       return true
   }

   override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
       //L.e("call: onScroll -> \n$e1 \n$e2 \n$distanceX $distanceY")
       firstMotionEvent = e1
       secondMotionEvent = e2

       val absX = Math.abs(distanceX)
       val absY = Math.abs(distanceY)

       if (absX > scrollDistanceSlop || absY > scrollDistanceSlop) {
           if (absY > absX) {
               //竖直方向的Scroll操作
               onScrollChange(if (distanceY > 0) ORIENTATION.TOP else ORIENTATION.BOTTOM, distanceY)
           } else if (absX > absY) {
               //水平方向的Scroll操作
               onScrollChange(if (distanceX > 0) ORIENTATION.LEFT else ORIENTATION.RIGHT, distanceX)
           }
       }

       return true
   }

})

如何让Layout移动起来?
通常情况下在View中, 我们会想到用View的x,y, 坐标来控制.或者translationX, translationY, 等;
在ViewGroup, 我们可以用ScrollTo, ScrollBy方法, 让布局移动起来.

但是, 我这里用了View.Layout方法, 通过改变View在ViewGroup中的坐标, 达到移动效果.

private fun refreshContentLayout(left: Int) {
   if (childCount == 2) {
       getChildAt(1).apply {
           layout(left, 0, left + this.measuredWidth, this.measuredHeight)  //关键点
       }
   }
}

我们只需要, 不断的传入不同的left坐标值, 就可以让View移动起来;

那么如何让left不断变化呢?
很自然的就想到了用动画, 动画虽然也简单, 但是我这里用了一个和自定义View密切相关的另一个必备神器类OverScroller
也许你会觉得OverScroller是用来滚动View的, 那你就太小看它了. 它其实内部也是动画机制.

如何使用OverScroller让left动起来?
其实, 你只要会用它, 基本上不难, 关键在于…请往下看!

 /**开始滚动到某个位置*/
 open fun startScrollTo(startX: Int, toX: Int) {
     overScroller.startScroll(startX, scrollY, toX - startX, 0, 300)
     postInvalidate()
 }

OverScroller密切关联的方法就是computeScroll了.其实你把它理解成OverScroller的执行回调, 可能更好一点.

 override fun computeScroll() {
     if (overScroller.computeScrollOffset()) {
         //scrollTo(overScroller.currX, overScroller.currY) //原本应该是这样的.
         val currX = overScroller.currX
         if (contentLayoutLeft != currX) {
             refreshContentLayout(currX)   //但是...投机取巧, 我们用这个...完美的让left动起来了.
         }
         postInvalidate()
     }
 }

到此:ViewDragHelper的拖动功能, 已经模仿出来了.但是, 我们的可扩展性, 可操作比他大100倍.

如何营造视差效果?
对于这个, 其实我们只需要在 更新内容left的时候, 同步更新菜单的left就行了. 只不过left要经过阻尼处理一下. 效果才逼真.

 //单独更新菜单,营造视差滚动
 private fun refreshMenuLayout() {
     //计算出菜单展开的比例
     val fl = contentLayoutLeft.toFloat() / maxMenuWidth
     if (fl > 0f && childCount > 0) {
         getChildAt(0).apply {
             //视差开始时的偏移值
             val menuOffsetStart = -maxMenuWidth / 2
             val left = menuOffsetStart + (menuOffsetStart.abs() * fl).toInt()
             layout(left, 0, left + this.measuredWidth, this.measuredHeight)
         }
     }
 }

如何实现阴影遮罩?
玩过Canvas的应该都知道, 直接绘制一个透明图层就行了. 关键在于, 透明度要跟随菜单的打开程度动态计算

override fun draw(canvas: Canvas) {
     super.draw(canvas)
     //绘制内容区域的阴影遮盖
     if (isMenuClose()) {

     } else {
         val layoutLeft = contentLayoutLeft
         debugPaint.color = Color.BLACK.tranColor((255 * (layoutLeft.toFloat() / maxMenuWidth) * 0.4f /*限制一下值*/).toInt())
         debugPaint.style = Paint.Style.FILL_AND_STROKE
         maskRect.set(layoutLeft, 0, measuredWidth, measuredHeight)
         canvas.drawRect(maskRect, debugPaint)
     }
 }

也许你还想学习更多, 来我的群吧, 我写代码的能力, 远大于写文章的能力:

联系作者

点此快速加群

请使用QQ扫码加群, 小伙伴们在等着你哦!

关注我的公众号, 每天都能一起玩耍哦!


开源地址: https://github.com/YaYaStudio/SliderMenuLayout

猜你喜欢

转载自blog.csdn.net/angcyo/article/details/79813216