NestedScrollView巢狀RecyclerView失去慣性解決以及滑動衝突的解決方案以及巢狀RV焦點載入問題
1、簡介
想必大家在開發中不可避免的都在使用RecyclerView吧,那麼也應該在NestScrollView中巢狀過RecyclerView吧,但是呢,你會發現當你的Rv和Nsv同向的時候,那麼會遇到滑動失去慣性的問題,這就是我們的問題一,再有當我們的Nsv是固定高度的,那麼會帶來Rv的滑動問題的,這是問題二,以下我們就探討下這兩個問題。
2、滑動慣性解決方案
說來慚愧,我也不知道是啥問題,下面先貼出解決方案,後面知道了再進行補充吧
manager.setSmoothScrollbarEnabled(true); manager.setAutoMeasureEnabled(true); //取消recycleview的滑動 mRvContent.setHasFixedSize(true); mRvContent.setNestedScrollingEnabled(false);
3. 解決限定高度的滑動衝突
執行效果:
3.1情景還原
佈局如下圖所示,整個佈局的主體是個滑動NestScrollView,上部分為內容的主體,固定樣式,而下部分則為在固定高度的LinearLayout中的RecyclerVierw。然後怎麼滑動都沒有反應
<android.support.v4.widget.NestedScrollView android:id="@+id/mNestView" android:layout_width="match_parent" android:layout_height="match_parent"> <!--獲取焦點 不然會被Recv搶奪--> <LinearLayout android:focusable="true" android:focusableInTouchMode="true" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <View android:background="@color/colorAccent" android:layout_width="match_parent" android:layout_height="200dp"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="520dp"> <com.example.apple.scrolldemo.confict.TNoConflictRecyclerView android:id="@+id/rv" android:layout_width="match_parent" android:layout_height="match_parent"> </com.example.apple.scrolldemo.confict.TNoConflictRecyclerView> </RelativeLayout> </LinearLayout> </android.support.v4.widget.NestedScrollView>
問題解釋
首先應該清楚的是,NestedScrollview的滑動原理是通過 scrollTo()來調整並記錄它的滑動位置,而且只要是有拖拽的動作那麼NestedScrollView就會攔截掉滑動的 事件,由它自己去處理滑動,即滾動控制元件。所以我們需要處理的是當RecyclerView滑動的時候,對應的父佈局 NestedScrollVew不允許攔截。
下面貼出NestScrollView的原始碼
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * This method JUST determines whether we want to intercept the motion. * If we return true, onMotionEvent will be called and we do the actual * scrolling there. */ /* * Shortcut the most recurring case: the user is in the dragging * state and he is moving his finger. We want to intercept this * motion. */ final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check * whether the user has moved far enough from his original down touch. */ /* * Locally do absolute value. mLastMotionY is set to the y value * of the down event. */ final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { // If we don't have a valid id, the touch down wasn't on content. break; } final int pointerIndex = ev.findPointerIndex(activePointerId); if (pointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + activePointerId + " in onInterceptTouchEvent"); break; } final int y = (int) ev.getY(pointerIndex); final int yDiff = Math.abs(y - mLastMotionY); if (yDiff > mTouchSlop && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) { mIsBeingDragged = true; mLastMotionY = y; initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); mNestedYOffset = 0; final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } break; } case MotionEvent.ACTION_DOWN: { final int y = (int) ev.getY(); if (!inChild((int) ev.getX(), y)) { mIsBeingDragged = false; recycleVelocityTracker(); break; } /* * Remember location of down touch. * ACTION_DOWN always refers to pointer index 0. */ mLastMotionY = y; mActivePointerId = ev.getPointerId(0); initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); /* * If being flinged and user touches the screen, initiate drag; * otherwise don't. mScroller.isFinished should be false when * being flinged. We need to call computeScrollOffset() first so that * isFinished() is correct. */ mScroller.computeScrollOffset(); mIsBeingDragged = !mScroller.isFinished(); startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH); break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: /* Release the drag */ mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } stopNestedScroll(ViewCompat.TYPE_TOUCH); break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; }
正確的處理
即是重寫RecyclerView,讓上下滑動時不被攔截掉事件,讓RecyclerView去處理事件。
public class NoConflictRecyclerView extends RecyclerView{
private float mStartX;
private float mStartY;
public NoConflictRecyclerView(Context context) {
super(context);
}
public NoConflictRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public NoConflictRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction())
{
case MotionEvent.ACTION_DOWN:
mStartX = ev.getRawX();
mStartY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float endY = ev.getRawY();
float endX = ev.getRawX();
float x = endX - mStartX;
float y = endY - mStartY;
/* 左右滑動不攔截,上下滑動攔截*/
if (Math.abs(y) > Math.abs(x))
{
/* 已經在頂部了*/
if (y > 0 && !canScrollVertically(-1)){
getParent().requestDisallowInterceptTouchEvent(false);
}else if (y < 0 && !canScrollVertically(1)){
// 不能再上滑了 ========================
getParent().requestDisallowInterceptTouchEvent(false);
}else {
getParent().requestDisallowInterceptTouchEvent(true);
}
}else {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
Log.e("Rv正在滑動","-dx ="+dx+"---dy ="+dy);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
}
}
4. 解決recycleview搶佔焦點
不管NestScrollView的內容是什麼,會自動跳轉到recycleview顯示
1)給NestScrollView節點新增
android:focusableInTouchMode="true"
2)給NestScrollView下的包裹滑動內容的layout新增
android:descendantFocusability="blocksDescendants"
應用場景: 當NestedScrollView內建了RecyclerView 你期望NestedScrollView獲得焦點時,頂部展開。
該節點的viewgroup(也就是NestScrollView)會優先其子類控制元件而獲取到焦點。
左邊是不處理RecyclerView獲得滑動焦點的效果;
右邊是處理郭RecyclerView失去焦點,且NestedScrollView獲得焦點的情況;
效果還是很顯然易見的,希望對大家有幫助。