自定义View系列(8)--越界回弹ScrollView

来源:互联网 发布:淘宝网mac版下载 编辑:程序博客网 时间:2024/05/21 19:21

难度

中等

效果说明

越界回弹的效果不用多说了吧,大家应该都知道, 不知道的看下方效果图。

效果图

效果图

特性说明

  • 支持阻尼系数
  • 支持多指触控
  • 支持上拉回弹、下拉回弹
  • 支持设置开启/关闭回弹:ENABLED_ALL、ENABLED_TOP、ENABLED_BOTTOM 、ENABLED_NONE
  • 不影响原有手势的分发处理
  • 支持设置最大滑动距离
  • 支持设置插值器

实现原理

整体采用offsetTopAndBottom()+ValueAnimator实现。

事件分发处理

重写dispatchTouchEvent(MotionEvent ev)方法,在ACTION_DOWN中判断是否可以下拉或者上拉,如果可以,就拦截此次事件

滑动处理

ACTION_MOVE中,计算每次滑动的差值diffY,然后使用offsetTopAndBottom() 进行滑动

手指抬起处理

ACTION_UP中,获取已滑动的距离scrollY,然后使用ValueAnimator计算每一帧滑动的距离,最后再次使用offsetTopAndBottom()进行滑动

多点触控

多点触控其实很简单,都是有套路可寻的,只要单点触控没问题,多点触控其实很好实现,因为虽然是多点触控,但是实际上只有一个手指处于活跃状态。

关于dispatchTouchEvent(MotionEvent ev)方法

在Android的整个事件传递体系中,很多人都知道dispatchTouchEvent(MotionEvent ev)方法是用来分发事件的,分发后的事件如果由自身处理,则需要重写
onTouchEvent(MotionEvent ev)进行相关操作,但有时候这种方式很麻烦,特别是在我们继承已有的Layout时,比如ScrollViewFrameLayout等,
因为这些Layout本身就有一些事件的处理机制,如何在不破坏已有的处理机制的基础上再加上我们自己的处理逻辑,这是一个较为困难的问题。

dispatchTouchEvent(MotionEvent ev)方法要先于onInterceptTouchEvent(MotionEvent ev)方法执行,我们可以在dispatchTouchEvent(MotionEvent ev)方法中处理一些拦截逻辑,这比在onInterceptTouchEvent(MotionEvent ev)方法中处理拦截逻辑有时候会更好。因为在dispatchTouchEvent(MotionEvent ev)方法中我们可以同时处理事件的拦截以及view的滑动等操作。当然要使用哪一个方法还要视具体情况而定。

在继承ScrollView时,我们既要保证原有的ScrollView的逻辑不变,还要在此基础上添加我们自己的逻辑,使用dispatchTouchEvent(MotionEvent ev)是一个比较好的选择。

核心代码

  @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        int action = ev.getAction() & MotionEvent.ACTION_MASK;        switch (action) {            case MotionEvent.ACTION_DOWN:                if (mAnimator.isStarted()) mAnimator.cancel();                mActivePointerId = ev.getPointerId(0);                mLastY = (int) ev.getY();                canPullDown = isCanPullDown();                canPullUp = isCanPullUp();                break;            case MotionEvent.ACTION_MOVE:                final int y = (int) ev.getY(ev.findPointerIndex(mActivePointerId));                int diffY = y - mLastY;                if ((canPullUp || canPullDown)) {                    ViewParent parent = getParent();                    if (parent != null) parent.requestDisallowInterceptTouchEvent(true);                    move(diffY);                }                mLastY = y;                break;            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                if (canPullDown || canPullUp) {                    final int scrollY = mChild.getTop();                    mLastFrameValue = scrollY;                    mAnimator.setIntValues(scrollY, 0);                    mAnimator.start();                }                mActivePointerId = MotionEvent.INVALID_POINTER_ID;                break;            case MotionEvent.ACTION_POINTER_DOWN:                final int downActionIndex = ev.getActionIndex();                mLastY = (int) ev.getY(downActionIndex);                mActivePointerId = ev.getPointerId(downActionIndex);                break;            case MotionEvent.ACTION_POINTER_UP:                final int upActionIndex = ev.getActionIndex();                final int pointerId = ev.getPointerId(upActionIndex);                if (pointerId == mActivePointerId) {                    final int newPointerIndex = upActionIndex == 0 ? 1 : 0;                    mActivePointerId = ev.getPointerId(newPointerIndex);                }                mLastY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));                break;        }        super.dispatchTouchEvent(ev);//分发父view的事件        return true;    }

以上是实现整个效果的核心代码,更多详细处理逻辑请移步至PopularEffect。

PopularEffect是一个汇集各种效果的仓库,除此之外,还对每种效果的实现方式都做了剖析,并编写了对应的样例。虽然仓库已经建立了快一年,但实际上并没有精力去维护,现在打算把它维护起来,我只要有时间,都会尽量添加一些效果分析和样例,虽然只有我一个人维护这个仓库,注定速度上会很慢(分析效果以及编写样例是最为费时费力的),但我相信水滴石穿! 感兴趣的可以stat一波(手动滑稽)。

阅读全文
0 0
原创粉丝点击