本篇为该系列的第三篇,将通过一个实际的业务需求来讲述ViewDragHelper的实际运用。

目录

ViewDragHelper 的介绍以及初步使用请阅读这篇:
ViewDragHelper (一)- 介绍及简单用例(入门篇)
ViewDragHelper 的源码以及Callback的详情介绍请阅读这篇:
ViewDragHelper (二)- 源码及原理解读(进阶篇)
利用DrageHelper 打造仿陌陌APP视频播放页的demo请阅读这篇:
ViewDragHelper (三)- 打造仿陌陌视频播放页(深入篇)

介绍

首先,系统的DrawerLayout 抽屉想必大家都不陌生,它的侧重点在于左右滑动。鉴于已经有很多大牛写过类似的,咱们就不再过多地讲述这个了,有兴趣的朋友可以自行查找相关的文章。本篇文章主要讲解如何利用ViewDragHelper来打造一个可以下拉拖拽关闭以及左右滑动切换的功能。

效果演示

1. QZone

QQ空间视频播放页也有这个下拉关闭的功能,效果图如下:

若想要的功能仅仅只是它,那么可以直接参考第一篇文章的代码,会简洁很多,文章链接:ViewDragHelper (一)- 介绍及简单用例(入门篇)

2. 陌陌播放页

陌陌播放页的效果图:

3. 实际效果

下面是本项目的效果图:

真机展示的效果可能会好点儿


正文

本文主要讲解的点有如下几个:

  1. 滑动方向判定。
  2. 如何限制为单个方向的拖拽。
  3. 事件分发以及拦截。
  4. 平移动画问题。
  5. 下拉时缩放及背景透明处理。
  6. 背景高斯图片替换处理。
  7. 嵌套ScrollView / RecyclerView事件冲突处理。
  8. 多点触控 Invalid pointerId 问题解决。

初始化

首先, 我们还是和第一篇文章一样,创建一个DragView(继承自ViewGroup),以及一个CallBack(继承自 ViewDragHelper.Callback)。
然后进行相关初始化操作。
1. 初始化ViewDragHelper。
2. 初始化CallBack ,用于监听ViewDragHelper相关事件,回调给DraggableView。
3. 初始化DraggableListener,用于回调给外部。

具体代码可参考demo,项目地址贴在文章底部。

事件分发拦截处理

代码总篇幅太长,就不贴完整源代码了,用伪代码描述大致实现思路。有需要完整代码的朋友可以自行下载GitHub 上面的demo。

onInterceptTouchEvent 方法里面判断了手势方向,以及滑动冲突,多点触控导预防处理。伪代码如下:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {if (!isEnabled()) {return false;}switch(event) {case ACTION_DOWN:if (activePointerId == INVALID_POINTER) {return false;}case ACTION_UP or ACTION_CANCEL:viewDragHelper.cancel();}return  isViewUnderChild || shouldInterceptTouchEvent;
}

onTouchEvent方法伪代码如下:

    @Overridepublic boolean onTouchEvent(MotionEvent ev) {if (activePointerId == INVALID_POINTER_POINTER) {return false;}if (ev.getPointerCount() > 1) { //屏蔽多指操作Log.e(TAG, "onTouchEvent, getPointerCount > 1 (多点触控,屏蔽掉)");mDragView.dispatchTouchEvent(cloneMotionEventWithAction(ev, MotionEvent.ACTION_CANCEL));viewDragHelper.cancel();return false;}switch (EVENT.ACTION) {case ACTION_DOWN://记录按下位置的XY坐标viewDragHelper.processTouchEvent(ev);      mDragView.dispatchTouchEvent(ev);break;case ACTION_MOVE://当前手指的XY坐标与按下坐标相减。if (!isJudgeWay) {//还未判定滑动方向//判断滑动方向//从按下 ,到手指当前位置的移动距离 = 根号(X^2 + Y^2)//若移动距离超过系统移动默认阈值if (mMoveDistance >= viewDragHelper.getTouchSlop()) {isJudgeWay = true;}}/*** 将事件分发给子控件的条件:* 1.没有被关闭* 2.非顶部下滑动* 3.向上滑动*/if (isFullScreen) {//全屏状态mDragView.dispatchTouchEvent(ev);} else {//非全屏状态下if(不在顶部下滑,或者上滑){// 分发事件给子控件mDragView.dispatchTouchEvent(ev);}else if (已在ScrollView顶部,并且手势为下滑) { //顶部下拉拖拽,分发事件给DragHelperviewDragHelper.processTouchEvent(ev); //分发CANCEL事件给子控件,以取消DOWN事件} else if (MOVE_LEFT || MOVE_RIGHT) {//左右滑动//分发事件给DragHelper进行左右拖拽viewDragHelper.processTouchEvent(ev); //分发CANCEL事件给子控件,以取消DOWN事件} else { //都不符合条件//分发 ACTION_CANCEL给子控件;}}break;default:if (ev.getAction() == MotionEvent.ACTION_UP) {//还原方向判定}mDragView.dispatchTouchEvent(ev);viewDragHelper.processTouchEvent(ev);break;}//可见时消费掉触摸事件,避免底层其他控件触发return !isClosedAtBottom();}

在onInterceptTouchEvent 方法中处理事件的拦截逻辑,当手指点在子控件的有效范围区域,DraggableView 将会拦截事件,触发自身的onTouchEvent方法。

在onTouchEvent方法中判断滑动方向,并根据方向以及是否在ScrollView的顶部来分发事件。若在ScrollView顶部并且符合下拉拖拽,则将事件分发给viewDragHelper,否则分发给子控件。具体细节可参考源代码。

回调监听的处理

Y轴方向改变监听

当子控件的位置发生改变时,会触发ViewDragHelper.Callback的onViewPositionChanged方法。
我们在创建CallBack对象时,在构造方法传入了DraggableView对象进来。此时我们调用 draggableView.onViewPositionChanged()方法回调View的坐标参数。
参数根据实际需求添加即可,这里我们只需要用上top,即顶部位置,因此只写了一个参数。然后在draggableView中通过listener将其回调给外部。

限制垂直方向只能下拉,不能往上拖拽。
    @Overridepublic int clampViewPositionVertical(View child, int top, int dy) {Log.d(TAG, "clampViewPositionVertical" + ", top" + top + ", dy:" + dy);//水平滑动时触发了竖直方向,屏蔽掉if (draggableView.Move_Way.equals(draggableView.MOVE_LEFT)|| draggableView.Move_Way.equals(draggableView.MOVE_RIGHT)) {return 0;}mRangeY += dy;return Math.max(mRangeY, 0);}

由于viewDragHelper内部,会因为滑动嵌套的原因,在滑动距离未超过系统的mTouchSlop值时,会触发cancel 导致重置到原位,因此这里我们通过累加dy(单次滑动距离)来实现。并且保证 return 大于0,不让子控件向上拖拽。从而实现单向地下拉拖拽的功能。

水平方向滑动

若手势为左右滑动,则屏蔽掉垂直方向,限制只能左右滑动。

@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// Log.d(TAG, "clampViewPositionHorizontal" + ", left" + left + ", dx:" + dx);//竖直滑动时触发了水平方向,屏蔽掉if (draggableView.Move_Way.equals(draggableView.MOVE_TOP)|| draggableView.Move_Way.equals(draggableView.MOVE_BOTTOM)) {return 0;}return left;}
手指松开,计算并处理滑动

ViewDragHelper的 ACTION_UP 或CANCEL事件触发时,会回调onViewReleased方法。我们需要在该方法里面判断已移动距离和方向,让子控件继续自动滑动到指定的位置。

@Overridepublic void onViewReleased(View releasedChild, float xVel, float yVel) {super.onViewReleased(releasedChild, xVel, yVel);//Log.d(TAG, "onViewReleased" + "xVel:" + xVel + ", yVel:" + yVel);mRangeY = 0;int top = releasedChild.getTop(); //获取子控件Y值int left = releasedChild.getLeft(); //获取子控件X值if (Math.abs(left) <= Math.abs(top)) {//竖直滑动triggerOnReleaseActionsWhileVerticalDrag(top);} else if (Math.abs(top) < Math.abs(left)) {//水平滑动triggerOnReleaseActionsWhileHorizontalDrag(left);}}/*** 计算竖直方向的滑动*/private void triggerOnReleaseActionsWhileVerticalDrag(float moveY) {//Log.d(TAG, "ReleaseVerticalDrag"+", moveY:" + moveY);if (moveY < 0 && moveY <= -Y_MIN_DISTANCE) {draggableView.onReset();} else if (moveY > 0 && moveY >= Y_MIN_DISTANCE) {draggableView.closeToBottom();} else {draggableView.onReset();}}/*** 计算水平方向的滑动*/private void triggerOnReleaseActionsWhileHorizontalDrag(float moveX) {
//        Log.d(TAG, "ReleaseHorizontalDrag"+", moveX:" + moveX);if (moveX < 0 && moveX <= -X_MIN_DISTANCE) {draggableView.closeToLeft();} else if (moveX > 0 && moveX >= X_MIN_DISTANCE) {draggableView.closeToRight();} else {draggableView.onReset();}}

其中,viewDragHelper的 settleCapturedViewAt() 以及 smoothSlideViewTo()方法都需要利用 computeScroll() 来实时刷新位置。

   @Overridepublic void computeScroll() {if (viewDragHelper.continueSettling(true)) {ViewCompat.postInvalidateOnAnimation(this);}}

回调外部处理

在onViewReleased 方法触发并计算滑动方向之后。就会根据计算结果判定是否回弹或者滑动到屏幕指定位置,并通过listener回调通知外部。

/*** Called when the view is minimized.*/void onClosedToBottom();/*** Called when the view is closed to the left.*/void onClosedToLeft();/*** Called when the view is closed to the right.*/void onClosedToRight();/*** Called when the child view location changed* @param top*/void onBackgroundChanged(int top);

DraggableListener接口的四个方法分别是下拉关闭,向左切换,向右切换,位置改变时回调。

透明度、缩放动画及背景动态替换

我们通过onBackgroundChanged回调方法获取 子控件top位置的改变值。然后设置透明及缩放动画。具体代码如下:

    @Overridepublic void onBackgroundChanged(int top) {int newAlpha = 255 - (int) (255 * ((float) top / (float) dragView.getRootView().getHeight()));if (newAlpha == 255) {dragView.setBackgroundResource(R.mipmap.bg_gauss_blur);} else {dragView.setBackgroundColor(ContextCompat.getColor(this, R.color.colorBackground));}dragView.getBackground().setAlpha(newAlpha);if (newAlpha < 216) { //达到子控件缩放最小值,原大小的0.85倍scrollView.setScaleX(0.85f);scrollView.setScaleY(0.85f);} else {// newAlpha >= 204 平滑缩放scrollView.setScaleX(1 - (255.0f - (float) newAlpha) / 255);scrollView.setScaleY(1 - (255.0f - (float) newAlpha) / 255);}}

总结

ViewDragHelper实质上也是通过分析MotionEvent 事件来进行操控子控件的移动。在实际使用过程中,我们特别需要注意控件嵌套滑动的问题。通过这个demo,我们想必也会发现,利用ViewDragHelper可以帮助我们省去一部分滑动动画的繁琐逻辑。但我们需要更加注意事件的滑动冲突,合理分发事件。通过这个系列,相信大家对于View事件分发机制及滑动冲突处理也会有一个更加深刻的认识。若有疑问,欢迎留言讨论~

GitHub 项目地址传送门:ViewDragHelperDemo
有兴趣的朋友可以下载完整喜欢的朋友可以 star 一波~

ViewDragHelper (三)- 打造仿陌陌视频播放页(深入篇)相关推荐

  1. Android仿老版本陌陌登录注册介绍页实现

    楼主手机上装了好多个软件,陌陌不常玩的,看了下它的登录注册下面是一些美女的图片,而且有透明度的变化,关键是图片没有压缩和失真的情况,然后分析了下,这些肯定不是一张背景图了,可想而知,著名的9宫格啊,这 ...

  2. uni-app直播实例|仿抖音小视频|uniapp仿陌陌直播

    优直播uni-liveShow是基于vue+uni-app+vuex+nvue+swiper等技术开发仿制抖音|火山小视频/陌陌直播实战项目,支持编译到三端(H5.小程序.App端) 且兼容效果一致. ...

  3. android仿陌陌tab,uniapp直播室|仿抖音视频|nvue+uniapp高仿陌陌直播

    一.介绍说明 U直播uniLiveShow是一款基于vue+Nvue+uni-app技术开发的综合小视频/聊天室/直播等功能的聊天直播项目.界面高仿热门抖音|火山小视频/陌陌直播,可滑动切换视频播放, ...

  4. 基于android的防抖音直播,基于vue+uniapp直播项目实现uni-app仿抖音/陌陌直播室功能...

    一.项目简介 uni-liveShow是一个基于vue+uni-app技术开发的集小视频/IM聊天/直播等功能于一体的微直播项目.界面仿制抖音|火山小视频/陌陌直播,支持编译到多端(H5.小程序.Ap ...

  5. vue整合uniapp_基于vue+uniapp直播项目|uni-app仿抖音/陌陌直播室

    一.项目简介 uni-liveShow是一个基于vue+uni-app技术开发的集小视频/IM聊天/直播等功能于一体的微直播项目.界面仿制抖音|火山小视频/陌陌直播,支持编译到多端(H5.小程序.Ap ...

  6. android仿陌陌tab,Vue|Nuxt.js仿探探卡片式左右拖拽|vue仿Tinder

    开场技术宅男对探探/陌陌并不陌生,一款专注于陌生人的社交App.里面的左右滑动翻牌子效果更是让人眼前一亮,似乎有一种古时君王选妃子的感觉.让人玩的爱不释手. 一睹风采 哈哈,效果还行吧.下面就简单的讲 ...

  7. 陌陌打造最大LBS社交广告平台

    移动社交平台陌陌举行 "莫等商机,移触即发"商业化产品全国巡讲活动,这也是陌陌商业化以来首次面向广告主发起的系列巡讲,同时也意味着陌陌场景化营销商业进程提速,陌陌也提出将全力打造中 ...

  8. 打造最大的LBS社交广告平台,陌陌凭什么?

    最近,陌陌正在举行"莫等商机,移触即发"商业化产品全国巡讲活动.陌陌副总裁,商业化产品负责人在活动当中首次透露了陌陌想要打造国内最大的LBS社交广告平台的野心.实际上,陌陌之所以能 ...

  9. android 自定义裁剪 陌陌,Android之View篇6————仿陌陌卡片左右滑动选择布局

    Android之View篇6----仿陌陌卡片左右滑动选择控件 一.目录 Android之View篇6----仿陌陌卡片左右滑动选择控件 一.目录 二.效果图 三.业务需求梳理 四.思路分析 1. 新 ...

最新文章

  1. windows 2003几个优化技巧
  2. js 正则表达式奇偶字符串替换_Python中的正则表达式及其常用匹配函数用法简介...
  3. asp.net的几个错误
  4. Vitaly and Night
  5. 1061. 判断题(15)
  6. 【机器学习】机器学习从零到掌握之十 -- 教你使用Python实现决策树
  7. GeeksForGeeks 翻译计划 | ApacheCN
  8. Template parse errors: The pipe 'translate' could not be found
  9. 腾讯九次面试C++,如今面试题了如指掌
  10. Z - 犯罪嫌疑人(思维题目)
  11. Solidworks常用技巧
  12. TE飞到对象完成事件
  13. 神奇的机器人评课_小学信息技术《我的编程我做主——点亮神奇的灯》评课稿...
  14. (2022.5.27)【Win10】Windows10重置后微软商店闪退打不开、图片闪退打不开、UWP应用闪退打不开——可能的解决方案
  15. 史蒂夫·保罗·乔布斯
  16. Pixelmator for Mac(全能图像编辑软件)
  17. [转载]关于sql连接语句中的Integrated Security=SSPI
  18. music的matlab程序,DOA经典算法MUSIC的MATLAB代码(作者:Nikhil Shetty).pdf
  19. 利用three建立一个3d园区
  20. apache与php乱码

热门文章

  1. Vue跳转到一个新的页面的多种方法
  2. (附源码)计算机毕业设计ssm党员学习管理系统
  3. 微信小程序封装showModal/showToast
  4. 碧蓝航线服务器维护时间,碧蓝航线4月9日停服维护公告内容详解
  5. 字符数组转list集合
  6. java 获取系统时间不对_java new Date()得到的时间和系统时间不一样
  7. python面试题-输入一个由n个大小写字母组成的字符,按Ascii码值从小到大排序,查找字符串中第k个最小Ascii码值的字母
  8. PHP学习笔记——文件上传
  9. 《乐高EV3机器人搭建与编程》一2.3 球头万向轮
  10. Mac电脑搭建vue环境