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

news/2025/3/5 11:05:14/
最近刷抖音看视频时,对一个视频某个位置比较感兴趣,采用双指放大查看细节,然后还可以随意滑动到任何位置,比较感兴趣,决定自己来实现此效果;
分析效果: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/topprivate var originalXY: IntArray? = null// 触摸时 双指中间的点 / 双指距离private var originalTwoPointerCenter: Point? = nullprivate var originalDistance: Int = 0// 移动时 双指距离 缩放比例private var moveDistance: Int = 0private var moveScale: Float = 0.0f;// 双指移动距离的增量比(用于计算缩放比、背景颜色)private var moveDistanceIncrement: Float = 0.0f// 缩放的Viewprivate var scaleableView: View? = null// 缩放的View原LayoutParamsprivate var viewLayoutParams: ViewGroup.LayoutParams? = null// 缩放的View,在dialog中的LayoutParamsprivate var dialogFrameLayoutParams: FrameLayout.LayoutParams? = null// 用于缩放的dialogprivate var dialog: ScaleDialog? = null// 缩放的动画状态private var isDismissAnimating: Boolean = false//监听回调private var mListener: OnVideoZoomListener? = nullprivate var mOneOrTwoListener: OnVideoZoomOneOrTwoPointListener? = nullfun 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!!.layoutParamsdialogFrameLayoutParams =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 + moveDistanceIncrementmoveScale = 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 = 0fpivotY = 0fscaleX = moveScalescaleY = 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;


http://www.ppmy.cn/news/11122.html

相关文章

FFmpeg功能命令汇总

前言 如此强大的FFmpeg&#xff0c;能够实现视频采集、视频格式转化、视频截图、视频添加水印、视频切片、视频录制、视频推流、更改音视频参数功能等。通过终端命令如何实现这些功能&#xff0c;Richy在本文做一记录&#xff0c;以备之后查阅。 注意&#xff1a;下面一一列举…

Linux常用命令——nethogs命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) nethogs 终端下的网络流量监控工具 补充说明 有很多适用于Linux系统的开源网络监视工具。比如说&#xff0c;你可以用命令iftop来检查带宽使用情况。netstat用来查看接口统计报告&#xff0c;还有top监控系统当…

【自学Python】Python input()函数

Python input()函数 Python input()函数教程 在 Python 中&#xff0c;input() 函数用于获取用于的输入&#xff0c;并给出提示。input() 函数&#xff0c;总是返回 string 类型&#xff0c;因此&#xff0c;我们可以使用 input() 函数&#xff0c;获取用户输入的任何数据类型…

Python3,我把新年祝福写在“雨“中,你看,雨一直下,气氛还算融洽,在同个屋檐下....

新年愿望写在"雨"中1、引言2、代码实战2.1 模块介绍2.1.1 Pygame 介绍2.1.2 Pygame的display介绍2.1.3 Pygame的event介绍2.1.4 Pygame的font介绍2.2 代码示例3、总结1、引言 小屌丝&#xff1a;鱼哥&#xff0c;2023年了&#xff0c; 你有啥愿望啊&#xff1f; 小鱼…

大一计算机新生,感觉什么都学不会怎么办?

虽然今天什么都没做&#xff0c;但是还是辛苦我了。。。刚开始难是正常现象&#xff0c;可以先梳理一下 计算基础基础很重要&#xff0c;实践为上 计算机组成与原理、数据结构、计算机网络、操作系统、编程语言 书看得越多&#xff0c;其实你越能找到自己的方向&#xff0c;一…

前端Vue和Element-UI配合后端接口进行数据交互

前言 本次用element-ui的table组件&#xff0c;简单案例演示下前后端数据交互。 前提声明&#xff1a;如果不知道如何在vue中引入element-ui&#xff0c;可以先看下这篇文章:Vue引入并使用Element-UI组件库的两种方式 静态页面 首先先写一个静态页面吧&#xff0c;数据都是…

00后少年的心力之作(已开源) | heartt(心力算法)

00后少年的心力之作(已开源) | 综合性极强的文本摘要算法: heartt 大家好&#xff0c;我是 heartt 算法的作者&#xff0c;一名热爱编程的学习者。 今天&#xff0c;我要向大家介绍我的新算法&#xff1a;heartt。 文章目录一、前言二、算法的介绍2.1 功能简介2.2 核心思想2.…

于仕琪C/C++ 学习笔记

C函数指针有哪几类&#xff1f;函数指针、lambda、仿函数对象分别是什么&#xff1f; 如何利用谓词对给定容器进行自定义排序&#xff1f; 传递引用和传递值的区别&#xff1f;传递常引用和传递引用之间的区别&#xff1f;传递右值引用和传递引用之 间的区别&#xff1f; 函…