RecyclerView代替了ListView腰耙,而NestedScrollView代替了ScrollView懊纳,他們兩個都可以用來跟ToolBar交互迂猴,實現(xiàn)上拉下滑中ToolBar的變化。在NestedScrollView的名字中其實就可以看出他的作用了盾沫,Nested是嵌套的意思裁赠,而ToolBar基本需要嵌套使用.
問題一,使用NestedScrollView嵌套RecyclerView時,滑動lRecyclerView列表會出現(xiàn)強烈的卡頓感.
體驗極其不流暢,這不是我們希望的.于是,百度了一下輕松找到解決辦法.
mRecyclerView.setNestedScrollingEnabled(false);
加上這句之后,整個世界都平靜了,非常流暢啊!
這里面做了啥?
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
...
}
點擊RecyclerView源碼,發(fā)現(xiàn)其實現(xiàn)了ScrollingView和NestedScrollingChild兩個接口.
ScrollingView點進源碼查看.
public interface ScrollingView {
/**
* <p>Compute the horizontal range that the horizontal scrollbar
* represents.</p>
*
* @return the total horizontal range represented by the horizontal
* scrollbar
*
* @see #computeHorizontalScrollExtent()
* @see #computeHorizontalScrollOffset()
* @see android.widget.ScrollBarDrawable
*/
int computeHorizontalScrollRange();
/**
* <p>Compute the horizontal offset of the horizontal scrollbar's thumb
* within the horizontal range. This value is used to compute the position
* of the thumb within the scrollbar's track.</p>
* @return the horizontal offset of the scrollbar's thumb
*
* @see #computeHorizontalScrollRange()
* @see #computeHorizontalScrollExtent()
* @see android.widget.ScrollBarDrawable
*/
int computeHorizontalScrollOffset();
/**
* <p>Compute the horizontal extent of the horizontal scrollbar's thumb
* within the horizontal range. This value is used to compute the length
* of the thumb within the scrollbar's track.</p>
*
* @return the horizontal extent of the scrollbar's thumb
*
* @see #computeHorizontalScrollRange()
* @see #computeHorizontalScrollOffset()
* @see android.widget.ScrollBarDrawable
*/
int computeHorizontalScrollExtent();
/**
* <p>Compute the vertical range that the vertical scrollbar represents.</p>
*
*
* @return the total vertical range represented by the vertical scrollbar
*
* <p>The default range is the drawing height of this view.</p>
*
* @see #computeVerticalScrollExtent()
* @see #computeVerticalScrollOffset()
* @see android.widget.ScrollBarDrawable
*/
int computeVerticalScrollRange();
/**
* <p>Compute the vertical offset of the vertical scrollbar's thumb
* within the horizontal range. This value is used to compute the position
* of the thumb within the scrollbar's track.</p>
*
* @return the vertical offset of the scrollbar's thumb
*
* @see #computeVerticalScrollRange()
* @see #computeVerticalScrollExtent()
* @see android.widget.ScrollBarDrawable
*/
int computeVerticalScrollOffset();
/**
* <p>Compute the vertical extent of the vertical scrollbar's thumb
* within the vertical range. This value is used to compute the length
* of the thumb within the scrollbar's track.</p>
*
*
* @return the vertical extent of the scrollbar's thumb
*
* @see #computeVerticalScrollRange()
* @see #computeVerticalScrollOffset()
* @see android.widget.ScrollBarDrawable
*/
int computeVerticalScrollExtent();
}
可以看出該接口主要關(guān)聯(lián)橫向和縱向滑動距離的計算.那NestedScrollingChild呢?這是個什么?
點進其源碼,經(jīng)過一頓翻譯和分析,發(fā)現(xiàn)其定義的方法并不多:
public interface NestedScrollingChild {
/**
* 設(shè)置嵌套滑動是否能用
*
* @param enabled true to enable nested scrolling, false to disable
*/
public void setNestedScrollingEnabled(boolean enabled);
/**
* 判斷嵌套滑動是否可用
*
* @return true if nested scrolling is enabled
*/
public boolean isNestedScrollingEnabled();
/**
* 開始嵌套滑動
*
* @param axes 表示方向軸,有橫向和豎向
*/
public boolean startNestedScroll(int axes);
/**
* 停止嵌套滑動
*/
public void stopNestedScroll();
/**
* 判斷是否有父View 支持嵌套滑動
* @return whether this view has a nested scrolling parent
*/
public boolean hasNestedScrollingParent();
/**
* 在子View的onInterceptTouchEvent或者onTouch中赴精,調(diào)用該方法通知父View滑動的距離
*
* @param dx x軸上滑動的距離
* @param dy y軸上滑動的距離
* @param consumed 父view消費掉的scroll長度
* @param offsetInWindow 子View的窗體偏移量
* @return 支持的嵌套的父View 是否處理了 滑動事件
*/
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
/**
* 子view處理scroll后調(diào)用
*
* @param dxConsumed x軸上被消費的距離(橫向)
* @param dyConsumed y軸上被消費的距離(豎向)
* @param dxUnconsumed x軸上未被消費的距離
* @param dyUnconsumed y軸上未被消費的距離
* @param offsetInWindow 子View的窗體偏移量
* @return true if the event was dispatched, false if it could not be dispatched.
*/
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
/**
* 滑行時調(diào)用
*
* @param velocityX x 軸上的滑動速率
* @param velocityY y 軸上的滑動速率
* @param consumed 是否被消費
* @return true if the nested scrolling parent consumed or otherwise reacted to the fling
*/
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
/**
* 進行滑行前調(diào)用
*
* @param velocityX x 軸上的滑動速率
* @param velocityY y 軸上的滑動速率
* @return true if a nested scrolling parent consumed the fling
*/
public boolean dispatchNestedPreFling(float velocityX, float velocityY);
}
再回頭看下RecyclerView中
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
mClipToPadding = a.getBoolean(0, true);
a.recycle();
} else {
mClipToPadding = true;
}
setScrollContainer(true);
//默認獲取焦點
setFocusableInTouchMode(true);
final ViewConfiguration vc = ViewConfiguration.get(context);
mTouchSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
mItemAnimator.setListener(mItemAnimatorListener);
initAdapterManager();
initChildrenHelper();
// If not explicitly specified this view is important for accessibility.
if (ViewCompat.getImportantForAccessibility(this)
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
ViewCompat.setImportantForAccessibility(this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
mAccessibilityManager = (AccessibilityManager) getContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE);
setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
// Create the layoutManager if specified.
boolean nestedScrollingEnabled = true;
if (attrs != null) {
int defStyleRes = 0;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
defStyle, defStyleRes);
String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);
int descendantFocusability = a.getInt(
R.styleable.RecyclerView_android_descendantFocusability, -1);
if (descendantFocusability == -1) {
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
}
a.recycle();
createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
if (Build.VERSION.SDK_INT >= 21) {
a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,
defStyle, defStyleRes);
nestedScrollingEnabled = a.getBoolean(0, true);
a.recycle();
}
} else {
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
}
//默認支持嵌套滾動
// Re-set whether nested scrolling is enabled so that it is set on all API levels
setNestedScrollingEnabled(nestedScrollingEnabled);
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return getScrollingChildHelper().isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return getScrollingChildHelper().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
getScrollingChildHelper().stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return getScrollingChildHelper().hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
}
這是全部都交給getScrollingChildHelper()這個方法的返回對象處理了啊佩捞,看看這個方法是怎么實現(xiàn)的。
private NestedScrollingChildHelper getScrollingChildHelper() {
if (mScrollingChildHelper == null) {
mScrollingChildHelper = new NestedScrollingChildHelper(this);
}
return mScrollingChildHelper;
}
這樣的話.NestedScrollingChild 接口的方法都交給NestedScrollingChildHelper這個代理對象處理了.
那么到這個類的源碼中定位到我們關(guān)注方法的實現(xiàn);
public class NestedScrollingChildHelper {
private final View mView;
private ViewParent mNestedScrollingParent;
private boolean mIsNestedScrollingEnabled;
private int[] mTempNestedScrollConsumed;
/**
* Construct a new helper for a given view.
*/
public NestedScrollingChildHelper(View view) {
mView = view;
}
/**
* Enable nested scrolling.
*
* <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
* method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same
* signature to implement the standard policy.</p>
*
* @param enabled true to enable nested scrolling dispatch from this view, false otherwise
*/
public void setNestedScrollingEnabled(boolean enabled) {
if (mIsNestedScrollingEnabled) {
ViewCompat.stopNestedScroll(mView);
}
mIsNestedScrollingEnabled = enabled;
}
public boolean isNestedScrollingEnabled() {
return mIsNestedScrollingEnabled;
}...
}
可以看出RecyclerView默認是setNestedScrollingEnabled(true),是支持嵌套滾動的,也就是說當它嵌套在NestedScrollView中時,默認會隨著NestedScrollView滾動而滾動,放棄了自己的滾動.
所以給我們的感覺就是滯留蕾哟、卡頓.主動將該值置false可以有效解決該問題.
問題二,使用NestedScrollView嵌套RecyclerView時,每次打開界面都是定位在RecyclerView在屏幕頂端,列表上面的布局都被頂上去了.
這個問題花了我不少時間去查原因,最終定位到是RecyclerView搶占了焦點,自動滾動導致的.
查看RecyclerView的源碼發(fā)現(xiàn)一忱,它會在構(gòu)造方法中調(diào)用setFocusableInTouchMode(true),所以搶到焦點后一定會定位到第一行的位置突出RecyclerView的顯示
解決方法就是NestScrollView節(jié)點添加
android:focusableInTouchMode="true"
然后在NestScrollView的子節(jié)點view添加:
android:descendantFocusability="blocksDescendants"
或者 直接
mRecyclerVIew.setFocusableInTouchMode(false)
參考資料: