转载请标明出处:
http://blog.csdn.net/xuehuayous/article/details/50387089
本文出自:【Kevin.zhou的博客】
前言:接着上一篇《Android PullToRefresh 分析之三、响应手势事件》,这一篇主要分析如何扩展PullToRefreshBase,来创建各式各样的刷新加载内容区域。
还是以简单的ScrollView为例进行分析,为毛我们老是欺负PullToRefreshScrollView,不急分析完ScrollView我们再分析PullToRefreshListView。翠花,上代码!
除了构造方法之外,只有四个方法,即getPullToRefreshScrollDirection()、createRefreshableView()、isReadyForPullStart()、isReadyForPullEnd()。这四个方法通过名字就可以看出来是对应的我们提出的四个问题,那么一一来分析下:
@Override public final Orientation getPullToRefreshScrollDirection() { return Orientation.VERTICAL; }
@Override protected ScrollView createRefreshableView(Context context, AttributeSet attrs) { ScrollView scrollView; if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) { scrollView = new InternalScrollViewSDK9(context, attrs); } else { scrollView = new ScrollView(context, attrs); } scrollView.setId(R.id.scrollview); return scrollView; }可以看到如果当前系统版本大于9创建的是InternalScrollVIewSDK9,其他情况是创建的系统的ScrollView,那么这个InternalScrollView是什么呢,来看下:
@TargetApi(9) final class InternalScrollViewSDK9 extends ScrollView { public InternalScrollViewSDK9(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); // Does all of the hard work... OverscrollHelper.overScrollBy(PullToRefreshScrollView.this, deltaX, scrollX, deltaY, scrollY, getScrollRange(), isTouchEvent); return returnValue; } /** * Taken from the AOSP ScrollView source */ private int getScrollRange() { int scrollRange = 0; if (getChildCount() > 0) { View child = getChildAt(0); scrollRange = Math.max(0, child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop())); } return scrollRange; } }
@Override protected boolean isReadyForPullStart() { return mRefreshableView.getScrollY() == 0; }
@Override protected boolean isReadyForPullEnd() { View scrollViewChild = mRefreshableView.getChildAt(0); if (null != scrollViewChild) { return mRefreshableView.getScrollY() >= (scrollViewChild.getHeight() - getHeight()); } return false; }
@Override public final Orientation getPullToRefreshScrollDirection() { return Orientation.VERTICAL; }
@Override protected ListView createRefreshableView(Context context, AttributeSet attrs) { ListView lv = createListView(context, attrs); // Set it to this so it can be used in ListActivity/ListFragment lv.setId(android.R.id.list); return lv; }
@Override protected boolean isReadyForPullStart() { return isFirstItemVisible(); }调用了一个isFirstItemVisible()方法,也比较好理解,判断是否可以触发刷新动作的依据就是判断ListView的第一个条目是否完全可见:
private boolean isFirstItemVisible() { final Adapter adapter = mRefreshableView.getAdapter(); ...... if (mRefreshableView.getFirstVisiblePosition() <= 1) { final View firstVisibleChild = mRefreshableView.getChildAt(0); if (firstVisibleChild != null) { return firstVisibleChild.getTop() >= mRefreshableView.getTop(); } return false; }可以看出,这里获取ListView的第一个条目,然后判断该条目是否完全可见。
protected boolean isReadyForPullEnd() { return isLastItemVisible(); }相同地,它也调用了一个 isLastItemVisible()的方法:
private boolean isLastItemVisible() { final Adapter adapter = mRefreshableView.getAdapter(); ...... final int lastItemPosition = mRefreshableView.getCount() - 1; final int lastVisiblePosition = mRefreshableView.getLastVisiblePosition(); if (lastVisiblePosition >= lastItemPosition - 1) { final int childIndex = lastVisiblePosition - mRefreshableView.getFirstVisiblePosition(); final View lastVisibleChild = mRefreshableView.getChildAt(childIndex); if (lastVisibleChild != null) { return lastVisibleChild.getBottom() <= mRefreshableView.getBottom(); } } return false; }这里通过判断,可见的最后一个条目是否是最后一个并且是否完全可见来判定到达了底部。
3. 还有错误,原来是缺少构造函数:
public class PullToRefreshRecyclerView extends PullToRefreshBase<RecyclerView>{ public PullToRefreshRecyclerView(Context context) { super(context); } public PullToRefreshRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); } public PullToRefreshRecyclerView(Context context, Mode mode) { super(context, mode); } public PullToRefreshRecyclerView(Context context, Mode mode, AnimationStyle animStyle) { super(context, mode, animStyle); } @Override public Orientation getPullToRefreshScrollDirection() { return null; } @Override protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) { return null; } @Override protected boolean isReadyForPullEnd() { return false; } @Override protected boolean isReadyForPullStart() { return false; } }
@Override public Orientation getPullToRefreshScrollDirection() { return Orientation.VERTICAL; }
@Override protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) { RecyclerView recyclerView = new RecyclerView(context, attrs); return recyclerView; }
@Override protected boolean isReadyForPullStart() { return isFirstItemVisible(); }在这里我们模仿下PullToRefreshListView的设置,搞一个isFirstItemVisible(),那么重点就是这个方法的编写了:
private boolean isFirstItemVisible() { final Adapter<?> adapter = getRefreshableView().getAdapter(); // 如果未设置Adapter或者Adapter没有数据可以下拉刷新 if (null == adapter || adapter.getItemCount() == 0) { if (DEBUG) { Log.d(LOG_TAG, "isFirstItemVisible. Empty View."); } return true; } else { // 第一个条目完全展示,可以刷新 if (getFirstVisiblePosition() == 0) { return mRefreshableView.getChildAt(0).getTop() >= mRefreshableView.getTop(); } } return false; }这里又分为当前RecyclerView是否设置了Adapter,如果未设置数据适配器,我们是允许进行刷新加载动作的,所以返回 true;然后就是获取第一个条目View,getTop()就是获取它距离父控件(RecyclerView)顶端的距离,如果该距离等于RecyclerView距离顶端的距离那么就是第一条木是完全可见的,有点不好理解,来画个图说明下:
/** * @Description: 获取第一个可见子View的位置下标 * * @return int: 位置 * @version 1.0 * @date 2015-9-23 * @Author zhou.wenkai */ private int getFirstVisiblePosition() { View firstVisibleChild = mRefreshableView.getChildAt(0); return firstVisibleChild != null ? mRefreshableView.getChildAdapterPosition(firstVisibleChild) : -1; }