一起来看看菜鸟裹裹的悬浮按钮效果是怎么样的:

看到那个免费送电影票了么,那个就是悬浮的按钮,这是一个View,可以是Button或者其他,都可以
然后我们需要用到ViewDragHelper,就要先去了解这个Helper的简单应用
首先我们重写一个ViewGroup继承一个布局就比较简单,用起布局也方便

  • 第一个,初始化ViewDragHelper,这个Helper只要使用creat()方法就可以创建了
    mDragHelper = ViewDragHelper.create(this, 1.0f, newViewDragCallback());
    创建的参数都很好理解,第一个是当前的ViewGroup,第二个是灵敏度,具体效果可以多设置值去玩玩,挺好玩的,第三个就是Helper的回调

  • 第二个就是要弄清楚ViewDragHelper的回调了,我这里用到了几个回调,一一解释一下,

    除了图片这些,还有一个回调,名称是tryCaptureView(View view, int pointerId)
    好了,现在一个一个说明:
    tryCaptureView(View view, int pointerId)这个回调是标明哪个哪个View可以被拖动,通过view去或者点Id去判断,返回true则标明可以拖动
    onViewDragStateChange(int state)这个回调是拖动的状态改变回调,里面有3种拖动状态
    1.STATE_IDLE:视图是当前没有被拖或动画舞/吸附的结果
    2.STATE_DRAGGING:当前视图正在被拖动
    3.STATE_SETTLING:fling完毕后被放置到一个位置
    onViewPositionChanged(View changedView, int left, int top, int dx, int dy)这个回调是拖动后位置改变的回调
    onViewCaptured(View capturedChild, int activePointerId)这个回调是当捕获子视图以拖动或结算时调用,提供当前拖动捕获视图的指针
    onViewReleased(View releasedChild, float xvel, float yvel)这个回调是当子视图不再被主动拖动时调用,也可以认为效果与STATE_IDLE差不多
    onEdgeTouched(int edgeFlags, int pointerId)这个回调是父视图的边界被触摸的回调
    onEdgeLock(int edgeFlags)这个回调是返回父视图的边界是否被锁定,edgeFlags就当前边界的状态,true为锁定,反之则解锁
    onEdgeDragStarted(int edgeFlags, int pointerId)这个回调是父视图边界开始拖动触发的回调,并且当前没有捕获到子视图
    getOrderedChildIndex(int index)这个回调是返回子视图当前的索引
    getViewHorizontalDragRange(View child)这个回调是返回一个可拖动的子视图的像素大小在水平范围的运动。此方法应该返回0则无法水平移动
    getViewVerticalDragRange(View child)这个回调是竖直方向,和意思是和上面一样
    clampViewPositionHorizontal(View child, int left, int dx)这个回调是限制水平拖动的子视图的运动。 默认实现不允许水平运动;扩展类必须覆盖此方法并提供水平方向限制的边界
    clampViewPositionHorizontal(View child, int left, int dx)这个回调和上面一样,不过是竖直方向的
    好了,回调方法就这么多,具体怎么用还需要好好去了解一下,我这里只用到了clampViewPositionHorizontalclampViewPositionHorizontalonViewReleased还有tryCaptureView,好了,下面开始贴代码咯:

/*** Created by Thong on 2017/6/30.*/
public class DragViewLayout extends RelativeLayout {private ViewDragHelper mDragHelper;private View dragview;public DragViewLayout(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}@Overrideprotected void onFinishInflate() {super.onFinishInflate();dragview = getChildAt(0);//第一个子View为悬浮按钮}private void init() {mDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragCallback());}}

一开始声明对象,这个不需要解释了吧,然后就是构造函数,重点是onFinishInflate()和init(),在onFinishInflate中获取悬浮的按钮,然后再init中初始化ViewDragHelper
接下来我们开始重写ViewDragHelper的CallBack:

private class ViewDragCallback extends ViewDragHelper.Callback {/*** 尝试捕获子view,一定要返回true* @param view 尝试捕获的view* @param pointerId 指示器id?* 这里可以决定哪个子view可以拖动*/@Overridepublic boolean tryCaptureView(View view, int pointerId) {return view == dragview;//返回如果是悬浮按钮则可以拖动,否则不能拖动}/*** 处理水平方向上的拖动* @param child 被拖动到view* @param left 移动到达的x轴的距离* @param dx 移动的x距离*/@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// 两个if主要是为了让view在ViewGroup里拖动,不超过Viewgroup边界if(getPaddingLeft() > left) {return getPaddingLeft();}if(getWidth() - child.getWidth() < left) {return getWidth() - child.getWidth();}return left;}/***  处理竖直方向上的拖动* @param child 被拖动到view* @param top 移动到达的y轴的距离* @param dy 移动的y距离*/@Overridepublic int clampViewPositionVertical(View child, int top, int dy) {// 两个if主要是为了让view在ViewGroup里拖动,不超过Viewgroup边界if(getPaddingTop() > top) {return getPaddingTop();}if(getHeight() - child.getHeight() < top) {return getHeight() - child.getHeight();}return top;}@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {if (releasedChild != null){int x = releasedChild.getRight() - (releasedChild.getWidth() / 2);int center = getResources().getDisplayMetrics().widthPixels / 2;if (x < center){mDragHelper.settleCapturedViewAt(0,releasedChild.getTop());}else{mDragHelper.settleCapturedViewAt(getResources().getDisplayMetrics().widthPixels-releasedChild.getWidth(),releasedChild.getTop());}invalidate();}}}

这里还要说明一下,如果这样写好了,其实还是不会吸附到边界的,因为什么呢,只要点击settleCapturedViewAt这个方法的话,会看到

OK,这个还不是主要的问题,在点击forceSettleCapturedViewAt这个方法进去look,look,然后我们会看到图中红色边框的

而且还要注意到注释的@return那里,能看到true if animation should continue through continueSettling(boolean) calls,意思就是如果返回true的话动画继续通过continueSettling调用,然后我们查看continueSettling的源码,方法如下:

看注释大概明白了这个方法是将捕获的子视图移动到当前时间的适当的位置,看到下面有用到deferCallbacks这个参数的是mParentView.post(mSetIdleRunnable);相当于就是不停的发送消息去刷新布局,当动画结束或者子视图已经移动到最终位置后,则停止刷新布局。
好了,说了这么多,那我们要怎么让子视图吸附的时候有一种回弹效果呢,只需要重写一个方法就可以了:

@Overridepublic void computeScroll() {if (mDragHelper.continueSettling(true)){invalidate();}}

就是这么简单,直接重写computeScroll()方法,然后再里面判断mDragHelper.continueSettling(true),至于为什么传入true呢,那么再来看看源码,其实源码都已经说了,如果要在computeScroll()调用,就应该传入true,多么简单直白T ^ T,如果这样都看不懂,那我也没办法啦。
想要用起来ViewDragHelper,还有一个重要的方法要重写的,

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_DOWN:mDragHelper.cancel(); // 相当于调用 processTouchEvent收到ACTION_CANCELbreak;}/*** 检查是否可以拦截touch事件* 如果onInterceptTouchEvent可以return true 则这里return true*/return mDragHelper.shouldInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {/*** 处理拦截到的事件* 这个方法会在返回前分发事件*/mDragHelper.processTouchEvent(event);return true;}

这两个方法必须要重写的,将TouchEvent和InterceptTouchEvent交给ViewDragHelper去处理就好了。
就这样就可以使用起来啦。
下面贴一下完整代码:

/*** Created by Thong on 2017/6/30.*/public class DragViewLayout extends RelativeLayout {private ViewDragHelper mDragHelper;private View dragview;public DragViewLayout(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}@Overrideprotected void onFinishInflate() {super.onFinishInflate();dragview = getChildAt(0);//第一个子View为悬浮按钮}private void init() {mDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragCallback());}private class ViewDragCallback extends ViewDragHelper.Callback {/*** 尝试捕获子view,一定要返回true* @param view 尝试捕获的view* @param pointerId 指示器id?* 这里可以决定哪个子view可以拖动*/@Overridepublic boolean tryCaptureView(View view, int pointerId) {return view == dragview;//返回如果是悬浮按钮则可以拖动,否则不能}/*** 处理水平方向上的拖动* @param child 被拖动到view* @param left 移动到达的x轴的距离* @param dx 建议的移动的x距离*/@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// 两个if主要是为了让view在ViewGroup里拖动,不超过Viewgroup边界if(getPaddingLeft() > left) {return getPaddingLeft();}if(getWidth() - child.getWidth() < left) {return getWidth() - child.getWidth();}return left;}/***  处理竖直方向上的拖动* @param child 被拖动到view* @param top 移动到达的y轴的距离* @param dy 建议的移动的y距离*/@Overridepublic int clampViewPositionVertical(View child, int top, int dy) {// 两个if主要是为了让view在ViewGroup里拖动,不超过Viewgroup边界if(getPaddingTop() > top) {return getPaddingTop();}if(getHeight() - child.getHeight() < top) {return getHeight() - child.getHeight();}return top;}@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {//这里判断吸附的边界,当子view的中间在屏幕左边,则吸附到左边,反之则吸附到右边if (releasedChild != null){int x = releasedChild.getRight() - (releasedChild.getWidth() / 2);int center = getResources().getDisplayMetrics().widthPixels / 2;if (x < center){mDragHelper.settleCapturedViewAt(0,releasedChild.getTop());}else{mDragHelper.settleCapturedViewAt(getResources().getDisplayMetrics().widthPixels-releasedChild.getWidth(),releasedChild.getTop());}//想要其作用就要调用computeScroll()invalidate();}}}@Overridepublic void computeScroll() {if (mDragHelper.continueSettling(true)){invalidate();}}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_DOWN:mDragHelper.cancel(); // 相当于调用 processTouchEvent收到ACTION_CANCELbreak;}/*** 检查是否可以拦截touch事件*/return mDragHelper.shouldInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {/*** 处理拦截到的事件* 这个方法会在返回前分发事件*/mDragHelper.processTouchEvent(event);return true;}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);}
}

修改
昨天发了这个博客,然后我遗漏了一个问题,就是拖动是可以拖动了,但是点击事件呢?所以针对这个点击的这个问题做了一下处理:
首先,我们先去研究一下源码,这个问题涉及到事件分发,所以建议先去弄懂事件分发机制。
要想触发点击事件,那么先要写一个点击事件啦,写完了之后,那么问题就来了,点击是可以了,为什么不能拖动了呢?因为事件分发机制的判断流程是这样的:dispatchTouchEvent—>onInterceptTouchEvent—>onTouchEvent,知道了流程就简单了,因为onClick事件也是一个Touch事件,所以也会走这样的流程,所以我们在onInterceptTouchEvent中打个断点调试一下,看一下拦截触摸事件里面是怎么样做的,一步一步的解决点击事件这个问题。
一开始在碰到这个页面的时候就会触发onInterceptTouchEvent,然后在ViewGroup中的onInterceptTouchEvent的return 调用 ViewDragHelper的shouldInterceptTouchEvent,然后首先看拦截事件的ACTION_DOWN,这个DOWN事件里面并没有特别的处理,主要都是记录按下时位置,然后保存,然后是到ACTION_MOVE,这个事件里面大有文章了,一进来就开始判断DOWN事件中保存的位置是不是为空,空就直接结束,然后获取触摸点,然后获取触摸点移动的位置,并且算出移动的偏移量,然后根据当前的位置去获取这个子视图,重要的来了,checkTouchSlop这个方法是判断这个子视图在当前位置是否为空,并且检查子视图是否合理拖动,如果子视图合理拖动的话,那么这个方法会返回true然后根据触摸点的移动而移动,而判断checkTouchSlop的时候,里面就判断mCallBack.getViewHorizontalDragRange(child) > 0 和mCallback.getViewVerticalDragRange(child) > 0,那这样就很明显了嘛,因为getViewHorizontalDragRange和getViewVerticalDragRange默认都是返回0的,所以这个方法返回是false,我还是贴一下代码吧,来,走着:

贴了图片应该就清楚多了吧,里面会判断拖动的范围是否大于0,然后再下面判断是水平拖动,还是垂直拖动,还是非单项拖动,只要拖动的距离大于mTouchSlop就认为是拖动状态,mTouchSlop又是什么鬼,我们可以看一看:

final ViewConfiguration vc = ViewConfiguration.get(context);
mTouchSlop = vc.getScaledTouchSlop();

ViewDragHelper中的mTouchSlop是这样获取的,相信写过ACTION_MOVE事件的人都知道有一个系统级的判断是否移动的数值,就是这个vc.getScaledTouchSlop()了,好,OK,既然知道了是因为getViewVerticalDragRange和getViewHorizontalDragRange这两个东西造成的原因,那么我们去重写这两个,让它不默认返回0,而是有返回一定数值,那么返回的数值应该是多少呢,既然是一个方向移动范围,那么我们可以获取父布局的宽高,并且减去子视图的宽高,剩下的数值就是子视图可以移动的范围数值了,所以最终这两个方法重写应该是这样的:

@Overridepublic int getViewVerticalDragRange(View child) {return dragview == child ? getMeasuredHeight() - child.getHeight() : 0;}@Overridepublic int getViewHorizontalDragRange(View child) {return dragview == child ? getMeasuredWidth() - child.getWidth() : 0;}

绕了这么一大圈,最终的结果只需要重写这两个方法,好坑啊,但是不否认这个Helper的确很好用,赞一个,不懂得都可以直接问我,评论或者私信都可以哦!

利用ViewDragHelper实现菜鸟裹裹的悬浮按钮效果相关推荐

  1. android 悬浮按钮 功能实现,怎么在Android中利用FloatingActionButton实现一个悬浮按钮效果...

    怎么在Android中利用FloatingActionButton实现一个悬浮按钮效果 发布时间:2020-12-02 17:41:30 来源:亿速云 阅读:238 作者:Leah 今天就跟大家聊聊有 ...

  2. button 样式_小程序 Button图标样式 实现悬浮按钮效果

    button button是小程序中重要的组件 微信官方api 但是这样的效果都不具备很好的美观性 非表单中实现悬浮按钮效果 将一个 矢量图图标 用小程序控件封装即可 这里使用text控件 将矢量图作 ...

  3. Android自定义旋钮效果,Android自定义悬浮按钮效果实现,带移动效果

    一个带动画效果的悬浮按钮.从下往上显示,从上往下消失. 代码比较简单,实现原理也比较简单. public class MainActivity extends Activity { private B ...

  4. android 悬浮按钮 功能实现,Android自定义悬浮按钮效果实现,带移动效果

    一个带动画效果的悬浮按钮.从下往上显示,从上往下消失. 代码比较简单,实现原理也比较简单. public class MainActivity extends Activity { private B ...

  5. android 悬停按钮,Android悬浮按钮的使用方法

    悬浮按钮效果如下图所示: 步骤1:引用 compile 'com.laocaixw.suspendbuttonlayout:suspendbuttonlayout:1.0.3' 步骤2:xml布局 a ...

  6. android悬浮功能实现,Android实现系统级悬浮按钮

    本文实例为大家分享了Android系统级悬浮按钮的具体代码,供大家参考,具体内容如下 具体的需求 1.就是做一个系统级的悬浮按钮,就像iPhone 桌面的那个悬浮按钮效果一样,能随意拖动,并且手一放开 ...

  7. Flutter 悬浮按钮 FloatingActionButton 的详细配置使用

    志在巅峰的攀登者,不会陶醉在沿途的某个脚印之中,在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天.每周,都会留下一些脚印,就是这些创作的 ...

  8. 菜鸟裹裹电脑版_干货|利用菜鸟裹裹商家版低价寄快递

    大家好,今天依旧是勤勤恳恳更新的小杨,上一期给大家发了一篇:"摆地摊进货渠道平台分享",那对于不想摆地摊,想发展微商等需要邮寄物品的朋友们,今天小杨给大家带来了:"利用菜 ...

  9. 菜鸟裹裹万能查:小包裹数据的大革新

    作者:哈九,菜鸟裹裹数据研发 无刃,菜鸟裹裹数据研发 夏招,菜鸟裹裹数据产品 菜鸟寄件业务当前为菜鸟的基础设施建设业务,是用户与[菜鸟]品牌最直接最基本的认知联系.通过与三通一达等巨头快递公司合作,降 ...

最新文章

  1. C++中数组的赋值方法
  2. 大牛是怎么思考设计MySQL优化方案
  3. C#反射在ADO中的巧用
  4. 解决pathForResource返回nil / 无法读取plist文件问题
  5. openjdk和jdk_JDK 11:发行候选更新和OpenJDK JDK 11 LTS
  6. 结构体+sort方法
  7. [剑指offer][JAVA]面试题第[46]题[把数字翻译成字符串][递归][逆推]
  8. 【BZOJ - 3224】普通平衡树(Splay模板题)
  9. poj 1797 HeavyTransportation——最小边的最大值
  10. FindWindowEX应用实例二则
  11. text-overflow:ellipsis的巧妙运用
  12. 类垂直站点插件实现与分享
  13. 科大讯飞 离线语音识别python_使用python语言调用科大讯飞离线语音合成
  14. 已安装flash插件,chrome仍提示未安装的解决方法
  15. 【智能制造】歌尔股份打造面向可重构和微服务的可穿戴产品智慧工厂
  16. vue-router 在ie内核下页面不跳转
  17. 建议收藏!仓库规划与布局设计整体方案
  18. 某某读书搜索__DATA__分析
  19. 【实战】python以及opencv实现信用卡的数字识别
  20. OLTP和OLAT的区别

热门文章

  1. 清华大学计算机科学与技术系黄必胜,刘斌(清华大学计算机科学与技术系教授)_百度百科...
  2. 一名25岁的董事长给大学生的18条忠告
  3. 【100%通过率】华为OD机试真题 Python 实现【最长的密码】【2022.11 Q4 新题】
  4. VR 项目的优化 尝试
  5. 零基础该如何学习UI设计,你的学习方法正确吗?
  6. CS61B sp2018笔记 | Testing and Selection Sort
  7. U3D各向异性Shader
  8. ListBox 控件
  9. mdb文件导入MySQL
  10. 川大计算机学院新生开学典礼,川大新生开学典礼 财大校长来串门儿(图)