您的位置:首页 > 移动开发 > Android开发

android-Ultra-Pull-To-Refresh/SwipeRefreshLayout嵌套ViewPager/ScrollView滑动冲突解决

2016-05-04 23:36 246 查看

android-Ultra-Pull-To-Refresh/SwipeRefreshLayout嵌套ViewPager滑动冲突解决

欢迎大家关注Android开源网络框架NoHttp:https://github.com/yanzhenjie/NoHttp

在线直播视频和代码下载:http://pan.baidu.com/s/1miEOtwG

版权声明:转载请注明本文转自Yolanda的CSDN博客: http://blog.csdn.net/yanzhenjie1003

QQ交流群1:46523908

QQ交流群2:46505645

群资源非常宝贵,请不要重复加群,谢谢。

前戏

  每次必不可少的前戏又来了。发文时
Android-PullToRefresh
这个框架已经停止维护3年了,很多人在关心我们现在用什么框架好,这里给大家推荐两个。一个是可爱可亲起可恨的Google官方v4包自带的
SwipeRefreshLayout
,一个是liaohuqiu同学在Github上发表的
android-Ultra-Pull-To-Refresh
,我强烈推荐使用后者,后者扩展性好,而且是国人自己开发的框架,交互设计也符合Google官方的操作规范。

  android-Ultra-Pull-To-Refresh托管地址:https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh/

  英文版官网:http://android-ultra-ptr.liaohuqiu.net/

  中文版官网:http://android-ultra-ptr.liaohuqiu.net/cn

  

  力荐
android-Ultra-Pull-To-Refresh
的原因是它特别易于扩展,而且api丰富简单,作者也是咱中国人,但是根本原因还是这个框架确实特别好用。而且
SwipeRefreshLayout
有的功能和动画它都有。

高潮

  现在引出我们的问题,我们好多人都使用
SwipeRefreshLayout
,如果不需要修改Header和动画的话,应该可以满足大多数需求了。上面也说了
SwipeRefreshLayout
有的动画和功能
android-Ultra-Pull-To-Refresh
都有,但是我们现在的情况是,不论使用这两者其中的哪个框架和
ViewPager
互相嵌套的时候(比如作为Banner),二者与
ViewPager
都会发生滑动冲突。

SwipeRefreshLayout与ViewPager嵌套滑动冲突

  先来解决个人认为次要且相对简单的
SwipeRefreshLayout
的冲突问题,只需要在
ViewPager
滑动的时候禁用了
SwipeRefreshLayout
就可以了,代码如下:

ViewPager mViewPager;
SwipeRefreshLayout mRefreshLayout;
...
mViewPager.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:// 经测试,ViewPager的DOWN事件不会被分发下来
case MotionEvent.ACTION_MOVE:
mRefreshLayout.setEnabled(false);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mRefreshLayout.setEnabled(true);
break;
}
return false;
}
});


  做个简单的解释,当我们手指在ViewPager上按下或者移动的时候让SwipeRefreshLayout不可用,当我们手指抬起或者滑动到ViewPager之外的时候让SwipeRefreshLayout可用。

  此方法同样适用于android-Ultra-Pull-To-Refresh(达哥做了测试后发现不是很准),但是是不是有点麻烦了,每次都需要这么处理一下,或者要继承系统的View做个小的封装。因此推荐看官使用android-Ultra-Pull-To-Refresh,我们接下来解决android-Ultra-Pull-To-Refresh与ViewPager嵌套时的滑动冲突。

android-Ultra-Pull-To-Refresh与ViewPager嵌套滑动冲突

  当我们不亦乐乎的在项目中使用android-Ultra-Pull-To-Refresh时,当我们用android-Ultra-Pull-To-Refresh和ViewPager互相嵌套的时候猛然发现,二者居然有滑动冲突,我们在框架主页的
issues
中也能看到这个问题,但是一直没有被解决,心中上万只草泥马奔腾而过,这时候达哥我就要出来来拯救世界了,看我们如何来解决这个问题。

  我们在android-Ultra-Pull-To-Refresh的源码托管ReadMe的末尾发现work with ViewPager: disableWhenHorizontalMove(),翻译过来就是和ViewPager一起使用,但是我们调用了这个方法后发现然并卵,那么问题在哪里呢?

  达哥阅读了android-Ultra-Pull-To-Refresh的PtrFramLayout源码后发现修改了这个bug,这里直接提供修改方法,我们打开PtrFramLayout.java这个类,找到308行代码:

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) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
if (mPtrIndicator.isInStartPosition()) {
mPreventForHorizontal = true;
}


  原因是,我们既然要禁用横向滑动的拦截,那么判断操作为横向并且要禁用横向拦截时给
mPreventForHorizontal
赋值为
true
即可,并不需要判断滑动距离。

同时
PtrFramLayout
的第113、114行代码就无用了,可以注释了,第54行
mPagingTouchSlop
成员变量也无用,可以注释了。

使用的同学请注意还是需要调用
PtrFrameLayout.disableWhenHorizontalMove(true)
来灵活控制是否需要拦截。

收尾福利

问题描述:
SwipeRefreshLayout
android-Ultra-Pull-To-Refresh
嵌套
ScrollView
ListView
GrdiView
RecyclerView
时,页面往上滑再往下滑时还没滑到顶部就触发了下拉刷新。

SwipeRefreshLayout嵌套上下滑动View时滑动冲突

  解决
SwipeRefreshLayout
冲突方案,我们来看看
SwipeRefreshLayout
源码发现一个方法
canChildScrollUp()
,意思是子View可以向上滚动吗,当返回true的时候
SwipeRefreshLayout
就不能被下拉刷新了,细细分析了代码之后发现这里只对子View做了判断,而我们实际开发中往往会在
SwipeRefreshLayout
中再嵌套一个ViewGroup,再在这个ViewGroup中放一个ScrollView,这时候我们说的问题就出现了。

  

  
canChildScrollUp()
方法源码


public boolean canChildScrollUp() {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0).getTop() < absListView.getPaddingTop());
} else {
return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}


  所以我们需要重写
SwipeRefreshLayout
canChildScrollUp()
方法,完整代码如下:

public class MySwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout {

private boolean canChildScrollUp;

public MySwipeRefreshLayout(Context context) {
super(context);
}

public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

public void setCanChildScrollUp(boolean canChildScrollUp) {
this.canChildScrollUp = canChildScrollUp;
}

@Override
public boolean canChildScrollUp() {
return canChildScrollUp;
}
}


  当我们监听到
SwipeRefreshLayout
中可以上下滑动的View向上滚动了就调用
swipeRefreshLayout.setChildScrollUp(true)
,当我们监听到
SwipeRefreshLayout
中可以上下滑动的View向下滚动并且已经到顶部了,就调用
swipeRefreshLayout.setChildScrollUp(false)


  但是我们每次嵌套都需要对的子View的滑动做判断,所以我们动态的指定
SwipeRefreshLayout
中的contentView,并且使用
SwipeRefreshLayout
canChildScrollU()
方法中的代码来判断,简直完美啊:

public class MySwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout {

private View contentView;

public MySwipeRefreshLayout(Context context) {
super(context);
}

public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

public void setContentView(View contentView) {
this.contentView = contentView;
}

@Override
public boolean canChildScrollUp() {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (contentView instanceof AbsListView) {
final AbsListView absListView = (AbsListView) contentView;
return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0).getTop() < absListView.getPaddingTop());
} else {
return ViewCompat.canScrollVertically(contentView, -1) || contentView.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(contentView, -1);
}
}
}


和android-Ultra-Pull-To-Refresh嵌套上下滑动View时滑动冲突

  其实PtrFramLayout的刷新接口PtrHandler提供了一个方法
checkCanDoRefresh(...)
来检查是否允许刷新,我们只需要在这里做判断返回
true
false
就OK了。

  然而我们的作者liaohuqiu同学也是想的非常周到,提供了一个
PtrDefaultHandler
类,实现了和
SwipeRefreshLayout
同样的判断,so看到这里的同学肯定知道怎么改了吧,我们先来看看
PtrDefaultHandler
源码:

public abstract class PtrDefaultHandler implements PtrHandler {

public static boolean canChildScrollUp(View view) {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (view instanceof AbsListView) {
final AbsListView absListView = (AbsListView) view;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return view.getScrollY() > 0;
}
} else {
return view.canScrollVertically(-1);
}
}

/**
* Default implement for check can perform pull to refresh
*
* @param frame
* @param content
* @param header
* @return
*/
public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
return !canChildScrollUp(content);
}

@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
return checkContentCanBePulledDown(frame, content, header);
}
}


  所以我们在使用刷新接口的时候使用
PtrDefaultHandler
就好了,重写
checkCanDoRefresh()
方法:

private ScrollView scrollView;

...

private PtrDefaultHandler defaultHandler = new PtrDefaultHandler() {

@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
return !canChildScrollUp(scrollView);
}

@Override
public void onRefreshBegin(PtrFrameLayout frame) {
// 做刷新的操作
...
}
};


  完美啊,有疑问的同学下面留言,收官睡觉。

  欢迎大家关注我的Android开源网络框架NoHttp,源码托管地址:https://github.com/yanzhenjie/NoHttp
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: