前两天刚写了一篇关于SwipeRefreshLayout的文章,讲的是在它的内部不是ListView,而是还有一个父布局,如FrameLayout时,会出现进度圈不显示的问题。今天的问题也跟那个bug类似,如果SwipeRefreshLayout的内部是ListView,没有这个bug,如果内部为FrameLayout(其它诸如RelativeLayout等也一样),在FrameLayout的内部才是ListView,那么会造成SwipeRefreshLayout和ListView的滑动冲突,先看下症状,如下图:
由于我的代码是写进自己的demo集中的,有一定的抽取和优化,自己又懒得再摘出来,所以有些代码会看着很奇怪,我都加了注释,如果哪里看得很奇怪,兄弟们可以留言给我。
public class SwipeRefreshLayoutBugActivity extends BaseActivity { SwipeRefreshLayout mSwipeRefreshLayout; ListView mListView; BaseAdapter mBaseAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.srl_bug_1); mListView = (ListView) findViewById(R.id.lv_bug_swipe_refresh_layout); //这里的BaseAdapter不是android自身提供的类,而是我自己封装的一个适配器基类 mBaseAdapter = new BaseAdapter<String>(this) { @Override public ViewHolder getHolder() { return new TextViewHolder(SwipeRefreshLayoutBugActivity.this); } }; mListView.setAdapter(mBaseAdapter); loadData(); } private void loadData(){ //OpenAPI是我封装的一个类,专门用来获取数据 OpenAPI.getVirtualData(10, new OpenAPI.AbstractCallback<String>(){ @Override public void onSuccess(List list) { mBaseAdapter.setData(list); } }); } @Override public int getLayoutResource() { return R.layout.activity_bug_swipe_refresh_layout; } @Override public String getTitleContent() { return "SwipeRefreshLayout与ListView的滑动冲突"; } } <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/title"/> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/srl_bug_1" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/lv_bug_swipe_refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout>对于可能出现的这种滑动冲突,SwipeRefreshLayout源码的编写者应该早有预料,所以特意在源码中流出了一个回调,OnChildScrollUpCallback,只要为SwipeRefreshLayout设置这个监听,并书写一定逻辑,就可以解决这个问题,代码如下
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.srl_bug_1); mListView = (ListView) findViewById(R.id.lv_bug_swipe_refresh_layout); mSwipeRefreshLayout.setOnChildScrollUpCallback(new SwipeRefreshLayout.OnChildScrollUpCallback() { @Override public boolean canChildScrollUp(SwipeRefreshLayout parent, @Nullable View child) { return canScrollVertically(child); } private boolean canScrollVertically(View child) { View childView; //这里的判断需要参考layout布局是怎么写的,因为上面的layout中,SwipeRefreshLayout中是FrameLayout,所以下面才会判断它是不是ViewGroup,如果你的布局在SwipeRefreshLayout中嵌套了几层之后才是ListView的话,下面的if语句需要相应更改 if (child instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) child; childView = viewGroup.getChildAt(0); } else { //如果子View是不可滑动的控件,如TextView等,通过判断在getScrollY()方法的返回值判断 return child.getScrollY() > 0; } if (childView instanceof AbsListView) { AbsListView view = (AbsListView) childView; int count = view.getChildCount(); int firstVisiblePosition = view.getFirstVisiblePosition(); return count > 0 && (firstVisiblePosition > 0 || view.getChildAt(0).getTop() > view.getPaddingTop()); } return false; } }); … }这里有一篇文章,是重写了SwipeRefreshLayout的一个方法,上面的具体代码,参考的就是里面的代码。
解决方法1是从SwipeRefreshLayout的角度来解决这个问题,我们还可以从ListView的角度来解决。可以为ListView设置一个滑动监听,监听类如下:
public class SwipeListViewOnScrollListener implements AbsListView.OnScrollListener { private SwipeRefreshLayout mSwipeView; public SwipeListViewOnScrollListener(SwipeRefreshLayout swipeView) { mSwipeView = swipeView; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { View firstView = view.getChildAt(firstVisibleItem); // 当firstVisibleItem是第0位。如果firstView==null说明列表为空,需要刷新;或者top==0说明已经到达列表顶部, 也需要刷新 if (firstVisibleItem == 0 && (firstView == null || firstView.getTop() == view.getPaddingTop())) { mSwipeView.setEnabled(true); } else { //不加这个if判断会引起其它问题,大家自行尝试 if(mSwipeView.isRefreshing()){ mSwipeView.setRefreshing(false); } mSwipeView.setEnabled(false); } } }这个类是我从网上找到,然后自己加了一个if判断,由于时间太长,忘了是从哪里看到的,所以就不备注来源了。
在activity中设置该监听对象即可,如下:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.srl_bug_1); mListView = (ListView) findViewById(R.id.lv_bug_swipe_refresh_layout); mListView.setOnScrollListener(new SwipeListViewOnScrollListener(mSwipeRefreshLayout)); … }成功了,最后我们来张正常的运行图
2种方法,使用的时候全凭个人喜好。在解决方法2的实现过程中,我还发现了另一个问题,如果监听类的onScroll()方法中,如果没有加如下代码
if(mSwipeView.isRefreshing()){ mSwipeView.setRefreshing(false); }也就是,没有关闭进度圈,那么再次拉动时,进度圈不会出现。这个问题需要研究一下SwipeRefreshLayout的源码,看一下setEnabled()方法到底做了什么,留着以后再来看吧。
