仿抖音视频双指缩放和单指滑动效果

        最近刷抖音看视频时,对一个视频某个位置比较感兴趣,采用双指放大查看细节,然后还可以随意滑动到任何位置,比较感兴趣,决定自己来实现此效果;
分析效果:ViewPager左右滑动,视频列表上下滑动+下拉刷新,双指进行缩放操作计算移动坐标来平移view,双指到单指也可以进行平移
问题评估:viewpager左右滑动和列表左右滑动冲突问题,单指滑动滑出边界和下拉刷新控件手势冲突;
        首先双指缩放和单指平移我想到的是,使用ScaleGestureDetector和GestureDetector,这两个一个是监听双指,一个是单指;看下了ScaleGestureDetector.OnScaleGestureListener的三个重写方法:
onScale
onScaleBegin
onScaleEnd
        双指放下,走的依次是onScaleBegin->onScale->onScaleEnd,只有ScaleBegin返回true时才会调用onScale和onScaleEnd,发现只要双指抬起来走的是onScaleEnd,因为我需要监听up事件才要还原View,发现不太适用;
        于是决定换一种思路,是不是可以在双指事件拿到之后对一个弹窗处理呢?如果只有单指Action_down事件,就把事件交给下拉刷新控件处理,双指Action_pointer_down时,弹出弹窗,对弹窗进行缩放,平移也对弹窗进行处理,简单点说就是一个dialog遮罩在页面上,根据双指单指控制展示还是隐藏;

单指:ACTION_DOWN->ACTION_MOVE->ACTION_UP;
多指:ACTION_DOWN->ACTION_POINTER_DOWN->ACTION_MOVE->ACTION_POINTER_UP->ACTION_UP

一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。当触摸事件被拦截时,Up可能是0个。View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。        

自定义控件首先要了解,一种是继承ViewGroup,一种是继承View;
dispatchTouchEvent
onInterceptTouchEvent
onTouchEvent
这是我自定义缩放View,获取到手势之后,根据双指、单指事件计算坐标
/**
 * 可缩放的Layout
 */
class TouchToScaleLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {

    // 缩放view的初始left/top
    private var originalXY: IntArray? = null

    // 触摸时 双指中间的点 / 双指距离
    private var originalTwoPointerCenter: Point? = null
    private var originalDistance: Int = 0

    // 移动时 双指距离 缩放比例
    private var moveDistance: Int = 0
    private var moveScale: Float = 0.0f;

    // 双指移动距离的增量比(用于计算缩放比、背景颜色)
    private var moveDistanceIncrement: Float = 0.0f

    // 缩放的View
    private var scaleableView: View? = null

    // 缩放的View原LayoutParams
    private var viewLayoutParams: ViewGroup.LayoutParams? = null

    // 缩放的View,在dialog中的LayoutParams
    private var dialogFrameLayoutParams: FrameLayout.LayoutParams? = null

    // 用于缩放的dialog
    private var dialog: ScaleDialog? = null

    // 缩放的动画状态
    private var isDismissAnimating: Boolean = false

    //监听回调
    private var mListener: OnVideoZoomListener? = null
    private var mOneOrTwoListener: OnVideoZoomOneOrTwoPointListener? = null

    fun setZoomListener(listener: OnVideoZoomListener) {
        mListener = listener
    }
    fun setOneOrTwoPointListener(listener:OnVideoZoomOneOrTwoPointListener){
        mOneOrTwoListener=listener
    }

    override fun onTouchEvent(ev: MotionEvent?): Boolean {
        //后续事件将可以继续传递给该view的onTouchEvent()处理
        //不想上传递
        return true
    }

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        if (childCount == 0 && scaleableView == null) return super.dispatchTouchEvent(ev)
        when (ev.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                Log.e("--------", "ACTION_DOWN")
                if (mOneOrTwoListener!=null){
                    mOneOrTwoListener!!.onOnePoint()
                }
                //交与系统处理
                return super.dispatchTouchEvent(ev)
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                //抬起直接复原下拉上拉
                if (mOneOrTwoListener!=null){
                    mOneOrTwoListener!!.onOnePoint()
                }

                if (ev.action == MotionEvent.ACTION_UP) {
                    Log.e("--------", "ACTION_UP")
                }
                if (ev.action == MotionEvent.ACTION_CANCEL) {
                    Log.e("--------", "ACTION_CANCEL")
                }
                //自己处理
                requestDisallowInterceptTouchEvent(true)
                if (scaleableView != null) {
                    if (!isDismissAnimating) {
                        dismissWithAnimator()
                    }
                    return true
                }
            }

            MotionEvent.ACTION_POINTER_DOWN -> {
                if (mOneOrTwoListener!=null){
                    mOneOrTwoListener!!.onTwoPoint()
                }
                Log.e("--------", "ACTION_POINTER_DOWN")
                //自己处理
                requestDisallowInterceptTouchEvent(true)
                if (scaleableView == null && childCount > 0) {
                    scaleableView = getChildAt(0)
                    originalXY = IntArray(2)
                    scaleableView?.getLocationOnScreen(originalXY)

                    dialog = ScaleDialog(context)
                    dialog?.show()

                    viewLayoutParams = scaleableView!!.layoutParams
                    dialogFrameLayoutParams =
                        LayoutParams(scaleableView!!.width, scaleableView!!.height).apply {
                            leftMargin = originalXY!![0]
                            topMargin = originalXY!![1]
                        }

                    postDelayed({
                        if (scaleableView != null && scaleableView?.parent == this && !isDismissAnimating) {
                            removeView(scaleableView)
                            dialog?.addView(scaleableView!!, dialogFrameLayoutParams)
                        }
                    }, 80)
                }

                originalDistance = getDistance(ev)
                if (originalTwoPointerCenter == null) {
                    originalTwoPointerCenter = Point()
                }
                originalTwoPointerCenter?.x = getTwoPointerCenterX(ev)
                originalTwoPointerCenter?.y = getTwoPointerCenterY(ev)

                return true
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e("--------", "ACTION_MOVE")
                if (scaleableView != null && scaleableView?.parent != this) {
                    if (ev.pointerCount == 2) {
                        if (mOneOrTwoListener!=null){
                            mOneOrTwoListener!!.onTwoPoint()
                        }
                        // 双指距离和距离比例
                        moveDistance = getDistance(ev)
                        moveDistanceIncrement =
                            (moveDistance.toFloat() - originalDistance.toFloat()) / originalDistance.toFloat()

                        // 关键点:
                        // 1.设置pivotX和pivotY为view左上角,相比View中心点更容易计算缩放后的位移
                        // 2.位移计算公式 (触摸屏幕时的坐标 * 缩放比 = 缩放后的坐标,当前两指中心点 - 缩放后的坐标 + 触摸屏幕时的leftMargin和topMargin = left和top最终需要的位移)
                        //   leftMargin = 当前两指中心点的x坐标 - 首次触摸屏幕时两指中心点的x坐标 乘以 缩放比 + 首次触摸时的原始leftMargin
                        //   topMargin同上,将x换成y

                        // 缩放
                        moveScale = 1 + moveDistanceIncrement
                        moveScale = max(0.5f, moveScale)
                        moveScale = min(3.0f, moveScale)
                        if (moveScale < 1) {
                            if (mListener != null) {
                                mListener!!.onScaleEnd(false)
                            }
                        } else if (moveScale > 1) {
                            //手指按下直接设置展示
                            if (mListener != null) {
                                mListener!!.onScaleEnd(true)
                            }
                        }
                        scaleableView?.run {
                            pivotX = 0f
                            pivotY = 0f
                            scaleX = moveScale
                            scaleY = moveScale
                        }

                        // 位移
                        if (originalTwoPointerCenter != null && originalXY != null) {
                            updateOffset(
                                (getTwoPointerCenterX(ev) - originalTwoPointerCenter!!.x * moveScale) + originalXY!![0].toFloat(),
                                (getTwoPointerCenterY(ev) - originalTwoPointerCenter!!.y * moveScale) + originalXY!![1].toFloat()
                            )
                        }

                        // 透明背景
                        dialog?.setShadowAlpha(max(min(0.8f, moveDistanceIncrement / 1.5f), 0f))
                        return true
                    } else if (ev.pointerCount == 1) { //单指移动
                        updateOffset(
                            getOnePointerCenterX(ev) - getOnePointerCenterX(ev) * moveScale,
                            getOnePointerCenterY(ev) - getOnePointerCenterY(ev) * moveScale
                        )
                        // 透明背景
                        dialog?.setShadowAlpha(max(min(0.8f, moveDistanceIncrement / 1.5f), 0f))
                        return true
                    }
                }

            }
        }
        //事件继续向下分发
        return super.dispatchTouchEvent(ev)
    }
    ...
}

如果你使用的有下拉刷新,注意把下拉、上拉进行动态控制,否则在滑动中会系统通知ACTION_CANCEL;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个小狼娃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值