Android 進階之旅 | NestedScrollView 嵌套RecycleView

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)

參考資料:

https://www.cnblogs.com/fuyaozhishang/p/8232378.html

http://www.reibang.com/p/23306c5886a7?utm_campaign

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市渐苏,隨后出現(xiàn)的幾起案子掀潮,更是在濱河造成了極大的恐慌,老刑警劉巖琼富,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仪吧,死亡現(xiàn)場離奇詭異,居然都是意外死亡鞠眉,警方通過查閱死者的電腦和手機薯鼠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來械蹋,“玉大人出皇,你說我怎么就攤上這事』└辏” “怎么了郊艘?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唯咬。 經(jīng)常有香客問我纱注,道長,這世上最難降的妖魔是什么胆胰? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任狞贱,我火速辦了婚禮,結(jié)果婚禮上蜀涨,老公的妹妹穿的比我還像新娘瞎嬉。我一直安慰自己,他們只是感情好厚柳,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布氧枣。 她就那樣靜靜地躺著,像睡著了一般别垮。 火紅的嫁衣襯著肌膚如雪挑胸。 梳的紋絲不亂的頭發(fā)上胚嘲,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天妒御,我揣著相機與錄音,去河邊找鬼。 笑死砸讳,一個胖子當著我的面吹牛润绵,可吹牛的內(nèi)容都是我干的孝治。 我是一名探鬼主播进倍,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼螟左!你這毒婦竟也來了啡浊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胶背,失蹤者是張志新(化名)和其女友劉穎巷嚣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钳吟,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡廷粒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了红且。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坝茎。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖暇番,靈堂內(nèi)的尸體忽然破棺而出嗤放,到底是詐尸還是另有隱情,我是刑警寧澤壁酬,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布次酌,位于F島的核電站,受9級特大地震影響舆乔,放射性物質(zhì)發(fā)生泄漏岳服。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一蜕煌、第九天 我趴在偏房一處隱蔽的房頂上張望派阱。 院中可真熱鬧诬留,春花似錦斜纪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绿贞,卻和暖如春因块,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背籍铁。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工涡上, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留趾断,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓吩愧,卻偏偏與公主長得像芋酌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子雁佳,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容