pulltorefresh 用的是这个框架: com.handmark.pulltorefresh.library;
做到一个奇葩功能,就是要使用android material design中的滚动隐藏tab的和搜索框,但是刷新要的是pulltorefresh 刷新 ,列表使用的是recyclerview(recyclerview自带nestedscroll)!
布局大致这样:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
android:background="@color/head_top_bg"
android:minHeight="?attr/actionBarSize"/>
<searchview
app:layout_scrollFlags="scroll|enterAlways"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
app:tabTextColor="@android:color/black"
app:tabIndicatorColor="@color/normalBlue"
app:tabSelectedTextColor="@color/normalBlue"
/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</android.support.design.widget.CoordinatorLayout>
要滚动隐藏的就是toobar 和 searchview,viewpager加载的是几个pulltorefresh<RecyclerView>。
然后,让我头疼了整整一下午的事情发生了,下拉刷新和上拉加载的时候,要滚动隐藏的部分一直都会和pulltorefresh拖动刷新冲突,也就是,无法完全滚动隐藏需要隐藏的tab.
试了n次后无果,只能查看通过寻根问底了,查看代码:
pulltorefresh中处理上拉刷新的关键代码如下:
switch (event.getAction()) {
<span style="white-space:pre"> </span>case MotionEvent.ACTION_MOVE: {
<span style="white-space:pre"> </span>final int activePointerIndex = MotionEventCompat.findPointerIndex(event,
mActivedPointerId);
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span> if (activePointerIndex == -1) {
<span style="white-space:pre"> </span> Log.d(LOG_TAG, "Invalid pointerId=" + mActivedPointerId + " in onTouchEvent");
<span style="white-space:pre"> </span> break;
<span style="white-space:pre"> </span> }
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span> if (mIsBeingDragged) {
<span style="white-space:pre"> </span>mLastMotionY = event.getY();
<span style="white-space:pre"> </span>mLastMotionX = event.getX();
<span style="white-space:pre"> </span>pullEvent();
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>break;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>case MotionEvent.ACTION_DOWN: {
<span style="white-space:pre"> </span>if (isReadyForPull()) {
<span style="white-space:pre"> </span>mLastMotionY = mInitialMotionY = event.getY();
<span style="white-space:pre"> </span>mLastMotionX = mInitialMotionX = event.getX();
<span style="white-space:pre"> </span>return true;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>mActivedPointerId = MotionEventCompat.getPointerId(event, 0);
<span style="white-space:pre"> </span>break;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>case MotionEvent.ACTION_CANCEL:
<span style="white-space:pre"> </span>case MotionEvent.ACTION_UP: {
<span style="white-space:pre"> </span>if (mIsBeingDragged) {
<span style="white-space:pre"> </span>mIsBeingDragged = false;
<span style="white-space:pre"> </span>if (mState == State.RELEASE_TO_REFRESH
<span style="white-space:pre"> </span>&& (null != mOnRefreshListener || null != mOnRefreshListener2)) {
<span style="white-space:pre"> </span>setState(State.REFRESHING, true);
<span style="white-space:pre"> </span>return true;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>// If we're already refreshing, just scroll back to the top
<span style="white-space:pre"> </span>if (isRefreshing()) {
<span style="white-space:pre"> </span>smoothScrollTo(0);
<span style="white-space:pre"> </span>return true;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>// If we haven't returned by here, then we're not in a state
<span style="white-space:pre"> </span>// to pull, so just reset
<span style="white-space:pre"> </span>setState(State.RESET);
<span style="white-space:pre"> </span>return true;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>break;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>return false;
<span style="white-space:pre"> </span>}
就是在ontouchEvent()事件当中进行操作。
下面来看实现recyclerview nestedScroll 的关键代码:
case MotionEvent.ACTION_MOVE: {
final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id " +
mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
} break;
也是在ontouchEvent()事件当中进行的,但是注意关键方法
<pre name="code" class="java">
dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset);
</pre>这个方法就是告诉nestedScrollParent,赶紧滚动你该滚动的东西,滚动了多少在mScrollConsumed里告诉我。我也要滚。然后关键的地方就是,在recyclerview滚完后,事件回传给他的parent 也就是 pulltorefreshBase的ontouchEvent,此时pulltorefreshBase 接收到move后根本就不知道要隐藏的view有没有隐藏了,就开始执行<pre name="code" class="java">pullEvent();
然后,得到的结果就是他开始截断事件传递,不给recyclerview,然后自己去执行拖动刷新。那么,怎么让他知道要滚动的view有没有滚动结束呢?
那么来看pulltoreftreshbase里面的这个方法:
private boolean isReadyForPull() {
switch (mMode) {
case PULL_FROM_START:
return isReadyForPullStart();
case PULL_FROM_END:
return isReadyForPullEnd();
case BOTH:
return isReadyForPullEnd() || isReadyForPullStart();
default:
return false;
}
}
这个方法就是询问是否可以开始拖动刷新啦。如果没有准备好,那我就不去实行拖动刷新。那么,要知道滚动刷新何时完成要的是
mScrollConsumed
这个参数,如果这个mScrollConsumed[1] = 0,那么说明已经要滚动的view已经完成滚动了。可以开始拖动刷新,那么在判断
sReadyForPullStart()
和
isReadyForPullEnd()
的时候加入这个判断就行了,由于这个是个静态方法,那只能去反射判断了,在pulltorefreshbase的继承类里面加上
代码:
@Override
protected boolean isReadyForPullStart() {
int[] scrollconsumed = getNestedScrollConsumed();
if(scrollconsumed[1] != 0){
return false;
}
if (mRefreshableView.getChildCount() <= 0)
return true;
int firstVisiblePosition = mRefreshableView.getChildAdapterPosition(mRefreshableView.getChildAt(0));
if (firstVisiblePosition == 0)
return mRefreshableView.getChildAt(0).getTop() == mRefreshableView.getPaddingTop();
else
return false;
}
@Override
protected boolean isReadyForPullEnd() {
int[] scrollconsumed = getNestedScrollConsumed();
if(scrollconsumed[1] != 0){
return false;
}
if(mRefreshableView.getChildCount() <= 0){
return true;
}
int lastVisiblePosition = mRefreshableView.getChildAdapterPosition(mRefreshableView.getChildAt(mRefreshableView.getChildCount() -1));
if (lastVisiblePosition >= mRefreshableView.getAdapter().getItemCount()-1) {
return mRefreshableView.getChildAt(mRefreshableView.getChildCount() - 1).getBottom() <= mRefreshableView.getBottom();
}
return false;
}
private int[] getNestedScrollConsumed(){
int[] consumed = new int[2];
try {
Field field = RecyclerView.class.getDeclaredField("mScrollConsumed");
if(field != null){
field.setAccessible(true);
consumed = (int[]) field.get(mRefreshableView);
return consumed;
}
return consumed;
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return consumed;
}