Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

解决android-Ultra-Pull-To-Refresh下拉刷新组件中嵌套ViewPager的一个bug #133

Open
dengyin2000 opened this issue Aug 1, 2015 · 42 comments

Comments

@dengyin2000
Copy link

PtrFrameLayout的dispatchTouchEvent方法实现没有考虑到requestDisallowInterceptTouchEvent,这样导致ViewPager不能通过requestDisallowInterceptTouchEvent解决PrtFrameLayout和ViewPager的事件冲突。

解决办法请看我的博客。http://dengyin2000.iteye.com/blog/2232210

@Lee-swifter
Copy link

我试了下,按照你的博客来,还是没能解决问题。ViewPager可正常滑动,但是此时下拉刷新却很容易出不来。

@liaohuqiu
Copy link
Owner

PtrFrameLayout.disableWhenHorizontalMove(true) 这个不能解决问题吗?

@Lee-swifter
Copy link

试了下,没什么效果,可能是因为我用的是第三方的banner,不是原生ViewPager的缘故。
我再尝试一下其他的方法吧,谢谢 @liaohuqiu @dengyin2000

@Aspsine
Copy link

Aspsine commented Sep 12, 2015

public boolean dispatchTouchEvent(MotionEvent e) {

}

move事件中的

if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
    if (mPtrIndicator.isInStartPosition()) {
        mPreventForHorizontal = true;
    }
}
if (mPreventForHorizontal) {
    return dispatchTouchEventSupper(e);
}

修改为:

if (mDisableWhenHorizontalMove && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY * mPtrIndicator.getResistance()))) {
    return dispatchTouchEventSupper(e);
}

然后将
mPagingTouchSlop = conf.getScaledTouchSlop() * 2,
改为
mPagingTouchSlop = conf.getScaledTouchSlop();

  1. 因为在横向滑动的过程中,可能会触发下拉导致mPtrIndicator.isInStartPosition()这个条件不成立,导致不会调用supper方法。
  2. offsetY在PtrIndicator做了offsetY = offsetY/mResistance 处理。(这个不影响,不过我觉得还是应该改成offsetY * mPtrIndicator.getResistance())
  3. 让横向滑动的mPagingTouchSlop小一点可以让横滑容易一点。

感谢 @liaohuqiu 提供这么伟大的库,我从中学到了很多。

@xiaoluome
Copy link

@Aspsine 你好,你的这种改法也是解决不了问题啊,我用的是GridView里面有Item是横向RecyclerView,还有轮播图ViewPager,都有点问题,横向滑动时,都会往下一点移动,导致不能横向

@Aspsine
Copy link

Aspsine commented Oct 28, 2015

@ixiaoluo
好久不见。之前写的,粗心没测试。现在更新了一下,测试没问题。
希望能帮到你。

@mmchong3173
Copy link

@Aspsine 请问你更新在哪里。

@Aspsine
Copy link

Aspsine commented Nov 4, 2015

@mmchong3173
我更新了之前给的解决方案。从这楼开始倒数第5楼。

@mmchong3173
Copy link

@Aspsine 我看到了,那意味着我只能下载源码修改后再依赖到项目里面咯?我现在是这么使用的compile 'in.srain.cube:ultra-ptr:1.0.11'

@Aspsine
Copy link

Aspsine commented Nov 4, 2015

@mmchong3173
是的。也可以以lib项目引入,修改源码,测试好。然后build一次,在build目录里面找到aar包,然后删除依赖项目,直接引用aar。等秋大 fix了以后再更换依赖。总之public 的api不变。

@mmchong3173
Copy link

@Aspsine 你好,我试了你的方案,发现viewpager在move的过程中只要稍稍有垂直方向的移动便会回到原来的位置,实际效果就是很难翻页。我的xml布局是直接在PtrFrameLayout里面嵌套一个v4包的Viewpager,而且我也把此方案放到了该项目的demo中,效果也是一样的,不知道是不是我使用的方式不对,望能指出,谢谢。

@clear2zero
Copy link

@ixiaoluo 看了一下描述的问题 , 昨天我也碰见场景跟你类似的问题 ,倒是解决了 , 我先说下我的场景 , 整个APP 最外层用viewpager做的各种子页面 , 每一个子页面头部是一个广告栏(viewpager实现的轮播banner)和listview 或者是gridView,ps:我的是gridview , 为了解决banner和gridview一起滚动 , 我将banner 做成了gridview 的header , 发现 banner的横向滚动不起作用 , 起初怀疑是最外层的viewpager拿走了banner里面viewpager的事件 , 就将外层viewpager 当作参数传递进来 , 在dispatchTouchEvent的时候调用外层viewpager.requestDisallowInterceptTouchEvent(true),也不起作用 , 然后我开始 怀疑是PtrFrameLayout拿走了事件 ,但是看了看PtrFrameLayout的事件分发的代码 , 如果设置了disableWhenHorizontalMove(true),PtrFrameLayout不会去终止横向的事件的 , 那么最后只剩下最后一个父控件阻止了事件,就是gridview了 , 然后我尝试在dispatchTouchEvent 禁用gridview的事件 , 结果还真好了 。 至于原因看gridview 对事件的处理 , gridview的父类AbsListView.onInterceptTouchEvent 有好几处reture true 终止事件的地方 ,并且没有判断横向移动 。 所以可以尝试我的方法 。 如果在问题再联系。

@pip1998
Copy link

pip1998 commented Nov 5, 2015

if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
    if (mPtrIndicator.isInStartPosition()) {
        mPreventForHorizontal = true;
    }
}

在以上代码中,作者的offsetX是touchevent与上一次 touchevent的差。错误在于作者将mPagingTouchSlopoffsetX做比较。因为offsetX实质是ΔX,不是距离。正确的比较方案应该是mPagingTouchSlop与(当前touchevent-按下时touchevent)做比较,进而给mPreventForHorizontal赋值

@mmchong3173
Copy link

发现只要PtrFrameLayout的里面的content不处在屏幕的最顶部,viewpager的滑动就没有问题。

@007wuqi
Copy link

007wuqi commented Nov 20, 2015

有正解吗

@wizardmly
Copy link

PtrFrameLayout里面包ScrollView, 然后ScrollView里面又嵌套ViewPager, 现在还存在这个问题, 不知道有解决办法了每

pip1998 added a commit to pip1998/android-Ultra-Pull-To-Refresh that referenced this issue Dec 17, 2015
@q232916067
Copy link

@Aspsine 大神,我按照你给的方案修改了,但是效果还是不理想,我viewpager里面的一个页面有轮播图。轮播图是在listview的顶部,滑动轮播图还是会触发下拉刷新的效果

@Lee-swifter
Copy link

我找到了一个解决这个问题的好方法,代码如下:

mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
    @Override
    public void onPageScrollStateChanged(int state) {
        mPtrFrame.setEnabled(state == ViewPager.SCROLL_STATE_IDLE);
    }
});

我使用这段代码,效果挺好的。只要是滑动ViewPager时便不会将下拉刷新滑动出来了。这种方法同样也适用于官方的SwipeRefreshLayoutViewPager配合。

我测试了这个项目和官方的下拉刷新控件,都能完美解决问题。我相信这段代码同样适用于你们的问题。

@captain-miao
Copy link

captain-miao commented Apr 24, 2016

@18671183990 这样向左侧滑、向右侧滑、下拉,还是体验不好吧?
对于像ViewPage侧滑的区域,[向左侧滑、向右侧滑] 优先级 高于 下拉:
目前fork改写的方式:PtrFrameLayout.java

    if (isHorizontalMoveArea(x, y) && !mPreventForHorizontal && (Math.abs(offsetX) > 0 && (Math.abs(offsetX) - Math.abs(offsetY) > 0.4))) {
        if (mPtrIndicator.isInStartPosition()) {
            mPreventForHorizontal = true;
        }
    }

    //For Horizontal Move Area, such as ViewPage
    private HorizontalMoveArea mHorizontalMoveArea;
    private boolean isHorizontalMoveArea(float x, float y){
        return mDisableWhenHorizontalMove || mHorizontalMoveArea == null || mHorizontalMoveArea.isHorizontalMoveArea(x, y);
    }

    public void setHorizontalMoveArea(HorizontalMoveArea horizontalMoveArea) {
        mHorizontalMoveArea = horizontalMoveArea;
    }

    public interface HorizontalMoveArea {
        boolean isHorizontalMoveArea(float x, float y);
    }

为了拿到准确的rect,需要ViewPage放在ViewGroup Layout中,activity_view_pager.xml

        mPtrFrame.setHorizontalMoveArea(new PtrFrameLayout.HorizontalMoveArea() {
            @Override 
            public boolean isHorizontalMoveArea(float x, float y) {
                Rect rect = new Rect();
                mFrameLayoutViewPager.getLocalVisibleRect(rect);
                return rect.contains((int)x, (int)y);
            } 
        }); 

@yanzhenjie
Copy link

yanzhenjie commented May 4, 2016

目前我通过修改PtrFramLayout源码解决了这个问题,目前的几种使用场景如下:

  1. ViewPager放在PtrFramLayout的顶部。
  2. PtrFramLayout中嵌套ScrollView,ScrollView中放一个ViewGroup,ViewGroup的顶部放ViewPager。
  3. ViewPager中嵌套PtrFramLayout,好吧其实前三种就是PtrFramLayout和ViewPager相互嵌套啦。
  4. ViewPager放在Layout的非顶部。

其中第三种滑动没有问题,没有冲突,主要就是第一种和第二种。需要修改的代码仅仅一行:

if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
    if (mPtrIndicator.isInStartPosition()) {
        mPreventForHorizontal = true;
    }
}

把上述代码的if判断的Math.abs(offsetX) > mPagingTouchSlop这一句去掉就可以了,完整代码如下:

if (mDisableWhenHorizontalMove && !mPreventForHorizontal && Math.abs(offsetX) > Math.abs(offsetY)) {
    if (mPtrIndicator.isInStartPosition()) {
        mPreventForHorizontal = true;
    }
}

原因是,我们既然要禁用横向滑动的拦截,那么判断操作为横向并且要禁用横向拦截时给mPreventForHorizontal赋值为true即可,并不需要判断滑动距离。
同时PtrFramLayout的第113、114行代码就无用了,可以注释了,第54行mPagingTouchSlop成员变量也无用,可以注释了。
使用的同学请注意还是需要调用PtrFrameLayout.disableWhenHorizontalMove(true)来灵活控制是否需要拦截。

@18671183990
Copy link

18671183990 commented May 19, 2016

我的情况是ListView的Header中有一个ViewPager,有冲突,试了上面的一些方法都不太理想,后来给ViewPager添加了touch监听,在down的时候设置mPtrFrame.setEnabled(false),然后move时判断
if (Math.abs(event.getY() - downY) > Math.abs(event.getX() - downX) && Math.abs(event.getY() - downY)>100f) {
mPrtLayout.setEnabled(true);
}
大家可以试下看,新手发帖 太紧张了

@18671183990
Copy link

@captain-miao 是的,虽然比改之前好多了,但体验确实有问题,多谢大神提醒.

@q232916067
Copy link

不知道在listview中有一个heard是横向的recycleview怎么解决呢。最外层是ptrframeayout

@yanzhenjie
Copy link

@q232916067 你看下我在上面的一条回复,按照我那样重写之后,调用PtrFrameLayout.disableWhenHorizontalMove(true)应该就好了。

@qingcdft
Copy link

@yanzhenjie,按照你的方案修改,OK了,感谢你的方案!

@LuoboDcom
Copy link

@yanzhenjie 你好,我参考你的代码,去掉了Math.abs(offsetX) > mPagingTouchSlop,但是横向滑动还是很不灵敏。我是在recyclerView 嵌套了 ViewPager。

@yanzhenjie
Copy link

@LuoboDcom 按照我那样重写之后,调用PtrFrameLayout.disableWhenHorizontalMove(true)应该就好了。这样会禁用左右滑动的,所以应该没有问题,重写了之后要用新的代码的类喔,不要忘记要在禁用左右滑动的地方调用PtrFrameLayout.disableWhenHorizontalMove(true)。

@LuoboDcom
Copy link

@yanzhenjie 谢谢,按照你的方法,可以了。就是下拉刷新时,可以看到recyclerView被下拉了,但是header没出来,所以,感觉下拉的灵敏度变低了

@xanaduo
Copy link

xanaduo commented Aug 25, 2016

能不能把改的放到这个库里面,这样你们问题是解决了,但是库还是有问题吧

@pzhangleo
Copy link

现在库的所有人没有在维护了吧,好多pr都没有处理,我建议咱们fork一个,大家一起维护吧

@frankfancode
Copy link

frankfancode commented Dec 7, 2016

场景:
PtrFrameLayout 中有竖向 Recyclerview,竖向Recyclerview 中有 ViewPager 写得横向轮播图 和 横向 Recyclerview。

问题:
横向滑动轮播图时,卡顿断续。

解决方法:
修改 PtrFrameLayout 中代码,

mPagingTouchSlop = conf.getScaledTouchSlop() * 2;

改成

 mPagingTouchSlop = conf.getScaledTouchSlop();

使 横滑 灵敏。

if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
    if (mPtrIndicator.isInStartPosition()) {
        mPreventForHorizontal = true;
    }
}
if (mPreventForHorizontal) {
    return dispatchTouchEventSupper(e);
}

改成

if (mDisableWhenHorizontalMove && (Math.abs(offsetX) > mHorizontalMoveSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
    mPreventForHorizontal = true;
}

if (mPreventForHorizontal) {
    dispatchTouchEventSupper(e);
    return true;
  /*一旦横向滑动,就禁止竖向滑动,免得滑了一部分PtrFrameLayout将事件夺走 又开始竖向滑动了。因为大拇指向左滑动时的弧线垂直导数会越来越大  offsetY 可能会 大过 offsetX */

}

新增全局变量
private int mHorizontalMoveSlop=20;//设置水平滑动的灵敏性

之所以不用 TouchSlop 是因为 不同的手机上的 TouchSlop 大小不一样,改之后效果不一样,一加上的是 24,moto 上的 是 72 ,72对于横滑来说太大了,我都统一改小了。不同的手机上效果就一样了

感谢秋大 和 Aspsine 。

@tangjie1989
Copy link

mPagingTouchSlop = conf.getScaledTouchSlop() * 2; 修改为 mPagingTouchSlop = conf.getScaledTouchSlop() recyclerview+viewpager 外套下拉刷新 改完后viewpager滑动好很多。

@hui453694187
Copy link

请问在WebView 的头部 有一个 banner 的时候导致, banner 左右滑动很容易触发下拉刷新, 这种 如何处理, 如何自己处理事件的分发, 自定义手指下滑距离和左右滑动距离来判断是下啦,还是滑动banner ?

@imliujun
Copy link

这个问题都这么久了,我感觉还是我的解决方案效果最好。 @yanzhenjie 你的这个方案是解决了左右滑的问题,但是下拉刷新变得非常不灵敏

#181 是我提交的PR

@shenminjie
Copy link

shenminjie commented Mar 7, 2017

感谢 @yanzhenjie 的解决方案,但你这个存在着下拉刷新不太灵敏,用户体验也不太好。我这里有个解决方案,在你这个基础上,添加HorizontalMoveView获取水平滑动的手势监听,当手势在HorizontalMoveView才进行拦截,这样就减少了其他区域的下拉事件的冲突,提高下拉刷新的灵敏。

public interface HorizontalMoveView {
   /**
     * 获取当前事件
     * MotionEvent.ACTION_DOWN
     * MotionEvent.ACTION_MOVE
     * 用于阻断手势
     */
    int getCurrentTouchEvent();
}

PtrFrameLayout.java:

  /**
     * 声明全局变量
     * 水平滑动的视图
     */
    private HorizontalMoveView mHorizontalMoveView;

    /**
     * 设置冲突手势的view,当手势在此view中,才阻断其事件
     * 提高用户体验
     *
     * @param horizontalMoveView horizontalMoveView 水平滑动的view
     */
    public void setHorizontalMoveView(HorizontalMoveView horizontalMoveView) {
        mHorizontalMoveView = horizontalMoveView;
    }

line 317:

if (mDisableWhenHorizontalMove && !mPreventForHorizontal && Math.abs(offsetX) > Math.abs(offsetY)) {
                    //判断手势是否在水平滚动的view中,如果没有正常下拉刷新,如果水平上,直接阻挡
                    boolean isTouch = mHorizontalMoveView != null &&
                            (mHorizontalMoveView.getCurrentTouchEvent() == MotionEvent.ACTION_DOWN || mHorizontalMoveView.getCurrentTouchEvent() == MotionEvent.ACTION_MOVE);
                    if (mPtrIndicator.isInStartPosition() && isTouch) {
                        mPreventForHorizontal = true;
                    }
                }

各位有需要可以查看我的fork

shenminjie added a commit to shenminjie/android-Ultra-Pull-To-Refresh that referenced this issue Mar 8, 2017
@dingbuoyi
Copy link

dingbuoyi commented Mar 9, 2017

@lzyzsd 这个PR什么时候可以合并进去。。。这个issue还是挺严重的

@dingbuoyi
Copy link

dingbuoyi commented Mar 9, 2017

@Lee-swifter 你的方法不推荐了,就算能解决VIEW PAGER的问题,也不适用于横向 recycle view。

@dingbuoyi
Copy link

dingbuoyi commented Mar 9, 2017

@pip1998
@imliujun
你们的代码都试过,我测试场景是scrollview里面嵌套了横向recycleview,都没有效果

@msdx
Copy link

msdx commented Apr 20, 2017

我也遇到了这个问题,不过我的问题不复杂,退回1.0.9之后可以解决。但是我看了一下PtrFrameLayout的实现,有些粗浅看法:
在Android的事件模型中,一个事件会经过分发、拦截、消费这三个过程。顶层往下分发,也可以拦截事件不让其往下分发,如果下层没有消费事件,上层才消费。
由于上层可以中断事件向下层的传递,而存在着这样的场景:上层可能需要拦截事件向下层的传递,而下层可能又需要事件。所以会有requestDisallowInterceptTouchEvent的情景。
而在PtrFrameLayout中,对事件的处理选择了在分发的这个过程中处理,从出发点上它就已经忽略(忽视)了下层不想让上层拦截事件的情况。
事件是否不允许被拦截使用mGroupFlags标志位判断,不过很遗憾该变量是@hide

@pzhangleo
Copy link

考虑重启一个项目,来完善这个库吧,作者已经没时间维护了吧

@yunmenggyy
Copy link

yunmenggyy commented Nov 2, 2017

大家好,我也遇到了这个问题,我的页面顶级是PtrFrameLayout,然后里面包含了一个scrollView,scrollView中包含一个圆形的自定义sickBar。我在滑动SickBar的时候,事件会被外层消费掉。我也直接使用方法requestDisallowInterceptTouchEvent但是没效果 最终的解决方案是在自定义的sickBar中添加一个触摸监听,滑动的时候调用PtrFrameLayoutsetEnabled(false),然后滑动结束以后调用setEnabled(true)`

@nailperry-zd
Copy link

@yanzhenjie 使用你的方案后,下拉刷新变得很不灵敏了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests