前言:
下拉刷新在众多的App中可谓是屡见不鲜,在Version19.1之后Google将SwipeRefreshLayout添加到了support-v4包里面,是刷新控件,它只允许一个直接子类,操作上比较简单但也不免有一些坑,如滑动冲突SwipeRefreshLayout+ViewPager等等。
Part 1、SwipeRefreshLayout的基础应用
xml如:
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/srl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#FF0000">
</FrameLayout>
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
java中进行设置
mRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.srl);
mRefreshLayout.setEnabled(true);
mRefreshLayout.setDistanceToTriggerSync(300);
mRefreshLayout.setProgressViewOffset(false, 200, 300);
mRefreshLayout.setColorSchemeResources(R.color.swipe_color_1,R.color.swipe_color_2,R.color.swipe_color_3,R.color.swipe_color_4);
mRefreshLayout.setSize(SwipeRefreshLayout.LARGE);
mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mRefreshLayout.setRefreshing(false);
}
}, 5000);
}
});
tips:
1、setDistanceToTriggerSync(int) // 设置手指在屏幕下拉多少距离会触发下拉刷新
2、setProgressViewOffset(boolean,int,int)//设置小圆圈的偏移量 1、是否进行缩放 2、开始出现的位置 3、最远出现的位置
3、setColorSchemeResources(int...color)//设置进度条的颜色主题 4种
4、setSize(int) //设置圆圈的大小,这里只提供了两种 DEFAULT和LARGE
5、setOnRefreshListener()//设置刷新的监听
效果~
当然你也可以在代码中将View添加在SwipeRefreshLayout里面,如:
View view = super.onCreateView(inflater, container, savedInstanceState);
mRefreshLayout = new MySwipeRefreshLayout(getContext());
mRefreshLayout.addView(view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mRefreshLayout.setLayoutParams(
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
tips:
1、这里继承了ListFragment,生成View使用super.onCreateView()方法
2、addView方法添加ViewGroup.LayoutParams.MATCH_PARENT来确保填充fragment或者是SwipeRefreshLayout控件
Part 2、SwipeRefreshLayout进阶使用
1、实现进入Activity便进行刷新功能
代码如下:
mRefreshLayout.setRefreshing(true);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mRefreshLayout.setRefreshing(false);
}
},2000);
通过查看源码
public void setRefreshing(boolean refreshing) {
if (refreshing && mRefreshing != refreshing) {
// scale and show
mRefreshing = refreshing;
...
mNotify = false;
startScaleUpAnimation(mRefreshListener);
} else {
setRefreshing(refreshing, false /* notify */);
}
}
可以看到不管是if还是else都涉及到了mRefreshListener,查看mRefreshListener
private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
...
@Override
public void onAnimationEnd(Animation animation) {
if (mRefreshing) {
// Make sure the progress view is fully visible
mProgress.setAlpha(MAX_ALPHA);
mProgress.start();
if (mNotify) {
if (mListener != null) {
mListener.onRefresh();
}
}
mCurrentTargetOffsetTop = mCircleView.getTop();
} else {
reset();
}
}
};
由于setRefreshing(boolean)方法将mNotify置为false,所以必然不会执行到mListener.onRefresh()方法。如果想通过手动设置刷新并且触发事件则需要调用
private void setRefreshing(boolean refreshing, final boolean notify) {
因为是私有的方法则通过反射调用
try {
Class clazz = mRefreshLayout.getClass();
Method method = clazz.getDeclaredMethod("setRefreshing",boolean.class,boolean.class);
method.setAccessible(true);
method.invoke(mRefreshLayout,true,true);
}catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
2、当存在多个View引起SwipeRefreshLayout的bug
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.andly.administrator.andly_md01.swiperefleshlayout.fragment.MultiViewFragment">
<GridView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="2" />
<TextView
android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="空内容"
android:layout_gravity="center"/>
</FrameLayout>
在布局文件中除了ListView或GridView之类的滑动控件外又新增了其它的控件,刚好这些控件是在滑动控件的上面则会出现如下效果
无论滑动到哪里,只要是向上滑动就会出现滑动控件
解决方案:
@Override
public boolean canChildScrollUp() {
}
重写此方法,当滑动的时候会不停的调用此方法,只需要在这里面判断当前是否ListView或GridView的顶部即可
@Override
public boolean canChildScrollUp() {
if (mSwipeableChildren != null && mSwipeableChildren.length > 0) {
for (View view : mSwipeableChildren) {
if (view != null && view.isShown() && !canViewScrollUp(view)) {
// 如果这个View显示和不能向上滑动则返回false
return false;
}
}
}
return true;
}
private boolean canViewScrollUp(View view) {
if (android.os.Build.VERSION.SDK_INT >= 14) {
// 4.0以上通过canScrolVertical方法来判断
return ViewCompat.canScrollVertically(view, -1);
} else {
if (view instanceof AbsListView) {
// 当4.0以上则通过当前可见Item的位置和第一个Item的top值来判断
final AbsListView listView = (AbsListView) view;
return listView.getChildCount() > 0 &&
(listView.getFirstVisiblePosition() > 0
|| listView.getChildAt(0).getTop() < listView.getPaddingTop());
} else {
// 对于其它非滑动类型控件通过ScrollY来判断
return view.getScrollY() > 0;
}
}
}
tips:
1、SwipeRefreshLayout控件只有一个直接子控件
private void ensureTarget() {
// Don't bother getting the parent height if the parent hasn't been laid
// out yet.
if (mTarget == null) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (!child.equals(mCircleView)) {
mTarget = child;
break;
}
}
}
}
从源码可以看出将第一个子控件赋值给了mTarget2、canChildScrollUp():ChildView是否可以向上滑动,如果可以则返回true(即不显示滑动进度)
Part 3、SwipeRefreshLayout冲突解决
ViewPager+SwipeRefreshLayout引起的滑动冲突,当你在SwipeRefreshLayout引入ViewPager时,会引起当你从左下右下进行滑动的时候出现刷新的效果而不是ViewPager翻页的效果
效果~
解决方案:
之所以出现如上的效果是因为触摸事件被SwipeRefreshLayout吸收了,所以ViewPager便不能处理该事件,我们只需要重新复写SwipeRefreshLayout
public boolean onInterceptTouchEvent(MotionEvent ev) {
在这里面通过判断触摸偏移量来决定是否将事件交给ViewPager来处理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startX = ev.getX();
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if(isVp){//如果当前是ViewPager滑动状态则直接交给ViewPager来进行处理
return false;
}
float endX = ev.getX();
float endY = ev.getY();
float dx = Math.abs(endX-startX);
float dy = Math.abs(endY - startY);
if(dx > touchSlop && dx > dy){
isVp = true;
return false;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isVp = false;
break;
}
return super.onInterceptTouchEvent(ev);
}
ViewPager+SwipeRefreshLayout的更多详情请看:ViewPager+SwipeRefreshLayout冲突